search-algoritm 1.4.1 → 1.5.1

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/CHANGELOG.md CHANGED
@@ -1,19 +1,24 @@
1
1
  # Changelog
2
2
 
3
- All notable
3
+ All notable changes to this project will be documented in this file.
4
4
 
5
- to this project will be documented in this file.
5
+ ---
6
+ ## [1.5.0] - 2026-03-05
7
+ ### Changed
8
+ - Fixed default export for easier import: `const searchAlgoritm = require('search-algoritm')`.
9
+ - Consolidated all helper functions (`normalize`, `levenshtein`, `tokenize`, `matchScore`, `calculateItemScore`) into the main module.
10
+ - Module now works out-of-the-box without requiring `src/searchAlgoritm.js`.
11
+ - Solves previous errors: `TypeError: searchAlgoritm is not a function` and `ReferenceError: normalize is not defined`.
6
12
 
7
- ---
13
+ ---
8
14
  ## [1.4.1] - 2026-03-05
9
15
  ### Notice
10
16
  - Update due to error in version. 1.3.0 never released.
11
17
 
12
-
13
18
  ---
14
19
  ## [1.4.0] - 2026-03-05
15
20
  ### Added
16
- - Search algorithm now dynamically scans **all string fields** in any object.
21
+ - Search algoritm now dynamically scans **all string fields** in any object.
17
22
  - Fuzzy search, tokenization, and scoring applied across all fields.
18
23
  - Stopwords filtering, normalization, and accent-insensitive search preserved.
19
24
 
@@ -51,7 +56,6 @@ All notable
51
56
  - Added `version-status.js` and `postinstall` script to display the current package version status after installation (patch).
52
57
 
53
58
  ---
54
-
55
59
  ## [1.1.0] - 2025-12-01
56
60
  ### Added
57
61
  - **Accent-insensitive search** (`é → e`) for better matching of text with diacritics.
@@ -64,13 +68,11 @@ All notable
64
68
  - Improved fuzzy matching algoritm for more accurate relevance scoring.
65
69
 
66
70
  ---
67
-
68
71
  ## [1.0.1] - 2025-12-01
69
72
  ### Changed
70
73
  - Bumped version due to NPM error preventing re-publishing of 1.0.0.
71
74
 
72
75
  ---
73
-
74
76
  ## [1.0.0] - 2025-12-01
75
77
  ### Added
76
78
  - Initial release of **search-algoritm** npm package.
package/index.js CHANGED
@@ -1,4 +1,3 @@
1
1
  // search-algoritm/index.js
2
- const { searchAlgoritm } = require('./src/searchAlgoritm');
3
-
2
+ const searchAlgoritm = require('./src/searchAlgoritm'); // default export
4
3
  module.exports = searchAlgoritm;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "search-algoritm",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "Simple and lightweight fuzzy search algoritm for titles and descriptions.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,143 +1,147 @@
