@stonyx/utils 0.2.3-alpha.17 → 0.2.3-alpha.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/file.js CHANGED
@@ -13,18 +13,18 @@ export async function createFile(filePath, data, options = {}) {
13
13
  await fsp.writeFile(filePath, options.json ? objToJson(data) : String(data), 'utf8');
14
14
  }
15
15
  catch (error) {
16
- throw new Error(String(error));
16
+ throw error instanceof Error ? error : new Error(String(error));
17
17
  }
18
18
  }
19
19
  export async function updateFile(filePath, data, options = {}) {
20
20
  try {
21
21
  await fsp.access(filePath);
22
22
  const swapFile = `${filePath}.temp-${getTimestamp()}`;
23
- await fsp.writeFile(swapFile, options.json ? objToJson(data) : String(data));
23
+ await fsp.writeFile(swapFile, options.json ? objToJson(data) : String(data), 'utf8');
24
24
  await fsp.rename(swapFile, filePath);
25
25
  }
26
26
  catch (error) {
27
- throw new Error(String(error));
27
+ throw error instanceof Error ? error : new Error(String(error));
28
28
  }
29
29
  }
30
30
  export async function copyFile(sourcePath, targetPath, options = {}) {
@@ -34,19 +34,23 @@ export async function copyFile(sourcePath, targetPath, options = {}) {
34
34
  await fsp.access(sourcePath);
35
35
  }
36
36
  catch (error) {
37
- throw new Error(String(error));
37
+ throw error instanceof Error ? error : new Error(String(error));
38
38
  }
39
39
  try {
40
40
  await fsp.access(targetPath);
41
41
  if (!options.overwrite)
42
42
  return false;
43
43
  }
44
- catch { }
44
+ catch (error) {
45
+ if (isNodeError(error) && error.code === 'ENOENT') { /* file doesn't exist — proceed with copy */ }
46
+ else
47
+ throw error;
48
+ }
45
49
  try {
46
50
  await fsp.copyFile(sourcePath, targetPath);
47
51
  }
48
52
  catch (error) {
49
- throw new Error(String(error));
53
+ throw error instanceof Error ? error : new Error(String(error));
50
54
  }
51
55
  return true;
52
56
  }
@@ -62,7 +66,7 @@ export async function readFile(filePath, options = {}) {
62
66
  if (isNodeError(error) && error.code === 'ENOENT' && missingFileCallback) {
63
67
  return missingFileCallback(filePath);
64
68
  }
65
- throw new Error(String(error));
69
+ throw error instanceof Error ? error : new Error(String(error));
66
70
  }
67
71
  }
68
72
  export async function deleteFile(filePath, options) {
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Generic fuzzy string matching for cross-source reconciliation.
3
+ * Handles Unicode normalization, stop-word filtering, and word-set similarity scoring.
4
+ */
5
+ export interface FuzzyMatchOptions {
6
+ stopWords?: string[];
7
+ delimiter?: string;
8
+ threshold?: number;
9
+ }
10
+ export interface FuzzyMatchResult<T extends {
11
+ name: string;
12
+ }> {
13
+ entry: T;
14
+ score: number;
15
+ }
16
+ export default class FuzzyMatch {
17
+ stopWords: string[];
18
+ delimiter: string;
19
+ threshold: number;
20
+ constructor(options?: FuzzyMatchOptions);
21
+ normalize(name: string): string;
22
+ similarity(nameA: string, nameB: string): number;
23
+ findBestMatch<T extends {
24
+ name: string;
25
+ }>(nameA: string, nameB: string, entries: T[], threshold?: number): FuzzyMatchResult<T> | null;
26
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Generic fuzzy string matching for cross-source reconciliation.
3
+ * Handles Unicode normalization, stop-word filtering, and word-set similarity scoring.
4
+ */
5
+ function normalizeString(name, stopWords = []) {
6
+ let result = name
7
+ .normalize('NFD')
8
+ .replace(/[\u0300-\u036f]/g, '')
9
+ .toLowerCase()
10
+ .replace(/[^a-z0-9\s]/g, ' ');
11
+ if (stopWords.length) {
12
+ const pattern = new RegExp(`\\b(${stopWords.join('|')})\\b`, 'g');
13
+ result = result.replace(pattern, '');
14
+ }
15
+ return result.replace(/\s+/g, ' ').trim();
16
+ }
17
+ function wordSet(normalized) {
18
+ return new Set(normalized.split(' ').filter(w => w.length > 1));
19
+ }
20
+ export default class FuzzyMatch {
21
+ stopWords;
22
+ delimiter;
23
+ threshold;
24
+ constructor(options = {}) {
25
+ this.stopWords = options.stopWords || [];
26
+ this.delimiter = options.delimiter || '\u00B7';
27
+ this.threshold = options.threshold || 0.35;
28
+ }
29
+ normalize(name) {
30
+ return normalizeString(name, this.stopWords);
31
+ }
32
+ similarity(nameA, nameB) {
33
+ const aN = this.normalize(nameA);
34
+ const sN = this.normalize(nameB);
35
+ if (!aN || !sN)
36
+ return 0;
37
+ if (aN === sN)
38
+ return 1.0;
39
+ if (aN.includes(sN) || sN.includes(aN))
40
+ return 0.9;
41
+ const aWords = wordSet(aN);
42
+ const sWords = wordSet(sN);
43
+ if (aWords.size === 0 || sWords.size === 0)
44
+ return 0;
45
+ let overlap = 0;
46
+ for (const w of aWords) {
47
+ if (sWords.has(w)) {
48
+ overlap++;
49
+ }
50
+ else {
51
+ for (const sw of sWords) {
52
+ if (sw.startsWith(w) || w.startsWith(sw)) {
53
+ overlap += 0.7;
54
+ break;
55
+ }
56
+ }
57
+ }
58
+ }
59
+ return overlap / Math.max(aWords.size, sWords.size);
60
+ }
61
+ findBestMatch(nameA, nameB, entries, threshold) {
62
+ const minScore = threshold ?? this.threshold;
63
+ let bestMatch = null;
64
+ let bestScore = 0;
65
+ for (const entry of entries) {
66
+ const parts = entry.name.split(this.delimiter);
67
+ if (parts.length !== 2)
68
+ continue;
69
+ const [entryA, entryB] = parts;
70
+ const normalScore = (this.similarity(nameA, entryA) + this.similarity(nameB, entryB)) / 2;
71
+ const reversedScore = (this.similarity(nameA, entryB) + this.similarity(nameB, entryA)) / 2;
72
+ const score = Math.max(normalScore, reversedScore);
73
+ if (score > bestScore) {
74
+ bestScore = score;
75
+ bestMatch = entry;
76
+ }
77
+ }
78
+ return bestScore >= minScore && bestMatch ? { entry: bestMatch, score: bestScore } : null;
79
+ }
80
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "keywords": [
4
4
  "stonyx-module"
5
5
  ],
6
- "version": "0.2.3-alpha.17",
6
+ "version": "0.2.3-alpha.19",
7
7
  "description": "Utils module for Stonyx Framework",
8
8
  "repository": {
9
9
  "type": "git",
@@ -38,6 +38,10 @@
38
38
  "./string": {
39
39
  "types": "./dist/string.d.ts",
40
40
  "default": "./dist/string.js"
41
+ },
42
+ "./fuzzy-match": {
43
+ "types": "./dist/fuzzy-match.d.ts",
44
+ "default": "./dist/fuzzy-match.js"
41
45
  }
42
46
  },
43
47
  "publishConfig": {