@oscarpalmer/atoms 0.193.0 → 0.194.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 (204) hide show
  1. package/dist/array/difference.mjs +2 -2
  2. package/dist/array/intersection.mjs +2 -2
  3. package/dist/array/match.mjs +13 -13
  4. package/dist/array/slice.mjs +5 -5
  5. package/dist/array/sort.d.mts +5 -1
  6. package/dist/array/sort.mjs +4 -2
  7. package/dist/array/union.mjs +2 -2
  8. package/dist/beacon.d.mts +8 -1
  9. package/dist/beacon.mjs +52 -60
  10. package/dist/color/constants.d.mts +1 -1
  11. package/dist/color/constants.mjs +3 -5
  12. package/dist/color/instance.mjs +34 -41
  13. package/dist/color/misc/format.d.mts +3 -2
  14. package/dist/color/misc/format.mjs +4 -1
  15. package/dist/color/misc/is.d.mts +9 -1
  16. package/dist/color/misc/is.mjs +11 -1
  17. package/dist/color/misc/state.d.mts +6 -6
  18. package/dist/color/misc/state.mjs +11 -14
  19. package/dist/color/models.d.mts +23 -68
  20. package/dist/function/assert.mjs +2 -2
  21. package/dist/function/limit.d.mts +0 -6
  22. package/dist/function/limit.mjs +2 -2
  23. package/dist/function/memoize.mjs +19 -32
  24. package/dist/function/once.d.mts +0 -3
  25. package/dist/function/once.mjs +16 -13
  26. package/dist/function/retry.d.mts +1 -24
  27. package/dist/function/retry.mjs +1 -1
  28. package/dist/function/work.mjs +13 -11
  29. package/dist/herald.d.mts +30 -31
  30. package/dist/herald.mjs +74 -56
  31. package/dist/index.d.mts +100 -185
  32. package/dist/index.mjs +680 -702
  33. package/dist/internal/abort.d.mts +10 -0
  34. package/dist/internal/abort.mjs +14 -0
  35. package/dist/internal/array/chunk.mjs +2 -2
  36. package/dist/internal/array/find.mjs +2 -2
  37. package/dist/internal/array/sets.d.mts +4 -4
  38. package/dist/internal/array/sets.mjs +7 -7
  39. package/dist/internal/function/timer.mjs +19 -15
  40. package/dist/internal/number.mjs +7 -7
  41. package/dist/internal/result.mjs +3 -3
  42. package/dist/internal/sized.mjs +4 -4
  43. package/dist/internal/string.mjs +4 -4
  44. package/dist/internal/subscription.d.mts +24 -8
  45. package/dist/internal/subscription.mjs +49 -36
  46. package/dist/internal/value/equal.mjs +6 -6
  47. package/dist/internal/value/misc.mjs +14 -14
  48. package/dist/internal/value/set.mjs +2 -2
  49. package/dist/logger.mjs +15 -39
  50. package/dist/promise/constants.d.mts +17 -0
  51. package/dist/promise/constants.mjs +14 -0
  52. package/dist/promise/delay.mjs +7 -10
  53. package/dist/promise/helpers.mjs +1 -1
  54. package/dist/promise/index.mjs +14 -17
  55. package/dist/promise/misc.d.mts +2 -1
  56. package/dist/promise/misc.mjs +8 -7
  57. package/dist/promise/models.d.mts +4 -16
  58. package/dist/promise/models.mjs +4 -13
  59. package/dist/promise/timed.mjs +9 -10
  60. package/dist/query.mjs +15 -15
  61. package/dist/queue.mjs +103 -116
  62. package/dist/random.mjs +7 -7
  63. package/dist/result/index.d.mts +0 -3
  64. package/dist/result/index.mjs +1 -1
  65. package/dist/result/match.d.mts +0 -3
  66. package/dist/result/match.mjs +4 -4
  67. package/dist/result/misc.mjs +2 -2
  68. package/dist/result/work/flow.d.mts +0 -3
  69. package/dist/result/work/flow.mjs +5 -5
  70. package/dist/result/work/pipe.d.mts +0 -3
  71. package/dist/result/work/pipe.mjs +3 -3
  72. package/dist/string/case.mjs +20 -20
  73. package/dist/string/fuzzy.mjs +23 -18
  74. package/dist/string/index.mjs +7 -7
  75. package/dist/string/normalize.mjs +14 -14
  76. package/dist/string/template.mjs +2 -2
  77. package/dist/value/clone.mjs +10 -10
  78. package/dist/value/smush.mjs +2 -2
  79. package/package.json +13 -5
  80. package/src/array/difference.ts +0 -73
  81. package/src/array/exists.ts +0 -93
  82. package/src/array/filter.ts +0 -182
  83. package/src/array/find.ts +0 -183
  84. package/src/array/first.ts +0 -196
  85. package/src/array/flatten.ts +0 -20
  86. package/src/array/from.ts +0 -129
  87. package/src/array/get.ts +0 -131
  88. package/src/array/group-by.ts +0 -369
  89. package/src/array/index.ts +0 -26
  90. package/src/array/insert.ts +0 -52
  91. package/src/array/intersection.ts +0 -72
  92. package/src/array/last.ts +0 -194
  93. package/src/array/match.ts +0 -436
  94. package/src/array/move.ts +0 -315
  95. package/src/array/partition.ts +0 -91
  96. package/src/array/push.ts +0 -23
  97. package/src/array/reverse.ts +0 -33
  98. package/src/array/select.ts +0 -260
  99. package/src/array/single.ts +0 -93
  100. package/src/array/slice.ts +0 -335
  101. package/src/array/sort.ts +0 -607
  102. package/src/array/splice.ts +0 -106
  103. package/src/array/swap.ts +0 -322
  104. package/src/array/to-map.ts +0 -371
  105. package/src/array/to-record.ts +0 -351
  106. package/src/array/to-set.ts +0 -80
  107. package/src/array/toggle.ts +0 -79
  108. package/src/array/union.ts +0 -72
  109. package/src/array/unique.ts +0 -67
  110. package/src/array/update.ts +0 -75
  111. package/src/beacon.ts +0 -378
  112. package/src/color/constants.ts +0 -137
  113. package/src/color/index.ts +0 -83
  114. package/src/color/instance.ts +0 -162
  115. package/src/color/misc/alpha.ts +0 -56
  116. package/src/color/misc/format.ts +0 -29
  117. package/src/color/misc/get.ts +0 -210
  118. package/src/color/misc/is.ts +0 -221
  119. package/src/color/misc/state.ts +0 -243
  120. package/src/color/models.ts +0 -389
  121. package/src/color/space/hex.ts +0 -184
  122. package/src/color/space/hsl.ts +0 -174
  123. package/src/color/space/hwb.ts +0 -153
  124. package/src/color/space/rgb.ts +0 -240
  125. package/src/function/assert.ts +0 -174
  126. package/src/function/index.ts +0 -2
  127. package/src/function/limit.ts +0 -83
  128. package/src/function/memoize.ts +0 -202
  129. package/src/function/once.ts +0 -200
  130. package/src/function/retry.ts +0 -175
  131. package/src/function/work.ts +0 -1007
  132. package/src/herald.ts +0 -216
  133. package/src/index.ts +0 -68
  134. package/src/internal/array/callbacks.ts +0 -46
  135. package/src/internal/array/chunk.ts +0 -51
  136. package/src/internal/array/compact.ts +0 -56
  137. package/src/internal/array/find.ts +0 -200
  138. package/src/internal/array/group.ts +0 -42
  139. package/src/internal/array/index-of.ts +0 -182
  140. package/src/internal/array/insert.ts +0 -80
  141. package/src/internal/array/overlap.ts +0 -35
  142. package/src/internal/array/sets.ts +0 -74
  143. package/src/internal/array/shuffle.ts +0 -33
  144. package/src/internal/array/update.ts +0 -52
  145. package/src/internal/function/misc.ts +0 -8
  146. package/src/internal/function/timer.ts +0 -168
  147. package/src/internal/is.ts +0 -244
  148. package/src/internal/math/aggregate.ts +0 -164
  149. package/src/internal/math/misc.ts +0 -28
  150. package/src/internal/number.ts +0 -129
  151. package/src/internal/random.ts +0 -49
  152. package/src/internal/result.ts +0 -92
  153. package/src/internal/sized.ts +0 -25
  154. package/src/internal/string.ts +0 -128
  155. package/src/internal/subscription.ts +0 -264
  156. package/src/internal/value/compare.ts +0 -170
  157. package/src/internal/value/equal.ts +0 -465
  158. package/src/internal/value/get.ts +0 -57
  159. package/src/internal/value/handlers.ts +0 -95
  160. package/src/internal/value/has.ts +0 -118
  161. package/src/internal/value/misc.ts +0 -125
  162. package/src/internal/value/partial.ts +0 -46
  163. package/src/internal/value/set.ts +0 -132
  164. package/src/is.ts +0 -182
  165. package/src/logger.ts +0 -230
  166. package/src/math.ts +0 -281
  167. package/src/models.ts +0 -286
  168. package/src/number.ts +0 -1
  169. package/src/promise/delay.ts +0 -62
  170. package/src/promise/helpers.ts +0 -96
  171. package/src/promise/index.ts +0 -300
  172. package/src/promise/misc.ts +0 -96
  173. package/src/promise/models.ts +0 -195
  174. package/src/promise/timed.ts +0 -91
  175. package/src/query.ts +0 -147
  176. package/src/queue.ts +0 -814
  177. package/src/random.ts +0 -125
  178. package/src/result/index.ts +0 -105
  179. package/src/result/match.ts +0 -124
  180. package/src/result/misc.ts +0 -129
  181. package/src/result/models.ts +0 -83
  182. package/src/result/work/flow.ts +0 -488
  183. package/src/result/work/pipe.ts +0 -496
  184. package/src/sized/map.ts +0 -110
  185. package/src/sized/set.ts +0 -107
  186. package/src/string/case.ts +0 -236
  187. package/src/string/fuzzy.ts +0 -444
  188. package/src/string/index.ts +0 -199
  189. package/src/string/match.ts +0 -80
  190. package/src/string/normalize.ts +0 -205
  191. package/src/string/template.ts +0 -161
  192. package/src/value/clone.ts +0 -319
  193. package/src/value/collection.ts +0 -63
  194. package/src/value/diff.ts +0 -232
  195. package/src/value/freeze.ts +0 -322
  196. package/src/value/handle.ts +0 -3
  197. package/src/value/index.ts +0 -5
  198. package/src/value/merge.ts +0 -278
  199. package/src/value/omit.ts +0 -20
  200. package/src/value/pick.ts +0 -20
  201. package/src/value/shake.ts +0 -45
  202. package/src/value/smush.ts +0 -89
  203. package/src/value/transform.ts +0 -153
  204. package/src/value/unsmush.ts +0 -84