1
- console.log('[searchAlgoritm] 🔍 Module loaded');
2
-
3
- /**
4
- * Normalize text: lowercase, remove accents, trim, remove simple plurals
5
- * @param {string} str
6
- * @returns {string}
7
- */
8
- const normalize = (str = '') =>
9
- String(str)
10
- .toLowerCase()
11
- .normalize('NFD')
12
- .replace(/[\u0300-\u036f]/g, '')
13
- .replace(/s$/, '')
14
- .trim();
15
-
16
- /**
17
- * Levenshtein distance (edit distance)
18
- */
19
- const levenshtein = (a, b) => {
20
- if (a === b) return 0;
21
- if (!a) return b.length;
22
- if (!b) return a.length;
23
-
24
- const matrix = Array.from({ length: b.length + 1 }, () =>
25
- new Array(a.length + 1)
26
- );
27
-
28
- for (let i = 0; i <= b.length; i++) matrix[i][0] = i;
29
- for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
30
-
31
- for (let i = 1; i <= b.length; i++) {
32
- for (let j = 1; j <= a.length; j++) {
33
- matrix[i][j] =
34
- b[i - 1] === a[j - 1]
35
- ? matrix[i - 1][j - 1]
36
- : Math.min(
37
- matrix[i - 1][j - 1] + 1,
38
- matrix[i][j - 1] + 1,
39
- matrix[i - 1][j] + 1
40
- );
41
- }
42
- }
43
-
44
- return matrix[b.length][a.length];
45
- };
46
-
47
- /**
48
- * Stopwords
49
- */
50
- const stopWords = new Set([
51
- 'and', 'the', 'of', 'a', 'i', 'in', 'on', 'for', 'with',
52
- 'en', 'ett', 'och', 'från', 'med', 'på', 'för'
53
- ]);
54
-
55
- /**
56
- * Tokenize text
57
- */
58
- const tokenize = (text) =>
59
- normalize(text)
60
- .split(/\s+/)
61
- .filter(word => word && !stopWords.has(word));
62
-
63
- /**
64
- * Fuzzy match score
65
- */
66
- const matchScore = (text, query) => {
67
- if (!text || !query) return 0;
68
-
69
- const queryTokens = tokenize(query);
70
- const textTokens = tokenize(text);
71
-
72
- if (!queryTokens.length || !textTokens.length) return 0;
73
-
74
- let score = 0;
75
-
76
- for (const qWord of queryTokens) {
77
- if (textTokens.includes(qWord)) {
78
- score += 25;
79
- continue;
1
+ const searchAlgoritm = (() => {
2
+ console.log('[searchAlgoritm] 🔍 Module loaded');
3
+
4
+ /**
5
+ * Normalize text: lowercase, remove accents, trim, remove simple plurals
6
+ * @param {string} str
7
+ * @returns {string}
8
+ */
9
+ const normalize = (str = '') =>
10
+ String(str)
11
+ .toLowerCase()
12
+ .normalize('NFD')
13
+ .replace(/[\u0300-\u036f]/g, '')
14
+ .replace(/s$/, '')
15
+ .trim();
16
+
17
+ /**
18
+ * Levenshtein distance (edit distance)
19
+ */
20
+ const levenshtein = (a, b) => {
21
+ if (a === b) return 0;
22
+ if (!a) return b.length;
23
+ if (!b) return a.length;
24
+
25
+ const matrix = Array.from({ length: b.length + 1 }, () =>
26
+ new Array(a.length + 1)
27
+ );
28
+
29
+ for (let i = 0; i <= b.length; i++) matrix[i][0] = i;
30
+ for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
31
+
32
+ for (let i = 1; i <= b.length; i++) {
33
+ for (let j = 1; j <= a.length; j++) {
34
+ matrix[i][j] =
35
+ b[i - 1] === a[j - 1]
36
+ ? matrix[i - 1][j - 1]
37
+ : Math.min(
38
+ matrix[i - 1][j - 1] + 1,
39
+ matrix[i][j - 1] + 1,
40
+ matrix[i - 1][j] + 1
41
+ );
42
+ }
80
43
  }
81
44
 
82
- let bestSim = 0;
83
-
84
- for (const tw of textTokens) {
85
- const dist = levenshtein(tw, qWord);
86
- const sim = (1 - dist / Math.max(tw.length, qWord.length)) * 100;
87
- if (sim > bestSim) bestSim = sim;
88
- }
45
+ return matrix[b.length][a.length];
46
+ };
47
+
48
+ /**
49
+ * Stopwords
50
+ */
51
+ const stopWords = new Set([
52
+ 'and', 'the', 'of', 'a', 'i', 'in', 'on', 'for', 'with',
53
+ 'en', 'ett', 'och', 'från', 'med', 'på', 'för'
54
+ ]);
55
+
56
+ /**
57
+ * Tokenize text
58
+ */
59
+ const tokenize = (text) =>
60
+ normalize(text)
61
+ .split(/\s+/)
62
+ .filter(word => word && !stopWords.has(word));
63
+
64
+ /**
65
+ * Fuzzy match score
66
+ */
67
+ const matchScore = (text, query) => {
68
+ if (!text || !query) return 0;
69
+
70
+ const queryTokens = tokenize(query);
71
+ const textTokens = tokenize(text);
72
+
73
+ if (!queryTokens.length || !textTokens.length) return 0;
74
+
75
+ let score = 0;
76
+
77
+ for (const qWord of queryTokens) {
78
+ if (textTokens.includes(qWord)) {
79
+ score += 25;
80
+ continue;
81
+ }
89
82
 
90
- if (bestSim > 50) score += bestSim / 2;
91
- }
83
+ let bestSim = 0;
92
84
 
93
- if (textTokens.join(' ').includes(queryTokens.join(' '))) {
94
- score += 20;
95
- }
85
+ for (const tw of textTokens) {
86
+ const dist = levenshtein(tw, qWord);
87
+ const sim = (1 - dist / Math.max(tw.length, qWord.length)) * 100;
88
+ if (sim > bestSim) bestSim = sim;
89
+ }
96
90
 
97
- return Math.min(Math.round(score), 100);
98
- };
91
+ if (bestSim > 50) score += bestSim / 2;
92
+ }
99
93
 
100
- /**
101
- * Score item by scanning all string fields
102
- */
103
- const calculateItemScore = (item, query) => {
104
- let score = 0;
94
+ if (textTokens.join(' ').includes(queryTokens.join(' '))) {
95
+ score += 20;
96
+ }
105
97
 
106
- for (const key in item) {
107
- if (typeof item[key] === 'string') {
108
- const cacheKey = `_lc_${key}`;
109
- if (!item[cacheKey]) {
110
- item[cacheKey] = normalize(item[key]);
98
+ return Math.min(Math.round(score), 100);
99
+ };
100
+
101
+ /**
102
+ * Score item by scanning all string fields
103
+ */
104
+ const calculateItemScore = (item, query) => {
105
+ let score = 0;
106
+
107
+ for (const key in item) {
108
+ if (typeof item[key] === 'string') {
109
+ const cacheKey = `_lc_${key}`;
110
+ if (!item[cacheKey]) {
111
+ item[cacheKey] = normalize(item[key]);
112
+ }
113
+ score += matchScore(item[cacheKey], query);
111
114
  }
112
- score += matchScore(item[cacheKey], query);
113
115
  }
114
- }
115
116
 
116
- return score;
117
- };
117
+ return score;
118
+ };
118
119
 
119
- /**
120
- * Main search function
121
- */
122
- const searchAlgoritm = (query, dataList, enableLog = false) => {
123
- if (!query || !Array.isArray(dataList)) return [];
120
+ /**
121
+ * Main search function
122
+ */
123
+ const searchAlgoritm = (query, dataList, enableLog = false) => {
124
+ if (!query || !Array.isArray(dataList)) return [];
124
125
 
125
- const normalizedQuery = normalize(query);
126
+ const normalizedQuery = normalize(query);
126
127
 
127
- const results = dataList
128
- .map(item => ({
129
- ...item,
130
- _score: calculateItemScore(item, normalizedQuery)
131
- }))
132
- .filter(item => item._score > 0)
133
- .sort((a, b) => b._score - a._score);
128
+ const results = dataList
129
+ .map(item => ({
130
+ ...item,
131
+ _score: calculateItemScore(item, normalizedQuery)
132
+ }))
133
+ .filter(item => item._score > 0)
134
+ .sort((a, b) => b._score - a._score);
135
+
136
+ if (enableLog) {
137
+ console.log(`[searchAlgoritm] 🔎 Query: "${query}", results: ${results.length}`);
138
+ }
134
139
 
135
- if (enableLog) {
136
- console.log(`[searchAlgoritm] 🔎 Query: "${query}", results: ${results.length}`);
137
- }
140
+ return results;
141
+ };
138
142
 
139
- return results;
140
- };
143
+ // Exponera funktionerna du behöver
144
+ return searchAlgoritm;
145
+ })();
141
146
 
142
- /* Default export */
143
147
  module.exports = searchAlgoritm;