cmpstr 3.2.2 → 3.3.0

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.
Files changed (86) hide show
  1. package/dist/CmpStr.esm.js +2149 -1721
  2. package/dist/CmpStr.esm.min.js +2 -2
  3. package/dist/CmpStr.umd.js +2028 -1604
  4. package/dist/CmpStr.umd.min.js +2 -2
  5. package/dist/cjs/CmpStr.cjs +100 -51
  6. package/dist/cjs/CmpStrAsync.cjs +35 -18
  7. package/dist/cjs/index.cjs +1 -1
  8. package/dist/cjs/metric/Cosine.cjs +1 -1
  9. package/dist/cjs/metric/DamerauLevenshtein.cjs +1 -1
  10. package/dist/cjs/metric/DiceSorensen.cjs +1 -1
  11. package/dist/cjs/metric/Hamming.cjs +1 -1
  12. package/dist/cjs/metric/Jaccard.cjs +1 -1
  13. package/dist/cjs/metric/JaroWinkler.cjs +1 -1
  14. package/dist/cjs/metric/LCS.cjs +1 -1
  15. package/dist/cjs/metric/Levenshtein.cjs +1 -1
  16. package/dist/cjs/metric/Metric.cjs +40 -22
  17. package/dist/cjs/metric/NeedlemanWunsch.cjs +1 -1
  18. package/dist/cjs/metric/QGram.cjs +1 -1
  19. package/dist/cjs/metric/SmithWaterman.cjs +1 -1
  20. package/dist/cjs/phonetic/Caverphone.cjs +1 -1
  21. package/dist/cjs/phonetic/Cologne.cjs +1 -1
  22. package/dist/cjs/phonetic/Metaphone.cjs +1 -1
  23. package/dist/cjs/phonetic/Phonetic.cjs +27 -15
  24. package/dist/cjs/phonetic/Soundex.cjs +1 -1
  25. package/dist/cjs/root.cjs +4 -2
  26. package/dist/cjs/utils/DeepMerge.cjs +102 -97
  27. package/dist/cjs/utils/DiffChecker.cjs +1 -1
  28. package/dist/cjs/utils/Errors.cjs +22 -19
  29. package/dist/cjs/utils/Filter.cjs +59 -24
  30. package/dist/cjs/utils/HashTable.cjs +44 -29
  31. package/dist/cjs/utils/Normalizer.cjs +57 -28
  32. package/dist/cjs/utils/OptionsValidator.cjs +211 -0
  33. package/dist/cjs/utils/Pool.cjs +27 -13
  34. package/dist/cjs/utils/Profiler.cjs +41 -27
  35. package/dist/cjs/utils/Registry.cjs +5 -5
  36. package/dist/cjs/utils/StructuredData.cjs +83 -53
  37. package/dist/cjs/utils/TextAnalyzer.cjs +1 -1
  38. package/dist/esm/CmpStr.mjs +101 -52
  39. package/dist/esm/CmpStrAsync.mjs +35 -18
  40. package/dist/esm/index.mjs +1 -1
  41. package/dist/esm/metric/Cosine.mjs +1 -1
  42. package/dist/esm/metric/DamerauLevenshtein.mjs +1 -1
  43. package/dist/esm/metric/DiceSorensen.mjs +1 -1
  44. package/dist/esm/metric/Hamming.mjs +1 -1
  45. package/dist/esm/metric/Jaccard.mjs +1 -1
  46. package/dist/esm/metric/JaroWinkler.mjs +1 -1
  47. package/dist/esm/metric/LCS.mjs +1 -1
  48. package/dist/esm/metric/Levenshtein.mjs +1 -1
  49. package/dist/esm/metric/Metric.mjs +40 -22
  50. package/dist/esm/metric/NeedlemanWunsch.mjs +1 -1
  51. package/dist/esm/metric/QGram.mjs +1 -1
  52. package/dist/esm/metric/SmithWaterman.mjs +1 -1
  53. package/dist/esm/phonetic/Caverphone.mjs +1 -1
  54. package/dist/esm/phonetic/Cologne.mjs +1 -1
  55. package/dist/esm/phonetic/Metaphone.mjs +1 -1
  56. package/dist/esm/phonetic/Phonetic.mjs +30 -15
  57. package/dist/esm/phonetic/Soundex.mjs +1 -1
  58. package/dist/esm/root.mjs +3 -3
  59. package/dist/esm/utils/DeepMerge.mjs +103 -94
  60. package/dist/esm/utils/DiffChecker.mjs +1 -1
  61. package/dist/esm/utils/Errors.mjs +22 -19
  62. package/dist/esm/utils/Filter.mjs +59 -24
  63. package/dist/esm/utils/HashTable.mjs +44 -29
  64. package/dist/esm/utils/Normalizer.mjs +57 -28
  65. package/dist/esm/utils/OptionsValidator.mjs +210 -0
  66. package/dist/esm/utils/Pool.mjs +27 -13
  67. package/dist/esm/utils/Profiler.mjs +41 -27
  68. package/dist/esm/utils/Registry.mjs +5 -5
  69. package/dist/esm/utils/StructuredData.mjs +83 -53
  70. package/dist/esm/utils/TextAnalyzer.mjs +1 -1
  71. package/dist/types/CmpStr.d.ts +22 -15
  72. package/dist/types/CmpStrAsync.d.ts +3 -0
  73. package/dist/types/index.d.ts +3 -3
  74. package/dist/types/metric/Metric.d.ts +9 -9
  75. package/dist/types/phonetic/Phonetic.d.ts +4 -3
  76. package/dist/types/root.d.ts +3 -2
  77. package/dist/types/utils/DeepMerge.d.ts +80 -58
  78. package/dist/types/utils/Errors.d.ts +25 -8
  79. package/dist/types/utils/Filter.d.ts +4 -1
  80. package/dist/types/utils/HashTable.d.ts +12 -11
  81. package/dist/types/utils/Normalizer.d.ts +2 -1
  82. package/dist/types/utils/OptionsValidator.d.ts +193 -0
  83. package/dist/types/utils/Profiler.d.ts +9 -28
  84. package/dist/types/utils/StructuredData.d.ts +3 -0
  85. package/dist/types/utils/Types.d.ts +13 -1
  86. package/package.json +14 -5