@@ -1,236 +0,0 @@
1
- import {memoize, type Memoized} from '../function/memoize';
2
- import {join, words} from '../internal/string';
3
-
4
- // #region Types
5
-
6
- type Case = 'camel' | 'kebab' | 'pascal' | 'snake';
7
-
8
- type Options = {
9
- capitalizeAny: boolean;
10
- capitalizeFirst: boolean;
11
- type: Case;
12
- };
13
-
14
- // #endregion
15
-
16
- // #region Functions
17
-
18
- /**
19
- * Convert a string to camel case _(thisIsCamelCase)_
20
- *
21
- * @param value String to convert
22
- * @returns Camel-cased string
23
- */
24
- export function camelCase(value: string): string {
25
- return toCase(CASE_CAMEL, value, true, false);
26
- }
27
-
28
- /**
29
- * Capitalize the first letter of a string _(and lowercase the rest)_
30
- *
31
- * @param value String to capitalize
32
- * @returns Capitalized string
33
- */
34
- export function capitalize(value: string): string {
35
- if (typeof value !== 'string' || value.length === 0) {
36
- return '';
37
- }
38
-
39
- memoizedCapitalize ??= memoize(v =>
40
- v.length === 1
41
- ? v.toLocaleUpperCase()
42
- : `${v.charAt(0).toLocaleUpperCase()}${v.slice(1).toLocaleLowerCase()}`,
43
- );
44
-
45
- return memoizedCapitalize.run(value);
46
- }
47
-
48
- /**
49
- * Convert a string to kebab case _(this-is-kebab-case)_
50
- *
51
- * @param value String to convert
52
- * @returns Kebab-cased string
53
- */
54
- export function kebabCase(value: string): string {
55
- return toCase(CASE_KEBAB, value, false, false);
56
- }
57
-
58
- /**
59
- * Convert a string to lower case
60
- *
61
- * @param value String to convert
62
- * @returns Lower-cased string
63
- */
64
- export function lowerCase(value: string): string {
65
- if (typeof value !== 'string') {
66
- return '';
67
- }
68
-
69
- memoizedLowerCase ??= memoize(v => v.toLocaleLowerCase());
70
-
71
- return memoizedLowerCase.run(value);
72
- }
73
-
74
- /**
75
- * Convert a string to pascal case _(ThisIsPascalCase)_
76
- *
77
- * @param value String to convert
78
- * @returns Pascal-cased string
79
- */
80
- export function pascalCase(value: string): string {
81
- return toCase(CASE_PASCAL, value, true, true);
82
- }
83
-
84
- /**
85
- * Convert a string to snake case _(this_is_snake_case)_
86
- *
87
- * @param value String to convert
88
- * @returns Snake-cased string
89
- */
90
- export function snakeCase(value: string): string {
91
- return toCase(CASE_SNAKE, value, false, false);
92
- }
93
-
94
- /**
95
- * Convert a string to title case _(Capitalizing Every Word)_
96
- *
97
- * @param value String to convert
98
- * @returns Title-cased string
99
- */
100
- export function titleCase(value: string): string {
101
- if (typeof value !== 'string' || value.length === 0) {
102
- return '';
103
- }
104
-
105
- memoizedTitleCase ??= memoize(v =>
106
- v.length < 2 ? capitalize(v) : join(words(v).map(capitalize), ' '),
107
- );
108
-
109
- return memoizedTitleCase.run(value);
110
- }
111
-
112
- function toCase(
113
- type: Case,
114
- value: string,
115
- capitalizeAny: boolean,
116
- capitalizeFirst: boolean,
117
- ): string {
118
- caseMemoizers[type] ??= memoize(toCaseCallback.bind({type, capitalizeAny, capitalizeFirst}));
119
-
120
- return caseMemoizers[type].run(value);
121
- }
122
-
123
- function toCaseCallback(this: Options, value: string): string {
124
- if (typeof value !== 'string') {
125
- return '';
126
- }
127
-
128
- if (value.length < 1) {
129
- return value;
130
- }
131
-
132
- const {capitalizeAny, capitalizeFirst, type} = this;
133
-
134
- const parts = words(value);
135
- const partsLength = parts.length;
136
-
137
- const cased: string[] = [];
138
-
139
- for (let partIndex = 0; partIndex < partsLength; partIndex += 1) {
140
- const part = parts[partIndex];
141
-
142
- const acronymParts = part.replace(EXPRESSION_ACRONYM, (full, one, two, three) =>
143
- three === S ? full : `${one}-${two}${three}`,
144
- );
145
-
146
- const camelCaseParts = acronymParts.replace(EXPRESSION_CAMEL_CASE, REPLACEMENT_CAMEL_CASE);
147
-
148
- const items = camelCaseParts.split('-');
149
- const itemsLength = items.length;
150
-
151
- const partResult: string[] = [];
152
-
153
- let itemCount = 0;
154
-
155
- for (let itemIndex = 0; itemIndex < itemsLength; itemIndex += 1) {
156
- const item = items[itemIndex];
157
-
158
- if (item.length === 0) {
159
- continue;
160
- }
161
-
162
- if (!capitalizeAny || (itemCount === 0 && partIndex === 0 && !capitalizeFirst)) {
163
- partResult.push(item.toLocaleLowerCase());
164
- } else {
165
- partResult.push(capitalize(item));
166
- }
167
-
168
- itemCount += 1;
169
- }
170
-
171
- cased.push(join(partResult, delimiters[type]));
172
- }
173
-
174
- return join(cased, delimiters[type]);
175
- }
176
-
177
- /**
178
- * Convert a string to upper case
179
- *
180
- * @param value String to convert
181
- * @returns Upper-cased string
182
- */
183
- export function upperCase(value: string): string {
184
- if (typeof value !== 'string' || value.length === 0) {
185
- return '';
186
- }
187
-
188
- memoizedUpperCase ??= memoize(v => v.toLocaleUpperCase());
189
-
190
- return memoizedUpperCase.run(value);
191
- }
192
-
193
- // #endregion
194
-
195
- // #region Variables
196
-
197
- const CASE_CAMEL: Case = 'camel';
198
-
199
- const CASE_KEBAB: Case = 'kebab';
200
-
201
- const CASE_PASCAL: Case = 'pascal';
202
-
203
- const CASE_SNAKE: Case = 'snake';
204
-
205
- const DELIMTER_EMPTY = '';
206
-
207
- const DELIMITER_HYPHEN = '-';
208
-
209
- const DELIMITER_UNDERSCORE = '_';
210
-
211
- const EXPRESSION_CAMEL_CASE = /(\p{Ll})(\p{Lu})/gu;
212
-
213
- const EXPRESSION_ACRONYM = /(\p{Lu}*)(\p{Lu})(\p{Ll}+)/gu;
214
-
215
- const REPLACEMENT_CAMEL_CASE = '$1-$2';
216
-
217
- const S = 's';
218
-
219
- const caseMemoizers: Partial<Record<string, Memoized<typeof toCaseCallback>>> = {};
220
-
221
- const delimiters: Record<Case, string> = {
222
- [CASE_CAMEL]: DELIMTER_EMPTY,
223
- [CASE_KEBAB]: DELIMITER_HYPHEN,
224
- [CASE_PASCAL]: DELIMTER_EMPTY,
225
- [CASE_SNAKE]: DELIMITER_UNDERSCORE,
226
- };
227
-
228
- let memoizedCapitalize: Memoized<(value: string) => string>;
229
-
230
- let memoizedLowerCase: Memoized<(value: string) => string>;
231
-
232
- let memoizedTitleCase: Memoized<(value: string) => string>;
233
-
234
- let memoizedUpperCase: Memoized<(value: string) => string>;
235
-
236
- // #endregion
@@ -1,444 +0,0 @@
1
- import {isPlainObject} from '../internal/is';
2
- import {getString} from '../internal/string';
3
- import type {PlainObject, RequiredKeys} from '../models';
4
- import {lowerCase} from './case';
5
- import {includes} from './match';
6
-
7
- // #region Types
8
-
9
- /**
10
- * Fuzzy searcher for an array of items
11
- */
12
- export type Fuzzy<Item> = {
13
- /**
14
- * Get items currently being searched through
15
- *
16
- * @returns Original items
17
- */
18
- get items(): Item[];
19
-
20
- /**
21
- * Set new items to search through
22
- *
23
- * @param items New items to search through
24
- */
25
- set items(items: Item[]);
26
-
27
- /**
28
- * Get strings currently being searched through _(the stringified version of `items`)_
29
- *
30
- * @returns Stringified items
31
- */
32
- get strings(): string[];
33
-
34
- /**
35
- * Search for items matching the given value
36
- *
37
- * @param value Value to search for
38
- * @param options Search options
39
- * @returns Search results, with exact matches _(ordered)_ and similar matches _(ordered by relevance)_
40
- */
41
- search(value: string, options?: FuzzyOptions): FuzzyResult<Item>;
42
-
43
- /**
44
- * Search for items matching the given value
45
- *
46
- * @param value Value to search for
47
- * @param limit Maximum number of combined items to return in `exact` and `similar`
48
- * @returns Search results, with exact matches _(ordered)_ and similar matches _(ordered by relevance)_
49
- */
50
- search(value: string, limit: number): FuzzyResult<Item>;
51
- };
52
-
53
- export type FuzzyConfiguration<Item> = {
54
- /**
55
- * Handler to stringify items
56
- *
57
- * - May be a function that takes an item and returns a string, or if items are plain objects, a key of the item to use to grab a string value from
58
- * - Defaults to `getString`
59
- */
60
- handler?: (item: Item) => string;
61
- } & (Item extends PlainObject
62
- ? {
63
- /**
64
- * Key to use to stringify items
65
- *
66
- * _Prioritized over `handler`_
67
- */
68
- key?: keyof Item;
69
- }
70
- : {}) &
71
- FuzzyOptions;
72
-
73
- type FuzzyItem<Item> = {
74
- item: Item;
75
- haystack: string;
76
- };
77
-
78
- export type FuzzyOptions = {
79
- /**
80
- * Maximum number of combined items to return in `exact` and `similar` _(defaults to all matches)_
81
- */
82
- limit?: number;
83
- /**
84
- * Maximum score difference between the best and worst similar matches included in results
85
- *
86
- * - Higher values cast a wider net
87
- * - Defaults to `5`
88
- */
89
- tolerance?: number;
90
- };
91
-
92
- /**
93
- * Search results from a fuzzy search, with exact and similar matches
94
- */
95
- export type FuzzyResult<Item> = {
96
- /**
97
- * Ordered items that exactly match the search value
98
- */
99
- exact: Item[];
100
- /**
101
- * Ordered items that are similar to the search value, ranked by relevance
102
- */
103
- similar: Item[];
104
- };
105
-
106
- /**
107
- * Options for fuzzy searching
108
- */
109
- export type FuzzySearchOptions = FuzzyOptions;
110
-
111
- type FuzzyState<Item> = {
112
- handler: (item: Item) => string;
113
- items: Item[];
114
- limit?: number;
115
- strings: string[];
116
- tolerance: number;
117
- };
118
-
119
- // #endregion
120
-
121
- // #region Functions
122
-
123
- function getHandler<Item>(input: unknown): (item: Item) => string {
124
- if (input == null || input === getString) {
125
- return getString;
126
- }
127
-
128
- switch (typeof input) {
129
- case 'function':
130
- return input as (item: Item) => string;
131
-
132
- case 'string':
133
- return (item: Item) => (item as PlainObject)[input] as string;
134
-
135
- default: {
136
- if (isPlainObject(input)) {
137
- return getHandler(
138
- (input as FuzzyConfiguration<PlainObject>).key ??
139
- (input as FuzzyConfiguration<Item>).handler,
140
- );
141
- }
142
-
143
- throw new TypeError(MESSAGE_HANDLER);
144
- }
145
- }
146
- }
147
-
148
- function getItems<Item>(items: FuzzyItem<Item>[]): Item[] {
149
- return items
150
- .sort((first, second) => first.haystack.localeCompare(second.haystack))
151
- .map(({item}) => item);
152
- }
153
-
154
- function getFuzzyOptions<Item>(
155
- input: unknown,
156
- state?: FuzzyState<Item>,
157
- ): RequiredKeys<FuzzyOptions, 'tolerance'> {
158
- const options: FuzzyOptions = isPlainObject(input) ? input : {};
159
-
160
- const limit = typeof input === 'number' ? input : options.limit;
161
-
162
- if (typeof limit === 'number' && !Number.isNaN(limit) && limit >= 1) {
163
- options.limit = Math.floor(limit);
164
- } else {
165
- options.limit = state?.limit;
166
- }
167
-
168
- options.tolerance = getTolerance(options.tolerance, state);
169
-
170
- return options as RequiredKeys<FuzzyOptions, 'tolerance'>;
171
- }
172
-
173
- function getFuzzyState<Item>(items: Item[], input: unknown): FuzzyState<Item> {
174
- const handler = getHandler(input);
175
- const options = getFuzzyOptions(input);
176
-
177
- return {
178
- handler,
179
- items: items.slice(),
180
- limit: options.limit,
181
- strings: items.map(handler),
182
- tolerance: options.tolerance,
183
- };
184
- }
185
-
186
- function getTolerance<Item>(input: unknown, state?: FuzzyState<Item>): number {
187
- if (typeof input === 'number' && !Number.isNaN(input)) {
188
- return input < 0 ? 0 : Math.floor(input);
189
- }
190
-
191
- return state?.tolerance ?? PROXIMITY_THRESHOLD;
192
- }
193
-
194
- /**
195
- * Create a fuzzy searcher for an array of items
196
- *
197
- * @param items Items to search through
198
- * @param key Key to use to stringify items
199
- * @returns Fuzzy searcher
200
- */
201
- export function fuzzy<Item extends PlainObject, ItemKey extends keyof Item>(
202
- items: Item[],
203
- key?: ItemKey,
204
- ): Fuzzy<Item>;
205
-
206
- /**
207
- * Create a fuzzy searcher for an array of items
208
- *
209
- * @param items Items to search through
210
- * @param handler Handler to stringify items
211
- * @returns Fuzzy searcher
212
- */
213
- export function fuzzy<Item>(items: Item[], handler?: (item: Item) => string): Fuzzy<Item>;
214
-
215
- /**
216
- * Create a fuzzy searcher for an array of items
217
- *
218
- * @param items Items to search through
219
- * @param configuration Fuzzy configuration
220
- * @returns Fuzzy searcher
221
- */
222
- export function fuzzy<Item>(items: Item[], configuration?: FuzzyConfiguration<Item>): Fuzzy<Item>;
223
-
224
- export function fuzzy(items: unknown[], configuration?: unknown): Fuzzy<unknown> {
225
- if (!Array.isArray(items)) {
226
- throw new TypeError(MESSAGE_ARRAY);
227
- }
228
-
229
- const state = getFuzzyState(items, configuration);
230
-
231
- const instance = {
232
- search: (value: string, options?: number | FuzzyOptions) =>
233
- search(
234
- state.items,
235
- state.strings,
236
- value,
237
- options == null ? state : getFuzzyOptions(options, state),
238
- ),
239
- };
240
-
241
- Object.defineProperties(instance, {
242
- items: {
243
- get: () => state.items.slice(),
244
- set: (items: unknown[]) => {
245
- if (!Array.isArray(items)) {
246
- throw new TypeError(MESSAGE_ARRAY);
247
- }
248
-
249
- state.items = items.slice();
250
- state.strings = items.map(state.handler);
251
- },
252
- },
253
- strings: {
254
- get: () => state.strings.slice(),
255
- },
256
- });
257
-
258
- return Object.freeze(instance) as Fuzzy<unknown>;
259
- }
260
-
261
- fuzzy.match = fuzzyMatch;
262
-
263
- /**
264
- * Does the needle match the haystack in a fuzzy way?
265
- *
266
- * @param haystack Haystack to search through
267
- * @param needle Needle to search for
268
- * @returns `true` if the needle matches the haystack in a fuzzy way, otherwise `false`
269
- */
270
- export function fuzzyMatch(haystack: string, needle: string): boolean {
271
- if (typeof haystack !== 'string' || typeof needle !== 'string') {
272
- return false;
273
- }
274
-
275
- const trimmed = needle.trim();
276
-
277
- if (includes(haystack, trimmed, true)) {
278
- return true;
279
- }
280
-
281
- return getScore(haystack, trimmed) > -1;
282
- }
283
-
284
- function isSubsequence(haystack: string, needle: string): boolean {
285
- const lowerCaseHaystack = lowerCase(haystack);
286
- const lowerCaseNeedle = lowerCase(needle);
287
-
288
- const haystackLength = lowerCaseHaystack.length;
289
- const needleLength = lowerCaseNeedle.length;
290
-
291
- let needleIndex = 0;
292
-
293
- for (let haystackIndex = 0; haystackIndex < haystackLength; haystackIndex += 1) {
294
- // Advance needle pointer only on a matching character
295
- if (lowerCaseHaystack[haystackIndex] === lowerCaseNeedle[needleIndex]) {
296
- needleIndex += 1;
297
- }
298
-
299
- // All needle characters matched in order
300
- if (needleIndex === needleLength) {
301
- return true;
302
- }
303
- }
304
-
305
- return false;
306
- }
307
-
308
- function getScore(haystack: string, needle: string): number {
309
- if (!isSubsequence(haystack, needle)) {
310
- return -1;
311
- }
312
-
313
- const lowerCaseHaystack = lowerCase(haystack);
314
- const lowerCaseNeedle = lowerCase(needle);
315
-
316
- const needleLength = lowerCaseNeedle.length;
317
-
318
- let needleIndex = 0;
319
- let previousMatchIndex = -1;
320
- let score = 0;
321
-
322
- for (let haystackIndex = 0; haystackIndex < lowerCaseHaystack.length; haystackIndex += 1) {
323
- if (lowerCaseHaystack[haystackIndex] === lowerCaseNeedle[needleIndex]) {
324
- // +1 for each matched character
325
- score += 1;
326
-
327
- // Bonus for matching at the start of the string
328
- if (haystackIndex === 0) {
329
- score += 1;
330
- }
331
-
332
- // Proximity bonus: decays as gap between consecutive matches widens
333
- if (previousMatchIndex !== -1) {
334
- const gap = haystackIndex - previousMatchIndex - 1;
335
-
336
- score += Math.max(0, PROXIMITY_THRESHOLD - gap);
337
- }
338
-
339
- previousMatchIndex = haystackIndex;
340
-
341
- needleIndex += 1;
342
- }
343
-
344
- // All needle characters matched; no need to scan further
345
- if (needleIndex === needleLength) {
346
- break;
347
- }
348
- }
349
-
350
- // Penalty for longer strings to favour tighter matches
351
- score -= Math.floor(lowerCaseHaystack.length / LENGTH_DIVISOR);
352
-
353
- return Math.max(0, score);
354
- }
355
-
356
- function search<Item>(
357
- items: Item[],
358
- strings: string[],
359
- input: string,
360
- options: RequiredKeys<FuzzyOptions, 'tolerance'>,
361
- ) {
362
- const result: FuzzyResult<Item> = {
363
- exact: [],
364
- similar: [],
365
- };
366
-
367
- const value = typeof input === 'string' ? input.trim() : '';
368
-
369
- if (value.length === 0) {
370
- result.exact = items.slice(0, options.limit);
371
-
372
- return result;
373
- }
374
-
375
- let {length} = items;
376
-
377
- const exact: Array<FuzzyItem<Item>> = [];
378
- const similar: Array<Item> = [];
379
-
380
- const scored: Record<number, Array<FuzzyItem<Item>>> = {};
381
-
382
- for (let index = 0; index < length; index += 1) {
383
- const item = items[index];
384
- const haystack = strings[index];
385
-
386
- if (includes(haystack, value, true)) {
387
- exact.push({item, haystack});
388
-
389
- continue;
390
- }
391
-
392
- const score = getScore(haystack, value);
393
-
394
- if (score > -1) {
395
- scored[score] ??= [];
396
-
397
- scored[score].push({item, haystack});
398
- }
399
- }
400
-
401
- const keys = Object.keys(scored)
402
- .map(Number)
403
- .sort((first, second) => second - first);
404
-
405
- length = keys.length;
406
-
407
- if (length > 0 && options.tolerance > 0) {
408
- const maxScore = keys[0];
409
-
410
- for (let index = 0; index < length; index += 1) {
411
- const key = keys[index];
412
-
413
- if (maxScore - key > options.tolerance) {
414
- break;
415
- }
416
-
417
- similar.push(...getItems(scored[key]));
418
- }
419
- }
420
-
421
- result.exact = getItems(options.limit == null ? exact : exact.slice(0, options.limit));
422
-
423
- if (options.limit == null) {
424
- result.similar = similar;
425
- } else {
426
- result.similar = similar.slice(0, options.limit - result.exact.length);
427
- }
428
-
429
- return result;
430
- }
431
-
432
- // #endregion
433
-
434
- // #region Variables
435
-
436
- const LENGTH_DIVISOR = 3;
437
-
438
- const MESSAGE_ARRAY = 'Fuzzy requires an array of items';
439
-
440
- const MESSAGE_HANDLER = 'Fuzzy requires a key or function to stringify items';
441
-
442
- const PROXIMITY_THRESHOLD = 5;
443
-
444
- // #endregion