🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@stll/text-search

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@stll/text-search - npm Package Compare versions

Comparing version
1.0.1
to
1.0.2
+2
-0
dist/index.d.mts

@@ -18,5 +18,7 @@ //#region src/types.d.ts

name?: string;
literal?: false;
} | {
pattern: string;
name?: string;
literal?: false;
/** Fuzzy matching distance. Routes to

@@ -23,0 +25,0 @@ * @stll/fuzzy-search instead of regex. */

+4
-2

@@ -99,3 +99,3 @@ import { AhoCorasick } from "@stll/aho-corasick";

}
if ("literal" in entry && entry.literal) {
if (entry.literal === true) {
const hasPerPatternOpts = "caseInsensitive" in entry || "wholeWords" in entry;

@@ -411,2 +411,3 @@ const result = {

}
throw new Error("Unsupported engine type");
}

@@ -422,2 +423,3 @@ /**

}
throw new Error("Unsupported engine type");
}

@@ -440,3 +442,3 @@ /**

if (name !== void 0) result.name = name;
if ("distance" in m && m.distance !== void 0) result.distance = m.distance;
if (m.distance !== void 0) result.distance = m.distance;
return result;

@@ -443,0 +445,0 @@ });

@@ -1,1 +0,1 @@

{"version":3,"file":"index.mjs","names":[],"sources":["../src/engines.ts","../src/classify.ts","../src/merge.ts","../src/text-search.ts","../src/index.ts"],"sourcesContent":["/**\n * Late-bound engine registry.\n *\n * Native and WASM entry points call initEngines()\n * with their respective implementations before any\n * TextSearch instance is created.\n */\n\n/* eslint-disable-next-line @typescript-eslint/no-explicit-any */\ntype Constructor = new (...args: any[]) => any;\n\ntype Engines = {\n AhoCorasick: Constructor;\n FuzzySearch: Constructor;\n RegexSet: Constructor;\n};\n\nlet engines: Engines | undefined;\n\nexport const initEngines = (e: Engines): void => {\n engines = e;\n};\n\nexport const getEngines = (): Engines => {\n if (!engines) {\n throw new Error(\n \"Engines not initialized. Import from \" +\n \"@stll/text-search or @stll/text-search-wasm, \" +\n \"not from internal modules.\",\n );\n }\n return engines;\n};\n","import type { PatternEntry } from \"./types\";\n\n/**\n * Normalized pattern with metadata for routing.\n */\nexport type ClassifiedPattern = {\n /** Original index in the input array. */\n originalIndex: number;\n /** The regex-compatible pattern string. */\n pattern: string | RegExp;\n /** Optional name. */\n name?: string;\n /**\n * Number of top-level alternation branches.\n * Used to detect large alternations that should\n * be isolated into their own RegexSet instance.\n */\n alternationCount: number;\n /**\n * True if the pattern is a pure literal string\n * (no regex metacharacters). These can be routed\n * to Aho-Corasick for SIMD-accelerated matching.\n */\n isLiteral: boolean;\n /**\n * Fuzzy distance if this is a fuzzy pattern.\n * Routes to @stll/fuzzy-search.\n */\n fuzzyDistance?: number | \"auto\";\n /**\n * Per-pattern AC options. When set, this literal\n * is grouped with others that have the same\n * options into a separate AC engine instance.\n */\n acOptions?: {\n caseInsensitive?: boolean;\n wholeWords?: boolean;\n };\n};\n\n/**\n * Check if a string is a pure literal (no regex\n * metacharacters). Pure literals are routed to\n * Aho-Corasick instead of the regex DFA.\n */\nexport function isLiteralPattern(pattern: string): boolean {\n // All standard regex metacharacters cause a\n // pattern to be classified as regex (→ RegexSet).\n // To force literal AC routing for patterns with\n // dots/parens (e.g., \"s.r.o.\", \"č.p.\"), use the\n // explicit { literal: true } PatternEntry flag.\n for (let i = 0; i < pattern.length; i++) {\n const ch = pattern.charAt(i);\n if (\n ch === \"\\\\\" ||\n ch === \".\" ||\n ch === \"^\" ||\n ch === \"$\" ||\n ch === \"*\" ||\n ch === \"+\" ||\n ch === \"?\" ||\n ch === \"{\" ||\n ch === \"}\" ||\n ch === \"(\" ||\n ch === \")\" ||\n ch === \"[\" ||\n ch === \"]\" ||\n ch === \"|\"\n ) {\n return false;\n }\n }\n return pattern.length > 0;\n}\n\n/**\n * Count the maximum alternation branches at any\n * depth in a regex string. Used to detect patterns\n * with large alternations (even nested inside\n * groups) that should be isolated into their own\n * RegexSet to prevent DFA state explosion.\n *\n * \"a|b|c\" → 3\n * \"(a|b)|c\" → 2 (max of top=2, depth1=2)\n * \"(?:Ing\\\\.|Mgr\\\\.|Dr\\\\.)\" → 3 (depth 1)\n */\nexport function countAlternations(pattern: string): number {\n let depth = 0;\n let inClass = false;\n let i = 0;\n\n // Track max alternation count seen at any depth.\n // Each time we enter a group, start a fresh count.\n // When we leave, update the global max.\n let max = 1;\n let currentCount = 1; // count for current group\n const stack: number[] = []; // saved counts\n\n while (i < pattern.length) {\n const ch = pattern[i];\n\n if (ch === \"\\\\\" && i + 1 < pattern.length) {\n i += 2;\n continue;\n }\n\n if (ch === \"[\") inClass = true;\n if (ch === \"]\") inClass = false;\n\n if (!inClass) {\n if (ch === \"(\") {\n stack.push(currentCount);\n currentCount = 1;\n depth++;\n }\n if (ch === \")\") {\n if (currentCount > max) max = currentCount;\n currentCount = stack.pop() ?? 1;\n depth--;\n }\n if (ch === \"|\") {\n currentCount++;\n }\n }\n\n i++;\n }\n // Check top-level count too\n if (currentCount > max) max = currentCount;\n return max;\n}\n\n/**\n * Classify and normalize pattern entries.\n */\nexport function classifyPatterns(entries: PatternEntry[], allLiteral = false): ClassifiedPattern[] {\n return entries.map((entry, i) => {\n if (typeof entry === \"string\") {\n return {\n originalIndex: i,\n pattern: entry,\n alternationCount: allLiteral ? 0 : countAlternations(entry),\n isLiteral: allLiteral || isLiteralPattern(entry),\n };\n }\n\n if (entry instanceof RegExp) {\n return {\n originalIndex: i,\n pattern: entry,\n alternationCount: countAlternations(entry.source),\n isLiteral: false, // RegExp is never literal\n };\n }\n\n // Fuzzy pattern: has `distance` field\n if (\"distance\" in entry) {\n const result: ClassifiedPattern = {\n originalIndex: i,\n pattern: entry.pattern,\n alternationCount: 0,\n isLiteral: false,\n fuzzyDistance: entry.distance,\n };\n if (entry.name !== undefined) result.name = entry.name;\n return result;\n }\n\n // Explicit literal: skip metachar detection\n if (\"literal\" in entry && entry.literal) {\n const hasPerPatternOpts = \"caseInsensitive\" in entry || \"wholeWords\" in entry;\n const result: ClassifiedPattern = {\n originalIndex: i,\n pattern: entry.pattern,\n alternationCount: 0,\n isLiteral: true,\n };\n if (entry.name !== undefined) result.name = entry.name;\n if (hasPerPatternOpts) {\n const opts: NonNullable<ClassifiedPattern[\"acOptions\"]> = {};\n if (entry.caseInsensitive !== undefined) opts.caseInsensitive = entry.caseInsensitive;\n if (entry.wholeWords !== undefined) opts.wholeWords = entry.wholeWords;\n result.acOptions = opts;\n }\n return result;\n }\n\n const pat = entry.pattern;\n const source = pat instanceof RegExp ? pat.source : pat;\n\n const result: ClassifiedPattern = {\n originalIndex: i,\n pattern: pat,\n alternationCount: allLiteral ? 0 : countAlternations(source),\n isLiteral: typeof pat === \"string\" && (allLiteral || isLiteralPattern(pat)),\n };\n if (entry.name !== undefined) result.name = entry.name;\n return result;\n });\n}\n","import type { Match } from \"./types\";\n\n/**\n * Merge matches from multiple engines, sort by\n * position, and select non-overlapping (longest\n * first at ties). Same algorithm as regex-set's\n * internal select_non_overlapping.\n */\nexport function mergeAndSelect(matches: Match[]): Match[] {\n if (matches.length <= 1) return matches;\n\n // Sort: start ascending, longest first at ties\n matches.sort((a, b) => {\n if (a.start !== b.start) {\n return a.start - b.start;\n }\n return b.end - b.start - (a.end - a.start);\n });\n\n // Greedily select non-overlapping\n const selected: Match[] = [];\n let lastEnd = 0;\n\n for (const m of matches) {\n if (m.start >= lastEnd) {\n selected.push(m);\n lastEnd = m.end;\n }\n }\n\n return selected;\n}\n","import type { ClassifiedPattern } from \"./classify\";\nimport { classifyPatterns } from \"./classify\";\nimport { getEngines } from \"./engines\";\nimport { mergeAndSelect } from \"./merge\";\nimport type { Match, PatternEntry, TextSearchOptions } from \"./types\";\n\n/** Common engine interface for dispatch. */\ntype Engine = {\n isMatch: (haystack: string) => boolean;\n findIter: (haystack: string) => Match[];\n};\n\n/**\n * An engine instance with pattern index mapping.\n */\ntype RegexSlot = {\n type: \"regex\";\n rs: Engine;\n indexMap: number[];\n nameMap: (string | undefined)[];\n};\n\ntype AcSlot = {\n type: \"ac\";\n ac: Engine;\n indexMap: number[];\n nameMap: (string | undefined)[];\n};\n\ntype FuzzySlot = {\n type: \"fuzzy\";\n fs: Engine;\n indexMap: number[];\n nameMap: (string | undefined)[];\n};\n\ntype EngineSlot = RegexSlot | AcSlot | FuzzySlot;\n\n/**\n * Multi-engine text search orchestrator.\n *\n * Routes patterns to the optimal engine\n * configuration:\n * - Large alternation patterns get their own\n * RegexSet instance (prevents DFA state explosion)\n * - Normal patterns share a single RegexSet\n * (single-pass multi-pattern DFA)\n *\n * Merges results from all engines into a unified\n * non-overlapping Match[] sorted by position.\n */\nexport class TextSearch {\n private engines: EngineSlot[] = [];\n private patternCount: number;\n private overlapAll: boolean;\n /**\n * True when there's exactly one engine and all\n * patterns map to identity indices (0→0, 1→1, ...).\n * Enables zero-overhead findIter: return raw engine\n * output without remapping or object allocation.\n */\n private zeroOverhead: boolean = false;\n\n constructor(patterns: PatternEntry[], options?: TextSearchOptions) {\n this.patternCount = patterns.length;\n this.overlapAll = options?.overlapStrategy === \"all\";\n const maxAlt = options?.maxAlternations ?? 50;\n const classified = classifyPatterns(patterns, options?.allLiteral ?? false);\n\n // Four buckets:\n // 1. Fuzzy patterns → FuzzySearch (Levenshtein)\n // 2. Pure literals → Aho-Corasick (SIMD)\n // 3. Normal regex → shared RegexSet (DFA)\n // 4. Large alternations → isolated RegexSet\n const fuzzy: ClassifiedPattern[] = [];\n const literals: ClassifiedPattern[] = [];\n const shared: ClassifiedPattern[] = [];\n const isolated: ClassifiedPattern[] = [];\n\n for (const cp of classified) {\n if (cp.fuzzyDistance !== undefined) {\n fuzzy.push(cp);\n } else if (cp.isLiteral) {\n literals.push(cp);\n } else if (cp.alternationCount > maxAlt) {\n isolated.push(cp);\n } else {\n shared.push(cp);\n }\n }\n\n const rsOptions = {\n unicodeBoundaries: options?.unicodeBoundaries ?? true,\n wholeWords: options?.wholeWords ?? false,\n caseInsensitive: options?.caseInsensitive ?? false,\n };\n\n // Build fuzzy engine\n if (fuzzy.length > 0) {\n const fuzzyOpts: Parameters<typeof buildFuzzyEngine>[1] = {\n unicodeBoundaries: rsOptions.unicodeBoundaries,\n wholeWords: rsOptions.wholeWords,\n };\n if (options?.fuzzyMetric !== undefined) fuzzyOpts.metric = options.fuzzyMetric;\n if (options?.normalizeDiacritics !== undefined)\n fuzzyOpts.normalizeDiacritics = options.normalizeDiacritics;\n if (options?.caseInsensitive !== undefined)\n fuzzyOpts.caseInsensitive = options.caseInsensitive;\n this.engines.push(buildFuzzyEngine(fuzzy, fuzzyOpts));\n }\n\n // Build AC engine(s) for pure literals.\n // Group by per-pattern AC options so patterns\n // with different caseInsensitive/wholeWords\n // settings get separate AC instances.\n if (literals.length > 0) {\n const groups = new Map<string, ClassifiedPattern[]>();\n for (const cp of literals) {\n const ci = cp.acOptions?.caseInsensitive ?? rsOptions.caseInsensitive;\n const ww = cp.acOptions?.wholeWords ?? rsOptions.wholeWords;\n const key = `${ci ? 1 : 0}:${ww ? 1 : 0}`;\n const group = groups.get(key);\n if (group) {\n group.push(cp);\n } else {\n groups.set(key, [cp]);\n }\n }\n for (const [key, group] of groups) {\n const [ci, ww] = key.split(\":\");\n this.engines.push(\n buildAcEngine(group, {\n ...rsOptions,\n caseInsensitive: ci === \"1\",\n wholeWords: ww === \"1\",\n }),\n );\n }\n }\n\n // Adaptive regex grouping: try combining shared\n // patterns, measure actual search time on a\n // probe string. If combined is slower than\n // individual, fall back to isolation.\n if (shared.length > 1) {\n const combined = buildRegexEngine(shared, rsOptions);\n // Probe: 1KB of mixed content\n const probe = (\n \"Hello World 123 test@example.com \" +\n \"2025-01-01 +420 123 456 789 \" +\n \"Ing. Jan Novák, s.r.o. Praha 1 \"\n ).repeat(10);\n const t0 = performance.now();\n combined.rs.findIter(probe);\n const combinedMs = performance.now() - t0;\n\n // Individual baseline (sum of isolated scans)\n let individualMs = 0;\n const individualEngines: RegexSlot[] = [];\n for (const cp of shared) {\n const eng = buildRegexEngine([cp], rsOptions);\n const t1 = performance.now();\n eng.rs.findIter(probe);\n individualMs += performance.now() - t1;\n individualEngines.push(eng);\n }\n\n if (combinedMs > individualMs * 1.5) {\n // Combined is >1.5x slower — isolate\n for (const eng of individualEngines) {\n this.engines.push(eng);\n }\n } else {\n this.engines.push(combined);\n }\n } else if (shared.length === 1) {\n this.engines.push(buildRegexEngine(shared, rsOptions));\n }\n\n for (const cp of isolated) {\n this.engines.push(buildRegexEngine([cp], rsOptions));\n }\n\n // Zero-overhead fast path: when all patterns\n // land in a single engine, the indexMap is\n // identity (0→0, 1→1, ...) and no names need\n // attaching. findIter can return raw engine\n // output without any JS-side remapping.\n if (this.engines.length === 1) {\n const engine = this.engines[0];\n if (engine === undefined) {\n throw new Error(\"Expected single engine after length check\");\n }\n const hasNames = engine.nameMap.some((n) => n !== undefined);\n if (!hasNames) {\n this.zeroOverhead = true;\n }\n }\n }\n\n /** Number of patterns. */\n get length(): number {\n return this.patternCount;\n }\n\n /** Returns true if any pattern matches. */\n isMatch(haystack: string): boolean {\n for (const engine of this.engines) {\n if (engineIsMatch(engine, haystack)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Find matches in text.\n *\n * With `overlapStrategy: \"longest\"` (default):\n * returns non-overlapping matches, longest wins.\n *\n * With `overlapStrategy: \"all\"`: returns all\n * matches including overlaps, sorted by position.\n */\n findIter(haystack: string): Match[] {\n // Fast path: single engine, identity indexMap,\n // no names → return raw engine output directly.\n // Zero JS overhead: no remapping, no allocation.\n if (this.zeroOverhead) {\n const engine = this.engines[0];\n if (engine === undefined) {\n throw new Error(\"Zero-overhead path requires a single engine\");\n }\n return engineFindIter(engine, haystack);\n }\n\n // Single engine but needs name remapping\n if (this.engines.length === 1) {\n const engine = this.engines[0];\n if (engine === undefined) {\n throw new Error(\"Expected single engine after length check\");\n }\n return remapMatches(engineFindIter(engine, haystack), engine);\n }\n\n // Multi-engine: collect from all, remap in-place\n const all: Match[] = [];\n for (const engine of this.engines) {\n const matches = engineFindIter(engine, haystack);\n // In-place remapping avoids .map() allocation\n for (const m of remapMatches(matches, engine)) {\n all.push(m);\n }\n }\n\n if (this.overlapAll) {\n return all.sort((a, b) => a.start - b.start);\n }\n\n return mergeAndSelect(all);\n }\n\n /** Which pattern indices matched (not where). */\n whichMatch(haystack: string): number[] {\n const seen = new Set<number>();\n\n for (const engine of this.engines) {\n // AC doesn't have whichMatch — use findIter\n const matches = engineFindIter(engine, haystack);\n for (const m of matches) {\n const idx = engine.indexMap[m.pattern];\n if (idx === undefined) {\n throw new Error(`Missing indexMap entry for pattern ${m.pattern}`);\n }\n seen.add(idx);\n }\n }\n\n return [...seen];\n }\n\n /**\n * Replace all non-overlapping matches.\n * replacements[i] replaces pattern i.\n */\n replaceAll(haystack: string, replacements: string[]): string {\n if (replacements.length !== this.patternCount) {\n throw new Error(\n `Expected ${this.patternCount} ` + `replacements, got ${replacements.length}`,\n );\n }\n\n // Always use non-overlapping matches for\n // replacement, even if overlapStrategy is \"all\".\n const all: Match[] = [];\n for (const engine of this.engines) {\n const matches = engineFindIter(engine, haystack);\n for (const m of remapMatches(matches, engine)) {\n all.push(m);\n }\n }\n const matches = mergeAndSelect(all);\n\n let result = \"\";\n let last = 0;\n\n for (const m of matches) {\n result += haystack.slice(last, m.start);\n const replacement = replacements[m.pattern];\n if (replacement === undefined) {\n throw new Error(`Missing replacement for pattern ${m.pattern}`);\n }\n result += replacement;\n last = m.end;\n }\n\n result += haystack.slice(last);\n return result;\n }\n}\n\n/**\n * Build a RegexSet engine from classified patterns.\n */\nfunction buildRegexEngine(\n patterns: ClassifiedPattern[],\n options: {\n unicodeBoundaries: boolean;\n wholeWords: boolean;\n caseInsensitive: boolean;\n },\n): RegexSlot {\n const rsPatterns: (\n | string\n | RegExp\n | {\n pattern: string | RegExp;\n name?: string;\n }\n )[] = [];\n const indexMap: number[] = [];\n const nameMap: (string | undefined)[] = [];\n\n for (const cp of patterns) {\n if (cp.name !== undefined) {\n rsPatterns.push({\n pattern: cp.pattern,\n name: cp.name,\n });\n } else {\n rsPatterns.push(cp.pattern);\n }\n indexMap.push(cp.originalIndex);\n nameMap.push(cp.name);\n }\n\n const { RegexSet } = getEngines();\n const rs = new RegexSet(rsPatterns, options);\n\n return { type: \"regex\", rs, indexMap, nameMap };\n}\n\n/**\n * Build an Aho-Corasick engine from literal patterns.\n */\nfunction buildAcEngine(\n patterns: ClassifiedPattern[],\n options: {\n unicodeBoundaries: boolean;\n wholeWords: boolean;\n caseInsensitive: boolean;\n },\n): AcSlot {\n const literals: string[] = [];\n const indexMap: number[] = [];\n const nameMap: (string | undefined)[] = [];\n\n for (const cp of patterns) {\n literals.push(cp.pattern as string);\n indexMap.push(cp.originalIndex);\n nameMap.push(cp.name);\n }\n\n const { AhoCorasick } = getEngines();\n const ac = new AhoCorasick(literals, {\n wholeWords: options.wholeWords,\n unicodeBoundaries: options.unicodeBoundaries,\n caseInsensitive: options.caseInsensitive,\n });\n\n return { type: \"ac\", ac, indexMap, nameMap };\n}\n\n/**\n * Build a FuzzySearch engine from fuzzy patterns.\n */\nfunction buildFuzzyEngine(\n patterns: ClassifiedPattern[],\n options: {\n unicodeBoundaries: boolean;\n wholeWords: boolean;\n metric?: \"levenshtein\" | \"damerau-levenshtein\";\n normalizeDiacritics?: boolean;\n caseInsensitive?: boolean;\n },\n): FuzzySlot {\n const fsPatterns: {\n pattern: string;\n distance?: number | \"auto\";\n name?: string;\n }[] = [];\n const indexMap: number[] = [];\n const nameMap: (string | undefined)[] = [];\n\n for (const cp of patterns) {\n const entry: (typeof fsPatterns)[number] = {\n pattern: cp.pattern as string,\n };\n if (cp.fuzzyDistance !== undefined) entry.distance = cp.fuzzyDistance;\n if (cp.name !== undefined) entry.name = cp.name;\n fsPatterns.push(entry);\n indexMap.push(cp.originalIndex);\n nameMap.push(cp.name);\n }\n\n const fsOptions: Record<string, unknown> = {\n unicodeBoundaries: options.unicodeBoundaries,\n wholeWords: options.wholeWords,\n };\n if (options.metric !== undefined) fsOptions.metric = options.metric;\n if (options.normalizeDiacritics !== undefined)\n fsOptions.normalizeDiacritics = options.normalizeDiacritics;\n if (options.caseInsensitive !== undefined) fsOptions.caseInsensitive = options.caseInsensitive;\n const { FuzzySearch } = getEngines();\n const fs = new FuzzySearch(fsPatterns, fsOptions);\n\n return { type: \"fuzzy\", fs, indexMap, nameMap };\n}\n\n/**\n * Dispatch isMatch to the correct engine.\n */\nfunction engineIsMatch(engine: EngineSlot, haystack: string): boolean {\n switch (engine.type) {\n case \"ac\":\n return engine.ac.isMatch(haystack);\n case \"fuzzy\":\n return engine.fs.isMatch(haystack);\n case \"regex\":\n return engine.rs.isMatch(haystack);\n }\n}\n\n/**\n * Dispatch findIter to the correct engine.\n */\nfunction engineFindIter(engine: EngineSlot, haystack: string): Match[] {\n switch (engine.type) {\n case \"ac\":\n return engine.ac.findIter(haystack);\n case \"fuzzy\":\n return engine.fs.findIter(haystack);\n case \"regex\":\n return engine.rs.findIter(haystack);\n }\n}\n\n/**\n * Remap engine-local match indices to original\n * input indices and add names.\n */\nfunction remapMatches(matches: Match[], engine: EngineSlot): Match[] {\n return matches.map((m) => {\n const originalIdx = engine.indexMap[m.pattern];\n if (originalIdx === undefined) {\n throw new Error(`Missing indexMap entry for pattern ${m.pattern}`);\n }\n const name = engine.nameMap[m.pattern];\n const result: Match = {\n pattern: originalIdx,\n start: m.start,\n end: m.end,\n text: m.text,\n };\n if (name !== undefined) {\n result.name = name;\n }\n // Preserve edit distance from fuzzy matches\n if (\"distance\" in m && m.distance !== undefined) {\n result.distance = m.distance as number;\n }\n return result;\n });\n}\n","/* Native entry point — loads @stll engines\n * for Node.js/Bun and re-exports the public API. */\n\nimport { AhoCorasick } from \"@stll/aho-corasick\";\nimport { FuzzySearch } from \"@stll/fuzzy-search\";\nimport { RegexSet } from \"@stll/regex-set\";\n\nimport { initEngines } from \"./engines\";\n\ninitEngines({ AhoCorasick, FuzzySearch, RegexSet });\n\nexport { TextSearch } from \"./text-search\";\nexport type { Match, PatternEntry, TextSearchOptions } from \"./types\";\n"],"mappings":"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"}
{"version":3,"file":"index.mjs","names":[],"sources":["../src/engines.ts","../src/classify.ts","../src/merge.ts","../src/text-search.ts","../src/index.ts"],"sourcesContent":["/**\n * Late-bound engine registry.\n *\n * Native and WASM entry points call initEngines()\n * with their respective implementations before any\n * TextSearch instance is created.\n */\n\nimport type {\n Options as AhoOptions,\n PatternEntry as AhoPatternEntry,\n} from \"@stll/aho-corasick\";\nimport type {\n Options as FuzzyOptions,\n PatternEntry as FuzzyPatternEntry,\n} from \"@stll/fuzzy-search\";\nimport type {\n Options as RegexOptions,\n PatternEntry as RegexPatternEntry,\n} from \"@stll/regex-set\";\nimport type { Match } from \"./types\";\n\ntype Engine = {\n isMatch: (haystack: string) => boolean;\n findIter: (haystack: string) => Match[];\n};\n\ntype Engines = {\n AhoCorasick: new (\n patterns: AhoPatternEntry[],\n options?: AhoOptions,\n ) => Engine;\n FuzzySearch: new (\n patterns: FuzzyPatternEntry[],\n options?: FuzzyOptions,\n ) => Engine;\n RegexSet: new (\n patterns: RegexPatternEntry[],\n options?: RegexOptions,\n ) => Engine;\n};\n\nlet engines: Engines | undefined;\n\nexport const initEngines = (e: Engines): void => {\n engines = e;\n};\n\nexport const getEngines = (): Engines => {\n if (!engines) {\n throw new Error(\n \"Engines not initialized. Import from \" +\n \"@stll/text-search or @stll/text-search-wasm, \" +\n \"not from internal modules.\",\n );\n }\n return engines;\n};\n","import type { PatternEntry } from \"./types\";\n\n/**\n * Normalized pattern with metadata for routing.\n */\nexport type ClassifiedPattern = {\n /** Original index in the input array. */\n originalIndex: number;\n /** The regex-compatible pattern string. */\n pattern: string | RegExp;\n /** Optional name. */\n name?: string;\n /**\n * Number of top-level alternation branches.\n * Used to detect large alternations that should\n * be isolated into their own RegexSet instance.\n */\n alternationCount: number;\n /**\n * True if the pattern is a pure literal string\n * (no regex metacharacters). These can be routed\n * to Aho-Corasick for SIMD-accelerated matching.\n */\n isLiteral: boolean;\n /**\n * Fuzzy distance if this is a fuzzy pattern.\n * Routes to @stll/fuzzy-search.\n */\n fuzzyDistance?: number | \"auto\";\n /**\n * Per-pattern AC options. When set, this literal\n * is grouped with others that have the same\n * options into a separate AC engine instance.\n */\n acOptions?: {\n caseInsensitive?: boolean;\n wholeWords?: boolean;\n };\n};\n\n/**\n * Check if a string is a pure literal (no regex\n * metacharacters). Pure literals are routed to\n * Aho-Corasick instead of the regex DFA.\n */\nexport function isLiteralPattern(pattern: string): boolean {\n // All standard regex metacharacters cause a\n // pattern to be classified as regex (→ RegexSet).\n // To force literal AC routing for patterns with\n // dots/parens (e.g., \"s.r.o.\", \"č.p.\"), use the\n // explicit { literal: true } PatternEntry flag.\n for (let i = 0; i < pattern.length; i++) {\n const ch = pattern.charAt(i);\n if (\n ch === \"\\\\\" ||\n ch === \".\" ||\n ch === \"^\" ||\n ch === \"$\" ||\n ch === \"*\" ||\n ch === \"+\" ||\n ch === \"?\" ||\n ch === \"{\" ||\n ch === \"}\" ||\n ch === \"(\" ||\n ch === \")\" ||\n ch === \"[\" ||\n ch === \"]\" ||\n ch === \"|\"\n ) {\n return false;\n }\n }\n return pattern.length > 0;\n}\n\n/**\n * Count the maximum alternation branches at any\n * depth in a regex string. Used to detect patterns\n * with large alternations (even nested inside\n * groups) that should be isolated into their own\n * RegexSet to prevent DFA state explosion.\n *\n * \"a|b|c\" → 3\n * \"(a|b)|c\" → 2 (max of top=2, depth1=2)\n * \"(?:Ing\\\\.|Mgr\\\\.|Dr\\\\.)\" → 3 (depth 1)\n */\nexport function countAlternations(pattern: string): number {\n let depth = 0;\n let inClass = false;\n let i = 0;\n\n // Track max alternation count seen at any depth.\n // Each time we enter a group, start a fresh count.\n // When we leave, update the global max.\n let max = 1;\n let currentCount = 1; // count for current group\n const stack: number[] = []; // saved counts\n\n while (i < pattern.length) {\n const ch = pattern[i];\n\n if (ch === \"\\\\\" && i + 1 < pattern.length) {\n i += 2;\n continue;\n }\n\n if (ch === \"[\") inClass = true;\n if (ch === \"]\") inClass = false;\n\n if (!inClass) {\n if (ch === \"(\") {\n stack.push(currentCount);\n currentCount = 1;\n depth++;\n }\n if (ch === \")\") {\n if (currentCount > max) max = currentCount;\n currentCount = stack.pop() ?? 1;\n depth--;\n }\n if (ch === \"|\") {\n currentCount++;\n }\n }\n\n i++;\n }\n // Check top-level count too\n if (currentCount > max) max = currentCount;\n return max;\n}\n\n/**\n * Classify and normalize pattern entries.\n */\nexport function classifyPatterns(entries: PatternEntry[], allLiteral = false): ClassifiedPattern[] {\n return entries.map((entry, i) => {\n if (typeof entry === \"string\") {\n return {\n originalIndex: i,\n pattern: entry,\n alternationCount: allLiteral ? 0 : countAlternations(entry),\n isLiteral: allLiteral || isLiteralPattern(entry),\n };\n }\n\n if (entry instanceof RegExp) {\n return {\n originalIndex: i,\n pattern: entry,\n alternationCount: countAlternations(entry.source),\n isLiteral: false, // RegExp is never literal\n };\n }\n\n // Fuzzy pattern: has `distance` field\n if (\"distance\" in entry) {\n const result: ClassifiedPattern = {\n originalIndex: i,\n pattern: entry.pattern,\n alternationCount: 0,\n isLiteral: false,\n fuzzyDistance: entry.distance,\n };\n if (entry.name !== undefined) result.name = entry.name;\n return result;\n }\n\n // Explicit literal: skip metachar detection\n if (entry.literal === true) {\n const hasPerPatternOpts = \"caseInsensitive\" in entry || \"wholeWords\" in entry;\n const result: ClassifiedPattern = {\n originalIndex: i,\n pattern: entry.pattern,\n alternationCount: 0,\n isLiteral: true,\n };\n if (entry.name !== undefined) result.name = entry.name;\n if (hasPerPatternOpts) {\n const opts: NonNullable<ClassifiedPattern[\"acOptions\"]> = {};\n if (entry.caseInsensitive !== undefined) opts.caseInsensitive = entry.caseInsensitive;\n if (entry.wholeWords !== undefined) opts.wholeWords = entry.wholeWords;\n result.acOptions = opts;\n }\n return result;\n }\n\n const pat = entry.pattern;\n const source = pat instanceof RegExp ? pat.source : pat;\n\n const result: ClassifiedPattern = {\n originalIndex: i,\n pattern: pat,\n alternationCount: allLiteral ? 0 : countAlternations(source),\n isLiteral: typeof pat === \"string\" && (allLiteral || isLiteralPattern(pat)),\n };\n if (entry.name !== undefined) result.name = entry.name;\n return result;\n });\n}\n","import type { Match } from \"./types\";\n\n/**\n * Merge matches from multiple engines, sort by\n * position, and select non-overlapping (longest\n * first at ties). Same algorithm as regex-set's\n * internal select_non_overlapping.\n */\nexport function mergeAndSelect(matches: Match[]): Match[] {\n if (matches.length <= 1) return matches;\n\n // Sort: start ascending, longest first at ties\n matches.sort((a, b) => {\n if (a.start !== b.start) {\n return a.start - b.start;\n }\n return b.end - b.start - (a.end - a.start);\n });\n\n // Greedily select non-overlapping\n const selected: Match[] = [];\n let lastEnd = 0;\n\n for (const m of matches) {\n if (m.start >= lastEnd) {\n selected.push(m);\n lastEnd = m.end;\n }\n }\n\n return selected;\n}\n","import type { ClassifiedPattern } from \"./classify\";\nimport { classifyPatterns } from \"./classify\";\nimport { getEngines } from \"./engines\";\nimport { mergeAndSelect } from \"./merge\";\nimport type { Match, PatternEntry, TextSearchOptions } from \"./types\";\n\n/** Common engine interface for dispatch. */\ntype Engine = {\n isMatch: (haystack: string) => boolean;\n findIter: (haystack: string) => Match[];\n};\n\n/**\n * An engine instance with pattern index mapping.\n */\ntype RegexSlot = {\n type: \"regex\";\n rs: Engine;\n indexMap: number[];\n nameMap: (string | undefined)[];\n};\n\ntype AcSlot = {\n type: \"ac\";\n ac: Engine;\n indexMap: number[];\n nameMap: (string | undefined)[];\n};\n\ntype FuzzySlot = {\n type: \"fuzzy\";\n fs: Engine;\n indexMap: number[];\n nameMap: (string | undefined)[];\n};\n\ntype EngineSlot = RegexSlot | AcSlot | FuzzySlot;\n\n/**\n * Multi-engine text search orchestrator.\n *\n * Routes patterns to the optimal engine\n * configuration:\n * - Large alternation patterns get their own\n * RegexSet instance (prevents DFA state explosion)\n * - Normal patterns share a single RegexSet\n * (single-pass multi-pattern DFA)\n *\n * Merges results from all engines into a unified\n * non-overlapping Match[] sorted by position.\n */\nexport class TextSearch {\n private engines: EngineSlot[] = [];\n private patternCount: number;\n private overlapAll: boolean;\n /**\n * True when there's exactly one engine and all\n * patterns map to identity indices (0→0, 1→1, ...).\n * Enables zero-overhead findIter: return raw engine\n * output without remapping or object allocation.\n */\n private zeroOverhead: boolean = false;\n\n constructor(patterns: PatternEntry[], options?: TextSearchOptions) {\n this.patternCount = patterns.length;\n this.overlapAll = options?.overlapStrategy === \"all\";\n const maxAlt = options?.maxAlternations ?? 50;\n const classified = classifyPatterns(patterns, options?.allLiteral ?? false);\n\n // Four buckets:\n // 1. Fuzzy patterns → FuzzySearch (Levenshtein)\n // 2. Pure literals → Aho-Corasick (SIMD)\n // 3. Normal regex → shared RegexSet (DFA)\n // 4. Large alternations → isolated RegexSet\n const fuzzy: ClassifiedPattern[] = [];\n const literals: ClassifiedPattern[] = [];\n const shared: ClassifiedPattern[] = [];\n const isolated: ClassifiedPattern[] = [];\n\n for (const cp of classified) {\n if (cp.fuzzyDistance !== undefined) {\n fuzzy.push(cp);\n } else if (cp.isLiteral) {\n literals.push(cp);\n } else if (cp.alternationCount > maxAlt) {\n isolated.push(cp);\n } else {\n shared.push(cp);\n }\n }\n\n const rsOptions = {\n unicodeBoundaries: options?.unicodeBoundaries ?? true,\n wholeWords: options?.wholeWords ?? false,\n caseInsensitive: options?.caseInsensitive ?? false,\n };\n\n // Build fuzzy engine\n if (fuzzy.length > 0) {\n const fuzzyOpts: Parameters<typeof buildFuzzyEngine>[1] = {\n unicodeBoundaries: rsOptions.unicodeBoundaries,\n wholeWords: rsOptions.wholeWords,\n };\n if (options?.fuzzyMetric !== undefined) fuzzyOpts.metric = options.fuzzyMetric;\n if (options?.normalizeDiacritics !== undefined)\n fuzzyOpts.normalizeDiacritics = options.normalizeDiacritics;\n if (options?.caseInsensitive !== undefined)\n fuzzyOpts.caseInsensitive = options.caseInsensitive;\n this.engines.push(buildFuzzyEngine(fuzzy, fuzzyOpts));\n }\n\n // Build AC engine(s) for pure literals.\n // Group by per-pattern AC options so patterns\n // with different caseInsensitive/wholeWords\n // settings get separate AC instances.\n if (literals.length > 0) {\n const groups = new Map<string, ClassifiedPattern[]>();\n for (const cp of literals) {\n const ci = cp.acOptions?.caseInsensitive ?? rsOptions.caseInsensitive;\n const ww = cp.acOptions?.wholeWords ?? rsOptions.wholeWords;\n const key = `${ci ? 1 : 0}:${ww ? 1 : 0}`;\n const group = groups.get(key);\n if (group) {\n group.push(cp);\n } else {\n groups.set(key, [cp]);\n }\n }\n for (const [key, group] of groups) {\n const [ci, ww] = key.split(\":\");\n this.engines.push(\n buildAcEngine(group, {\n ...rsOptions,\n caseInsensitive: ci === \"1\",\n wholeWords: ww === \"1\",\n }),\n );\n }\n }\n\n // Adaptive regex grouping: try combining shared\n // patterns, measure actual search time on a\n // probe string. If combined is slower than\n // individual, fall back to isolation.\n if (shared.length > 1) {\n const combined = buildRegexEngine(shared, rsOptions);\n // Probe: 1KB of mixed content\n const probe = (\n \"Hello World 123 test@example.com \" +\n \"2025-01-01 +420 123 456 789 \" +\n \"Ing. Jan Novák, s.r.o. Praha 1 \"\n ).repeat(10);\n const t0 = performance.now();\n combined.rs.findIter(probe);\n const combinedMs = performance.now() - t0;\n\n // Individual baseline (sum of isolated scans)\n let individualMs = 0;\n const individualEngines: RegexSlot[] = [];\n for (const cp of shared) {\n const eng = buildRegexEngine([cp], rsOptions);\n const t1 = performance.now();\n eng.rs.findIter(probe);\n individualMs += performance.now() - t1;\n individualEngines.push(eng);\n }\n\n if (combinedMs > individualMs * 1.5) {\n // Combined is >1.5x slower — isolate\n for (const eng of individualEngines) {\n this.engines.push(eng);\n }\n } else {\n this.engines.push(combined);\n }\n } else if (shared.length === 1) {\n this.engines.push(buildRegexEngine(shared, rsOptions));\n }\n\n for (const cp of isolated) {\n this.engines.push(buildRegexEngine([cp], rsOptions));\n }\n\n // Zero-overhead fast path: when all patterns\n // land in a single engine, the indexMap is\n // identity (0→0, 1→1, ...) and no names need\n // attaching. findIter can return raw engine\n // output without any JS-side remapping.\n if (this.engines.length === 1) {\n const engine = this.engines[0];\n if (engine === undefined) {\n throw new Error(\"Expected single engine after length check\");\n }\n const hasNames = engine.nameMap.some((n) => n !== undefined);\n if (!hasNames) {\n this.zeroOverhead = true;\n }\n }\n }\n\n /** Number of patterns. */\n get length(): number {\n return this.patternCount;\n }\n\n /** Returns true if any pattern matches. */\n isMatch(haystack: string): boolean {\n for (const engine of this.engines) {\n if (engineIsMatch(engine, haystack)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Find matches in text.\n *\n * With `overlapStrategy: \"longest\"` (default):\n * returns non-overlapping matches, longest wins.\n *\n * With `overlapStrategy: \"all\"`: returns all\n * matches including overlaps, sorted by position.\n */\n findIter(haystack: string): Match[] {\n // Fast path: single engine, identity indexMap,\n // no names → return raw engine output directly.\n // Zero JS overhead: no remapping, no allocation.\n if (this.zeroOverhead) {\n const engine = this.engines[0];\n if (engine === undefined) {\n throw new Error(\"Zero-overhead path requires a single engine\");\n }\n return engineFindIter(engine, haystack);\n }\n\n // Single engine but needs name remapping\n if (this.engines.length === 1) {\n const engine = this.engines[0];\n if (engine === undefined) {\n throw new Error(\"Expected single engine after length check\");\n }\n return remapMatches(engineFindIter(engine, haystack), engine);\n }\n\n // Multi-engine: collect from all, remap in-place\n const all: Match[] = [];\n for (const engine of this.engines) {\n const matches = engineFindIter(engine, haystack);\n // In-place remapping avoids .map() allocation\n for (const m of remapMatches(matches, engine)) {\n all.push(m);\n }\n }\n\n if (this.overlapAll) {\n return all.sort((a, b) => a.start - b.start);\n }\n\n return mergeAndSelect(all);\n }\n\n /** Which pattern indices matched (not where). */\n whichMatch(haystack: string): number[] {\n const seen = new Set<number>();\n\n for (const engine of this.engines) {\n // AC doesn't have whichMatch — use findIter\n const matches = engineFindIter(engine, haystack);\n for (const m of matches) {\n const idx = engine.indexMap[m.pattern];\n if (idx === undefined) {\n throw new Error(`Missing indexMap entry for pattern ${m.pattern}`);\n }\n seen.add(idx);\n }\n }\n\n return [...seen];\n }\n\n /**\n * Replace all non-overlapping matches.\n * replacements[i] replaces pattern i.\n */\n replaceAll(haystack: string, replacements: string[]): string {\n if (replacements.length !== this.patternCount) {\n throw new Error(\n `Expected ${this.patternCount} ` + `replacements, got ${replacements.length}`,\n );\n }\n\n // Always use non-overlapping matches for\n // replacement, even if overlapStrategy is \"all\".\n const all: Match[] = [];\n for (const engine of this.engines) {\n const matches = engineFindIter(engine, haystack);\n for (const m of remapMatches(matches, engine)) {\n all.push(m);\n }\n }\n const matches = mergeAndSelect(all);\n\n let result = \"\";\n let last = 0;\n\n for (const m of matches) {\n result += haystack.slice(last, m.start);\n const replacement = replacements[m.pattern];\n if (replacement === undefined) {\n throw new Error(`Missing replacement for pattern ${m.pattern}`);\n }\n result += replacement;\n last = m.end;\n }\n\n result += haystack.slice(last);\n return result;\n }\n}\n\n/**\n * Build a RegexSet engine from classified patterns.\n */\nfunction buildRegexEngine(\n patterns: ClassifiedPattern[],\n options: {\n unicodeBoundaries: boolean;\n wholeWords: boolean;\n caseInsensitive: boolean;\n },\n): RegexSlot {\n const rsPatterns: (\n | string\n | RegExp\n | {\n pattern: string | RegExp;\n name?: string;\n }\n )[] = [];\n const indexMap: number[] = [];\n const nameMap: (string | undefined)[] = [];\n\n for (const cp of patterns) {\n if (cp.name !== undefined) {\n rsPatterns.push({\n pattern: cp.pattern,\n name: cp.name,\n });\n } else {\n rsPatterns.push(cp.pattern);\n }\n indexMap.push(cp.originalIndex);\n nameMap.push(cp.name);\n }\n\n const { RegexSet } = getEngines();\n const rs = new RegexSet(rsPatterns, options);\n\n return { type: \"regex\", rs, indexMap, nameMap };\n}\n\n/**\n * Build an Aho-Corasick engine from literal patterns.\n */\nfunction buildAcEngine(\n patterns: ClassifiedPattern[],\n options: {\n unicodeBoundaries: boolean;\n wholeWords: boolean;\n caseInsensitive: boolean;\n },\n): AcSlot {\n const literals: string[] = [];\n const indexMap: number[] = [];\n const nameMap: (string | undefined)[] = [];\n\n for (const cp of patterns) {\n literals.push(cp.pattern as string);\n indexMap.push(cp.originalIndex);\n nameMap.push(cp.name);\n }\n\n const { AhoCorasick } = getEngines();\n const ac = new AhoCorasick(literals, {\n wholeWords: options.wholeWords,\n unicodeBoundaries: options.unicodeBoundaries,\n caseInsensitive: options.caseInsensitive,\n });\n\n return { type: \"ac\", ac, indexMap, nameMap };\n}\n\n/**\n * Build a FuzzySearch engine from fuzzy patterns.\n */\nfunction buildFuzzyEngine(\n patterns: ClassifiedPattern[],\n options: {\n unicodeBoundaries: boolean;\n wholeWords: boolean;\n metric?: \"levenshtein\" | \"damerau-levenshtein\";\n normalizeDiacritics?: boolean;\n caseInsensitive?: boolean;\n },\n): FuzzySlot {\n const fsPatterns: {\n pattern: string;\n distance?: number | \"auto\";\n name?: string;\n }[] = [];\n const indexMap: number[] = [];\n const nameMap: (string | undefined)[] = [];\n\n for (const cp of patterns) {\n const entry: (typeof fsPatterns)[number] = {\n pattern: cp.pattern as string,\n };\n if (cp.fuzzyDistance !== undefined) entry.distance = cp.fuzzyDistance;\n if (cp.name !== undefined) entry.name = cp.name;\n fsPatterns.push(entry);\n indexMap.push(cp.originalIndex);\n nameMap.push(cp.name);\n }\n\n const fsOptions: {\n unicodeBoundaries: boolean;\n wholeWords: boolean;\n metric?: \"levenshtein\" | \"damerau-levenshtein\";\n normalizeDiacritics?: boolean;\n caseInsensitive?: boolean;\n } = {\n unicodeBoundaries: options.unicodeBoundaries,\n wholeWords: options.wholeWords,\n };\n if (options.metric !== undefined) fsOptions.metric = options.metric;\n if (options.normalizeDiacritics !== undefined)\n fsOptions.normalizeDiacritics = options.normalizeDiacritics;\n if (options.caseInsensitive !== undefined) fsOptions.caseInsensitive = options.caseInsensitive;\n const { FuzzySearch } = getEngines();\n const fs = new FuzzySearch(fsPatterns, fsOptions);\n\n return { type: \"fuzzy\", fs, indexMap, nameMap };\n}\n\n/**\n * Dispatch isMatch to the correct engine.\n */\nfunction engineIsMatch(engine: EngineSlot, haystack: string): boolean {\n switch (engine.type) {\n case \"ac\":\n return engine.ac.isMatch(haystack);\n case \"fuzzy\":\n return engine.fs.isMatch(haystack);\n case \"regex\":\n return engine.rs.isMatch(haystack);\n }\n throw new Error(\"Unsupported engine type\");\n}\n\n/**\n * Dispatch findIter to the correct engine.\n */\nfunction engineFindIter(engine: EngineSlot, haystack: string): Match[] {\n switch (engine.type) {\n case \"ac\":\n return engine.ac.findIter(haystack);\n case \"fuzzy\":\n return engine.fs.findIter(haystack);\n case \"regex\":\n return engine.rs.findIter(haystack);\n }\n throw new Error(\"Unsupported engine type\");\n}\n\n/**\n * Remap engine-local match indices to original\n * input indices and add names.\n */\nfunction remapMatches(matches: Match[], engine: EngineSlot): Match[] {\n return matches.map((m) => {\n const originalIdx = engine.indexMap[m.pattern];\n if (originalIdx === undefined) {\n throw new Error(`Missing indexMap entry for pattern ${m.pattern}`);\n }\n const name = engine.nameMap[m.pattern];\n const result: Match = {\n pattern: originalIdx,\n start: m.start,\n end: m.end,\n text: m.text,\n };\n if (name !== undefined) {\n result.name = name;\n }\n // Preserve edit distance from fuzzy matches\n if (m.distance !== undefined) {\n result.distance = m.distance;\n }\n return result;\n });\n}\n","/* Native entry point — loads @stll engines\n * for Node.js/Bun and re-exports the public API. */\n\nimport { AhoCorasick } from \"@stll/aho-corasick\";\nimport { FuzzySearch } from \"@stll/fuzzy-search\";\nimport { RegexSet } from \"@stll/regex-set\";\n\nimport { initEngines } from \"./engines\";\n\ninitEngines({ AhoCorasick, FuzzySearch, RegexSet });\n\nexport { TextSearch } from \"./text-search\";\nexport type { Match, PatternEntry, TextSearchOptions } from \"./types\";\n"],"mappings":"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"}
{
"name": "@stll/text-search",
"version": "1.0.1",
"version": "1.0.2",
"description": "Multi-engine text search orchestrator for Node.js and Bun. Routes literals, regex, and fuzzy patterns to the right engine automatically.",

@@ -22,3 +22,3 @@ "keywords": [

},
"packageManager": "bun@1.3.12",
"packageManager": "bun@1.3.14",
"files": [

@@ -48,20 +48,31 @@ "dist"

"test:runtime:node": "node scripts/runtime-smoke.mjs",
"lint": "oxlint .",
"format": "oxfmt .",
"typecheck": "tsc --noEmit",
"lint": "bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --deny-warnings --type-aware .",
"lint:fix": "bun --bun oxlint -c oxlint.config.ts --type-aware --fix .",
"format": "oxfmt . \"!.ai/**\" \"!.agents/**\" \"!.claude/**\" \"!AGENTS.md\" \"!CLAUDE.md\" \"!GEMINI.md\"",
"version:check": "node scripts/version-sync.mjs check",
"version:sync": "node scripts/version-sync.mjs sync"
"version:sync": "node scripts/version-sync.mjs sync",
"sync-ai": "bash scripts/sync-ai-skills.sh",
"sync-ai:check": "bash scripts/sync-ai-skills.sh --check"
},
"dependencies": {
"@stll/aho-corasick": "^1.0.0",
"@stll/fuzzy-search": "^1.0.0",
"@stll/regex-set": "^1.0.1"
"@stll/aho-corasick": "^1.0.1",
"@stll/fuzzy-search": "^1.1.0",
"@stll/regex-set": "^1.0.2"
},
"devDependencies": {
"@types/node": "^25.5.2",
"bun-types": "^1.3.10",
"oxfmt": "^0.44.0",
"oxlint": "^1.59.0",
"tsdown": "^0.21.6",
"typescript": "^5.9.3",
"vite": "^8.0.8"
"@stll/aho-corasick-wasm": "^1.0.1",
"@stll/fuzzy-search-wasm": "^1.1.0",
"@stll/oxlint-config": "^0.2.0",
"@stll/regex-set-wasm": "^1.0.2",
"@stll/typescript-config": "^0.2.0",
"@types/node": "^25.9.1",
"bun-types": "^1.3.14",
"oxfmt": "^0.52.0",
"oxlint": "^1.67.0",
"oxlint-tsgolint": "^0.23.0",
"tsdown": "^0.22.1",
"typescript": "^6.0.3",
"unrun": "^0.3.0",
"vite": "^8.0.14"
},

@@ -68,0 +79,0 @@ "engines": {