@@ -1,107 +1,116 @@
1
- // CmpStr v3.2.2 build-bb61120-260311 by Paul Köhler @komed3 / MIT License
2
- import { CmpStrUsageError } from './Errors.mjs';
1
+ // CmpStr v3.3.0 build-3699f85-260318 by Paul Köhler @komed3 / MIT License
2
+ import { ErrorUtil } from './Errors.mjs';
3
3
 
4
- const BRACKET_PATTERN = /\[(\d+)]/g;
5
- const PATH_CACHE = new Map();
6
- function parse(p) {
7
- let cached = PATH_CACHE.get(p);
8
- if (cached) return cached;
9
- const parsed = p
10
- .replace(BRACKET_PATTERN, '.$1')
11
- .split('.')
12
- .map((s) => {
13
- const n = Number(s);
14
- return Number.isInteger(n) && String(n) === s ? n : s;
15
- });
16
- PATH_CACHE.set(p, parsed);
17
- return parsed;
18
- }
19
- function get(t, path, fb) {
20
- let o = t;
21
- for (const k of parse(path)) {
22
- if (o == null || !(k in o)) return fb;
23
- o = o[k];
4
+ class DeepMerge {
5
+ static BRACKET_PATTERN = /\[(\d+)]/g;
6
+ static PATH_CACHE = new Map();
7
+ static walk(obj, keys) {
8
+ let o = obj;
9
+ for (let i = 0; i < keys.length; i++) {
10
+ const k = keys[i];
11
+ if (o == null || !(k in o)) return { exists: false };
12
+ o = o[k];
13
+ }
14
+ return { exists: true, value: o };
24
15
  }
25
- return o;
26
- }
27
- function has(t, path) {
28
- let o = t;
29
- for (const k of parse(path)) {
30
- if (o == null || !(k in o)) return false;
31
- o = o[k];
16
+ static parse(p) {
17
+ const cached = DeepMerge.PATH_CACHE.get(p);
18
+ if (cached) return cached;
19
+ const parsed = p
20
+ .replace(DeepMerge.BRACKET_PATTERN, '.$1')
21
+ .split('.')
22
+ .map((s) => {
23
+ const n = Number(s);
24
+ return Number.isInteger(n) && String(n) === s ? n : s;
25
+ });
26
+ if (DeepMerge.PATH_CACHE.size > 2000) DeepMerge.PATH_CACHE.clear();
27
+ DeepMerge.PATH_CACHE.set(p, parsed);
28
+ return parsed;
32
29
  }
33
- return true;
34
- }
35
- function set(t, path, value) {
36
- if (path === '') return value;
37
- const keys = parse(path);
38
- if (t !== undefined && (typeof t !== 'object' || t === null))
39
- throw new CmpStrUsageError(
30
+ static has(t, path) {
31
+ return DeepMerge.walk(t, DeepMerge.parse(path)).exists;
32
+ }
33
+ static get(t, path, fb) {
34
+ const r = DeepMerge.walk(t, DeepMerge.parse(path));
35
+ return r.exists ? r.value : fb;
36
+ }
37
+ static set(t, path, value) {
38
+ if (path === '') return value;
39
+ const keys = DeepMerge.parse(path);
40
+ ErrorUtil.assert(
41
+ t === undefined || (typeof t === 'object' && t !== null),
40
42
  `Cannot set property <${keys[0]}> of <${JSON.stringify(t)}>`,
41
43
  { path: keys[0], target: t }
42
44
  );
43
- const root = t ?? (typeof keys[0] === 'number' ? [] : Object.create(null));
44
- let cur = root;
45
- for (let i = 0; i < keys.length - 1; i++) {
46
- const k = keys[i];
47
- let n = cur[k];
48
- if (n != null && typeof n !== 'object')
49
- throw new CmpStrUsageError(
45
+ const root = t ?? (typeof keys[0] === 'number' ? [] : Object.create(null));
46
+ let cur = root;
47
+ for (let i = 0; i < keys.length - 1; i++) {
48
+ const k = keys[i];
49
+ let n = cur[k];
50
+ ErrorUtil.assert(
51
+ n == null || typeof n === 'object',
50
52
  `Cannot set property <${keys[i + 1]}> of <${JSON.stringify(n)}>`,
51
53
  { path: keys.slice(0, i + 2), value: n }
52
54
  );
53
- if (n == null)
54
- n = cur[k] = typeof keys[i + 1] === 'number' ? [] : Object.create(null);
55
- cur = n;
55
+ if (n == null)
56
+ n = cur[k] = typeof keys[i + 1] === 'number' ? [] : Object.create(null);
57
+ cur = n;
58
+ }
59
+ cur[keys[keys.length - 1]] = value;
60
+ return root;
56
61
  }
57
- cur[keys[keys.length - 1]] = value;
58
- return root;
59
- }
60
- function merge(
61
- t = Object.create(null),
62
- o = Object.create(null),
63
- mergeUndefined = false
64
- ) {
65
- const target = t ?? Object.create(null);
66
- Object.keys(o).forEach((k) => {
67
- const val = o[k];
68
- if (!mergeUndefined && val === undefined) return;
69
- if (k === '__proto__' || k === 'constructor') return;
70
- if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
71
- const existing = target[k];
72
- target[k] = merge(
73
- existing !== null &&
74
- typeof existing === 'object' &&
75
- !Array.isArray(existing)
76
- ? existing
77
- : Object.create(null),
78
- val,
79
- mergeUndefined
80
- );
81
- } else target[k] = val;
82
- });
83
- return target;
84
- }
85
- function rmv(t, path, preserveEmpty = false) {
86
- const keys = parse(path);
87
- const remove = (obj, i = 0) => {
88
- const key = keys[i];
89
- if (!obj || typeof obj !== 'object') return false;
90
- if (i === keys.length - 1) return delete obj[key];
91
- if (!remove(obj[key], i + 1)) return false;
92
- if (!preserveEmpty) {
93
- const val = obj[key];
94
- if (
95
- typeof val === 'object' &&
96
- ((Array.isArray(val) && val.every((v) => v == null)) ||
97
- (!Array.isArray(val) && Object.keys(val).length === 0))
98
- )
99
- delete obj[key];
62
+ static rmv(t, path, preserveEmpty = false) {
63
+ const keys = DeepMerge.parse(path);
64
+ const remove = (obj, i = 0) => {
65
+ const key = keys[i];
66
+ if (!obj || typeof obj !== 'object') return false;
67
+ if (i === keys.length - 1) return delete obj[key];
68
+ if (!remove(obj[key], i + 1)) return false;
69
+ if (!preserveEmpty) {
70
+ const val = obj[key];
71
+ let empty = true;
72
+ if (typeof val === 'object') {
73
+ if (Array.isArray(val))
74
+ for (let i = 0; i < val.length; i++) {
75
+ if (val[i] != null) {
76
+ empty = false;
77
+ break;
78
+ }
79
+ }
80
+ else empty = false;
81
+ }
82
+ if (empty) delete obj[key];
83
+ }
84
+ return true;
85
+ };
86
+ remove(t);
87
+ return t;
88
+ }
89
+ static merge(
90
+ t = Object.create(null),
91
+ o = Object.create(null),
92
+ mergeUndefined = false
93
+ ) {
94
+ const target = t ?? Object.create(null);
95
+ for (const k in o) {
96
+ const val = o[k];
97
+ if (!mergeUndefined && val === undefined) continue;
98
+ if (k === '__proto__' || k === 'constructor') continue;
99
+ if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
100
+ const existing = target[k];
101
+ target[k] = DeepMerge.merge(
102
+ existing !== null &&
103
+ typeof existing === 'object' &&
104
+ !Array.isArray(existing)
105
+ ? existing
106
+ : Object.create(null),
107
+ val,
108
+ mergeUndefined
109
+ );
110
+ } else target[k] = val;
100
111
  }
101
- return true;
102
- };
103
- remove(t);
104
- return t;
112
+ return target;
113
+ }
105
114
  }
106
115
 
107
- export { get, has, merge, rmv, set };
116
+ export { DeepMerge };
@@ -1,4 +1,4 @@
1
- // CmpStr v3.2.2 build-bb61120-260311 by Paul Köhler @komed3 / MIT License
1
+ // CmpStr v3.3.0 build-3699f85-260318 by Paul Köhler @komed3 / MIT License
2
2
  class DiffChecker {
3
3
  a;
4
4
  b;
@@ -1,20 +1,33 @@
1
- // CmpStr v3.2.2 build-bb61120-260311 by Paul Köhler @komed3 / MIT License
1
+ // CmpStr v3.3.0 build-3699f85-260318 by Paul Köhler @komed3 / MIT License
2
2
  class CmpStrError extends Error {
3
3
  code;
4
4
  meta;
5
- cause;
6
5
  when = new Date().toISOString();
7
6
  constructor(code, message, meta, cause) {
8
- super(message);
7
+ super(message, cause !== undefined ? { cause } : undefined);
9
8
  this.name = this.constructor.name;
10
9
  this.code = code;
11
10
  this.meta = meta;
12
- this.cause = cause;
13
11
  if (typeof Error.captureStackTrace === 'function') {
14
12
  Error.captureStackTrace(this, this.constructor);
15
13
  }
16
14
  }
17
- toJSON() {
15
+ format(stack = false) {
16
+ const parts = [`${this.name} [${this.code}]`, this.message];
17
+ if (this.meta)
18
+ for (const _ in this.meta) {
19
+ parts.push(JSON.stringify(this.meta));
20
+ break;
21
+ }
22
+ return (
23
+ parts.join(' - ') +
24
+ (stack && this.stack ? `\nStack Trace:\n${this.stack}` : '')
25
+ );
26
+ }
27
+ toString() {
28
+ return this.format(false);
29
+ }
30
+ toJSON(stack = false) {
18
31
  return {
19
32
  name: this.name,
20
33
  code: this.code,
@@ -26,23 +39,11 @@ class CmpStrError extends Error {
26
39
  ? {
27
40
  name: this.cause.name,
28
41
  message: this.cause.message,
29
- stack: this.cause.stack
42
+ stack: stack && this.cause.stack
30
43
  }
31
44
  : this.cause
32
45
  };
33
46
  }
34
- toString(stack = false) {
35
- const parts = [`${this.name} [${this.code}]`, this.message];
36
- if (this.meta && Object.keys(this.meta).length) {
37
- try {
38
- parts.push(JSON.stringify(this.meta));
39
- } catch {}
40
- }
41
- return (
42
- parts.join(' - ') +
43
- (stack && this.stack ? `\nStack Trace:\n${this.stack}` : '')
44
- );
45
- }
46
47
  }
47
48
  class CmpStrValidationError extends CmpStrError {
48
49
  constructor(message, meta, cause) {
@@ -68,7 +69,7 @@ class ErrorUtil {
68
69
  static assert(condition, message, meta) {
69
70
  if (!condition) throw new CmpStrUsageError(message, meta);
70
71
  }
71
- static create(err, message, meta) {
72
+ static rethrow(err, message, meta) {
72
73
  if (err instanceof CmpStrError) throw err;
73
74
  throw new CmpStrInternalError(message, meta, err);
74
75
  }
@@ -81,6 +82,7 @@ class ErrorUtil {
81
82
  try {
82
83
  return fn();
83
84
  } catch (err) {
85
+ if (err instanceof CmpStrError) throw err;
84
86
  throw new CmpStrInternalError(message, meta, err);
85
87
  }
86
88
  }
@@ -88,6 +90,7 @@ class ErrorUtil {
88
90
  try {
89
91
  return await fn();
90
92
  } catch (err) {
93
+ if (err instanceof CmpStrError) throw err;
91
94
  throw new CmpStrInternalError(message, meta, err);
92
95
  }
93
96
  }
@@ -1,21 +1,33 @@
1
- // CmpStr v3.2.2 build-bb61120-260311 by Paul Köhler @komed3 / MIT License
1
+ // CmpStr v3.3.0 build-3699f85-260318 by Paul Köhler @komed3 / MIT License
2
2
  import { ErrorUtil } from './Errors.mjs';
3
3
 
4
4
  class Filter {
5
+ static IDENTITY = (s) => s;
5
6
  static filters = new Map();
6
7
  static pipeline = new Map();
7
- static getPipeline(hook) {
8
+ static getPipeline(hook, force = false) {
8
9
  return ErrorUtil.wrap(
9
10
  () => {
10
- const cached = Filter.pipeline.get(hook);
11
- if (cached) return cached;
11
+ if (!force) {
12
+ const cached = Filter.pipeline.get(hook);
13
+ if (cached) return cached;
14
+ }
12
15
  const filter = Filter.filters.get(hook);
13
- if (!filter) return (s) => s;
14
- const pipeline = Array.from(filter.values())
15
- .filter((f) => f.active)
16
- .sort((a, b) => a.priority - b.priority)
17
- .map((f) => f.fn);
18
- const fn = (input) => pipeline.reduce((v, f) => f(v), input);
16
+ if (!filter) {
17
+ Filter.pipeline.set(hook, Filter.IDENTITY);
18
+ return Filter.IDENTITY;
19
+ }
20
+ const pipeline = [];
21
+ for (const f of filter.values()) if (f.active) pipeline.push(f);
22
+ pipeline.sort((a, b) => a.priority - b.priority);
23
+ const fn =
24
+ pipeline.length === 0
25
+ ? Filter.IDENTITY
26
+ : (input) => {
27
+ let v = input;
28
+ for (let i = 0; i < pipeline.length; i++) v = pipeline[i].fn(v);
29
+ return v;
30
+ };
19
31
  Filter.pipeline.set(hook, fn);
20
32
  return fn;
21
33
  },
@@ -33,9 +45,16 @@ class Filter {
33
45
  const filter = Filter.filters.get(hook) ?? new Map();
34
46
  const index = filter.get(id);
35
47
  if (index && !index.overrideable) return false;
48
+ if (
49
+ index &&
50
+ index.fn === fn &&
51
+ index.priority === priority &&
52
+ index.active === active
53
+ )
54
+ return true;
36
55
  filter.set(id, { id, fn, priority, active, overrideable });
37
56
  Filter.filters.set(hook, filter);
38
- Filter.pipeline.delete(hook);
57
+ Filter.getPipeline(hook, true);
39
58
  return true;
40
59
  },
41
60
  `Error adding filter <${id}> to hook <${hook}>`,
@@ -43,19 +62,28 @@ class Filter {
43
62
  );
44
63
  }
45
64
  static remove(hook, id) {
46
- Filter.pipeline.delete(hook);
47
65
  const filter = Filter.filters.get(hook);
48
- return filter ? filter.delete(id) : false;
66
+ if (!filter || !filter.delete(id)) return false;
67
+ Filter.getPipeline(hook, true);
68
+ return true;
49
69
  }
50
70
  static pause(hook, id) {
51
- Filter.pipeline.delete(hook);
52
- const f = Filter.filters.get(hook)?.get(id);
53
- return !!(f && ((f.active = false), true));
71
+ const filter = Filter.filters.get(hook);
72
+ if (!filter) return false;
73
+ const f = filter.get(id);
74
+ if (!f || !f.active) return false;
75
+ f.active = false;
76
+ Filter.getPipeline(hook, true);
77
+ return true;
54
78
  }
55
79
  static resume(hook, id) {
56
- Filter.pipeline.delete(hook);
57
- const f = Filter.filters.get(hook)?.get(id);
58
- return !!(f && ((f.active = true), true));
80
+ const filter = Filter.filters.get(hook);
81
+ if (!filter) return false;
82
+ const f = filter.get(id);
83
+ if (!f || f.active) return false;
84
+ f.active = true;
85
+ Filter.getPipeline(hook, true);
86
+ return true;
59
87
  }
60
88
  static list(hook, active = false) {
61
89
  const filter = Filter.filters.get(hook);
@@ -68,7 +96,11 @@ class Filter {
68
96
  return ErrorUtil.wrap(
69
97
  () => {
70
98
  const fn = Filter.getPipeline(hook);
71
- return Array.isArray(input) ? input.map(fn) : fn(input);
99
+ if (typeof input === 'string') return fn(input);
100
+ const arr = input;
101
+ const out = new Array(arr.length);
102
+ for (let i = 0; i < arr.length; i++) out[i] = fn(arr[i]);
103
+ return out;
72
104
  },
73
105
  `Error applying filters for hook <${hook}>`,
74
106
  { hook, input }
@@ -78,16 +110,19 @@ class Filter {
78
110
  return ErrorUtil.wrapAsync(
79
111
  async () => {
80
112
  const fn = Filter.getPipeline(hook);
81
- return Array.isArray(input)
82
- ? Promise.all(input.map(fn))
83
- : Promise.resolve(fn(input));
113
+ if (typeof input === 'string') return Promise.resolve(fn(input));
114
+ const arr = input;
115
+ const out = new Array(arr.length);
116
+ for (let i = 0; i < arr.length; i++)
117
+ out[i] = Promise.resolve(fn(arr[i]));
118
+ return Promise.all(out);
84
119
  },
85
120
  `Error applying filters for hook <${hook}>`,
86
121
  { hook, input }
87
122
  );
88
123
  }
89
124
  static clear(hook) {
90
- Filter.pipeline.clear();
125
+ Filter.clearPipeline();
91
126
  if (hook) Filter.filters.delete(hook);
92
127
  else Filter.filters.clear();
93
128
  }
@@ -1,28 +1,24 @@
1
- // CmpStr v3.2.2 build-bb61120-260311 by Paul Köhler @komed3 / MIT License
1
+ // CmpStr v3.3.0 build-3699f85-260318 by Paul Köhler @komed3 / MIT License
2
2
  class Hasher {
3
3
  static FNV_PRIME = 0x01000193;
4
4
  static HASH_OFFSET = 0x811c9dc5;
5
5
  static fastFNV1a(str) {
6
6
  const len = str.length;
7
+ const limit = len & -4;
7
8
  let hash = this.HASH_OFFSET;
8
- const chunks = Math.floor(len / 4);
9
- for (let i = 0; i < chunks; i++) {
10
- const pos = i * 4;
9
+ let i = 0;
10
+ for (; i < limit; i += 4) {
11
11
  const chunk =
12
- str.charCodeAt(pos) |
13
- (str.charCodeAt(pos + 1) << 8) |
14
- (str.charCodeAt(pos + 2) << 16) |
15
- (str.charCodeAt(pos + 3) << 24);
12
+ str.charCodeAt(i) |
13
+ (str.charCodeAt(i + 1) << 8) |
14
+ (str.charCodeAt(i + 2) << 16) |
15
+ (str.charCodeAt(i + 3) << 24);
16
16
  hash ^= chunk;
17
17
  hash = Math.imul(hash, this.FNV_PRIME);
18
18
  }
19
- const remaining = len % 4;
20
- if (remaining > 0) {
21
- const pos = chunks * 4;
22
- for (let i = 0; i < remaining; i++) {
23
- hash ^= str.charCodeAt(pos + i);
24
- hash = Math.imul(hash, this.FNV_PRIME);
25
- }
19
+ for (; i < len; i++) {
20
+ hash ^= str.charCodeAt(i);
21
+ hash = Math.imul(hash, this.FNV_PRIME);
26
22
  }
27
23
  hash ^= hash >>> 16;
28
24
  hash *= 0x85ebca6b;
@@ -33,32 +29,51 @@ class Hasher {
33
29
  }
34
30
  }
35
31
  class HashTable {
36
- LRU;
32
+ FIFO;
33
+ maxSize;
37
34
  static MAX_LEN = 2048;
38
- static TABLE_SIZE = 10_000;
39
35
  table = new Map();
40
- constructor(LRU = true) {
41
- this.LRU = LRU;
36
+ constructor(FIFO = true, maxSize = 10000) {
37
+ this.FIFO = FIFO;
38
+ this.maxSize = maxSize;
42
39
  }
43
40
  key(label, strs, sorted = false) {
44
- for (const str of strs) if (str.length > HashTable.MAX_LEN) return false;
45
- const hashes = strs.map((s) => Hasher.fastFNV1a(s));
46
- return [label, ...(sorted ? hashes.sort() : hashes)].join('-');
41
+ const n = strs.length;
42
+ const hashes = new Array(n);
43
+ for (let i = 0; i < n; i++) {
44
+ const s = strs[i];
45
+ if (s.length > HashTable.MAX_LEN) return false;
46
+ hashes[i] = Hasher.fastFNV1a(s);
47
+ }
48
+ if (sorted) hashes.sort((a, b) => a - b);
49
+ let key = label;
50
+ for (let i = 0; i < hashes.length; i++) key += '-' + hashes[i];
51
+ return key;
52
+ }
53
+ has(key) {
54
+ return this.table.has(key);
55
+ }
56
+ get(key) {
57
+ return this.table.get(key);
47
58
  }
48
- has = (key) => this.table.has(key);
49
- get = (key) => this.table.get(key);
50
59
  set(key, entry, update = true) {
51
60
  if (!update && this.table.has(key)) return false;
52
- while (!this.table.has(key) && this.table.size >= HashTable.TABLE_SIZE) {
53
- if (!this.LRU) return false;
61
+ if (!this.table.has(key) && this.table.size >= this.maxSize) {
62
+ if (!this.FIFO) return false;
54
63
  this.table.delete(this.table.keys().next().value);
55
64
  }
56
65
  this.table.set(key, entry);
57
66
  return true;
58
67
  }
59
- delete = (key) => this.table.delete(key);
60
- clear = () => this.table.clear();
61
- size = () => this.table.size;
68
+ delete(key) {
69
+ return this.table.delete(key);
70
+ }
71
+ clear() {
72
+ this.table.clear();
73
+ }
74
+ size() {
75
+ return this.table.size;
76
+ }
62
77
  }
63
78
 
64
79
  export { HashTable, Hasher };
@@ -1,4 +1,4 @@
1
- // CmpStr v3.2.2 build-bb61120-260311 by Paul Köhler @komed3 / MIT License
1
+ // CmpStr v3.3.0 build-3699f85-260318 by Paul Köhler @komed3 / MIT License
2
2
  import { ErrorUtil } from './Errors.mjs';
3
3
  import { HashTable } from './HashTable.mjs';
4
4
 
@@ -18,25 +18,49 @@ class Normalizer {
18
18
  static getPipeline(flags) {
19
19
  return ErrorUtil.wrap(
20
20
  () => {
21
- if (Normalizer.pipeline.has(flags))
22
- return Normalizer.pipeline.get(flags);
21
+ const cached = Normalizer.pipeline.get(flags);
22
+ if (cached) return cached;
23
23
  const { REGEX } = Normalizer;
24
- const steps = [
25
- ['d', (s) => s.normalize('NFD')],
26
- ['i', (s) => s.toLowerCase()],
27
- ['k', (s) => s.replace(REGEX.nonLetters, '')],
28
- ['n', (s) => s.replace(REGEX.nonNumbers, '')],
29
- ['r', (s) => s.replace(REGEX.doubleChars, '$1')],
30
- ['s', (s) => s.replace(REGEX.specialChars, '')],
31
- ['t', (s) => s.trim()],
32
- ['u', (s) => s.normalize('NFC')],
33
- ['w', (s) => s.replace(REGEX.whitespace, ' ')],
34
- ['x', (s) => s.normalize('NFKC')]
35
- ];
36
- const pipeline = steps
37
- .filter(([f]) => flags.includes(f))
38
- .map(([, fn]) => fn);
39
- const fn = (s) => pipeline.reduce((v, f) => f(v), s);
24
+ const steps = [];
25
+ for (let i = 0; i < flags.length; i++) {
26
+ switch (flags[i]) {
27
+ case 'd':
28
+ steps.push((s) => s.normalize('NFD'));
29
+ break;
30
+ case 'i':
31
+ steps.push((s) => s.toLowerCase());
32
+ break;
33
+ case 'k':
34
+ steps.push((s) => s.replace(REGEX.nonLetters, ''));
35
+ break;
36
+ case 'n':
37
+ steps.push((s) => s.replace(REGEX.nonNumbers, ''));
38
+ break;
39
+ case 'r':
40
+ steps.push((s) => s.replace(REGEX.doubleChars, '$1'));
41
+ break;
42
+ case 's':
43
+ steps.push((s) => s.replace(REGEX.specialChars, ''));
44
+ break;
45
+ case 't':
46
+ steps.push((s) => s.trim());
47
+ break;
48
+ case 'u':
49
+ steps.push((s) => s.normalize('NFC'));
50
+ break;
51
+ case 'w':
52
+ steps.push((s) => s.replace(REGEX.whitespace, ' '));
53
+ break;
54
+ case 'x':
55
+ steps.push((s) => s.normalize('NFKC'));
56
+ break;
57
+ }
58
+ }
59
+ const fn = (input) => {
60
+ let v = input;
61
+ for (let i = 0; i < steps.length; i++) v = steps[i](v);
62
+ return v;
63
+ };
40
64
  Normalizer.pipeline.set(flags, fn);
41
65
  return fn;
42
66
  },
@@ -44,18 +68,23 @@ class Normalizer {
44
68
  { flags }
45
69
  );
46
70
  }
47
- static normalize(input, flags) {
71
+ static normalize(input, flags, normalizedFlags) {
48
72
  return ErrorUtil.wrap(
49
73
  () => {
50
74
  if (!flags || typeof flags !== 'string' || !input) return input;
51
- flags = this.canonicalFlags(flags);
52
- if (Array.isArray(input))
53
- return input.map((s) => Normalizer.normalize(s, flags));
54
- const key = Normalizer.cache.key(flags, [input]);
55
- if (key && Normalizer.cache.has(key)) return Normalizer.cache.get(key);
56
- const res = Normalizer.getPipeline(flags)(input);
57
- if (key) Normalizer.cache.set(key, res);
58
- return res;
75
+ flags = normalizedFlags ?? this.canonicalFlags(flags);
76
+ const pipeline = Normalizer.getPipeline(flags);
77
+ const normalizeOne = (s) => {
78
+ const key = Normalizer.cache.key(flags, [s]);
79
+ if (key && Normalizer.cache.has(key))
80
+ return Normalizer.cache.get(key);
81
+ const res = pipeline(s);
82
+ if (key) Normalizer.cache.set(key, res);
83
+ return res;
84
+ };
85
+ return Array.isArray(input)
86
+ ? input.map(normalizeOne)
87
+ : normalizeOne(input);
59
88
  },
60
89
  `Failed to normalize input with flags: ${flags}`,
61
90
  { input, flags }