@oscarpalmer/atoms 0.219.0 → 0.221.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.
package/dist/index.mjs CHANGED
@@ -1132,7 +1132,7 @@ const VALUE_MISC_NESTED_MESSAGE_UNSAFE = "Access to this property is not allowed
1132
1132
  //#endregion
1133
1133
  //#region src/models/string/string.misc.model.ts
1134
1134
  const STRING_EXPRESSION_IGNORED = /(^|\.)(__proto__|constructor|prototype)(\.|$)/i;
1135
- const STRING_EXPRESSION_WORDS = /([^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+)/g;
1135
+ const STRING_EXPRESSION_WORDS = /[\dA-Za-z\P{ASCII}]+/gu;
1136
1136
  const STRING_EXPRESSION_WHITESPACE_PREFIX = /^(\s+)/;
1137
1137
  const STRING_MATCH_ENDS_WITH = "endsWith";
1138
1138
  const STRING_MATCH_INCLUDES = "includes";
@@ -1981,9 +1981,7 @@ function assertInstanceOf(constructor, message, error) {
1981
1981
  * @returns _Asserter_
1982
1982
  */
1983
1983
  function assertIs(condition, message, error) {
1984
- return (value) => {
1985
- assert(() => condition(value), message, error);
1986
- };
1984
+ return assertCondition(condition, message, error);
1987
1985
  }
1988
1986
  /**
1989
1987
  * Creates an _Asserter_ that asserts a property of a value exists and satisfies a condition, throwing an error if it does not
@@ -2568,12 +2566,17 @@ pipe.async = asyncPipe;
2568
2566
  //#endregion
2569
2567
  //#region src/models/string/string.case.model.ts
2570
2568
  const STRING_CASE_CAMEL = "camel";
2569
+ const STRING_CASE_CAPITALIZE = "capitalize";
2571
2570
  const STRING_CASE_KEBAB = "kebab";
2571
+ const STRING_CASE_LOWER = "lower";
2572
2572
  const STRING_CASE_PASCAL = "pascal";
2573
2573
  const STRING_CASE_SNAKE = "snake";
2574
+ const STRING_CASE_TITLE = "title";
2575
+ const STRING_CASE_UPPER = "upper";
2574
2576
  const STRING_EXPRESSION_CAMEL_CASE = /(\p{Ll})(\p{Lu})/gu;
2575
- const STRING_EXPRESSION_ACRONYM = /(\p{Lu}*)(\p{Lu})(\p{Ll}+)/gu;
2577
+ const STRING_EXPRESSION_ACRONYM = /(?<=\p{Lu})(?=\p{Lu}\p{Ll})(?!\p{Lu}s(?!\p{Ll}))/gu;
2576
2578
  const STRING_REPLACEMENT_CAMEL_CASE = "$1-$2";
2579
+ const ignoreCachingCases = /* @__PURE__ */ new Set(["lower", "upper"]);
2577
2580
  //#endregion
2578
2581
  //#region src/internal/string/case.ts
2579
2582
  /**
@@ -2608,9 +2611,11 @@ function camelCase(value) {
2608
2611
  * ```
2609
2612
  */
2610
2613
  function capitalize(value) {
2611
- if (typeof value !== "string" || value.length === 0) return "";
2612
- memoizedCapitalize ??= memoize((v) => v.length === 1 ? v.toLocaleUpperCase() : `${v.charAt(0).toLocaleUpperCase()}${v.slice(1).toLocaleLowerCase()}`);
2613
- return memoizedCapitalize.run(value);
2614
+ return toCase(STRING_CASE_CAPITALIZE, value, true, true, capitalizeHandler);
2615
+ }
2616
+ function capitalizeHandler(value) {
2617
+ if (value.length === 1) return value.toLocaleUpperCase();
2618
+ return `${value.charAt(0).toLocaleUpperCase()}${value.slice(1).toLocaleLowerCase()}`;
2614
2619
  }
2615
2620
  /**
2616
2621
  * Convert a string to kebab case _(this-is-kebab-case)_
@@ -2643,9 +2648,10 @@ function kebabCase(value) {
2643
2648
  * ```
2644
2649
  */
2645
2650
  function lowerCase(value) {
2646
- if (typeof value !== "string") return "";
2647
- memoizedLowerCase ??= memoize((v) => v.toLocaleLowerCase());
2648
- return memoizedLowerCase.run(value);
2651
+ return toCase(STRING_CASE_LOWER, value, false, false, lowerCaseHandler);
2652
+ }
2653
+ function lowerCaseHandler(value) {
2654
+ return value.toLocaleLowerCase();
2649
2655
  }
2650
2656
  /**
2651
2657
  * Convert a string to pascal case _(ThisIsPascalCase)_
@@ -2696,33 +2702,39 @@ function snakeCase(value) {
2696
2702
  * ```
2697
2703
  */
2698
2704
  function titleCase(value) {
2699
- if (typeof value !== "string" || value.length === 0) return "";
2700
- memoizedTitleCase ??= memoize((v) => v.length < 2 ? capitalize(v) : join(words(v).map(capitalize), " "));
2701
- return memoizedTitleCase.run(value);
2705
+ return toCase(STRING_CASE_TITLE, value, true, true, titleCaseHandler);
2702
2706
  }
2703
- function toCase(type, value, capitalizeAny, capitalizeFirst) {
2704
- caseMemoizers[type] ??= memoize(toCaseCallback.bind({
2705
- type,
2706
- capitalizeAny,
2707
- capitalizeFirst
2708
- }));
2709
- return caseMemoizers[type].run(value);
2707
+ function titleCaseHandler(value) {
2708
+ if (value.length < 2) return capitalize(value);
2709
+ return join(words(value).map(capitalize), " ");
2710
2710
  }
2711
- function toCaseCallback(value) {
2711
+ function toCase(type, value, capitalizeAny, capitalizeFirst, handler) {
2712
2712
  if (typeof value !== "string") return "";
2713
- if (value.length < 1) return value;
2713
+ if (value.trim().length === 0) return value;
2714
+ if (handler == null) {
2715
+ caseMemoizers[type] ??= memoize(toCaseCallback.bind({
2716
+ type,
2717
+ capitalizeAny,
2718
+ capitalizeFirst
2719
+ }));
2720
+ return caseMemoizers[type].run(value);
2721
+ }
2722
+ if (ignoreCachingCases.has(type)) return handler(value);
2723
+ simpleMemoizers[type] ??= memoize(handler);
2724
+ return simpleMemoizers[type].run(value);
2725
+ }
2726
+ function toCaseCallback(value) {
2714
2727
  const { capitalizeAny, capitalizeFirst, type } = this;
2715
2728
  const parts = words(value);
2716
2729
  const partsLength = parts.length;
2717
2730
  const cased = [];
2718
2731
  for (let partIndex = 0; partIndex < partsLength; partIndex += 1) {
2719
- const items = parts[partIndex].replace(STRING_EXPRESSION_ACRONYM, (full, one, two, three) => three === "s" ? full : `${one}-${two}${three}`).replace(STRING_EXPRESSION_CAMEL_CASE, STRING_REPLACEMENT_CAMEL_CASE).split("-");
2732
+ const items = parts[partIndex].replace(STRING_EXPRESSION_ACRONYM, "-").replace(STRING_EXPRESSION_CAMEL_CASE, STRING_REPLACEMENT_CAMEL_CASE).split("-");
2720
2733
  const itemsLength = items.length;
2721
2734
  const partResult = [];
2722
2735
  let itemCount = 0;
2723
2736
  for (let itemIndex = 0; itemIndex < itemsLength; itemIndex += 1) {
2724
2737
  const item = items[itemIndex];
2725
- if (item.length === 0) continue;
2726
2738
  if (!capitalizeAny || itemCount === 0 && partIndex === 0 && !capitalizeFirst) partResult.push(item.toLocaleLowerCase());
2727
2739
  else partResult.push(capitalize(item));
2728
2740
  itemCount += 1;
@@ -2746,9 +2758,10 @@ function toCaseCallback(value) {
2746
2758
  * ```
2747
2759
  */
2748
2760
  function upperCase(value) {
2749
- if (typeof value !== "string" || value.length === 0) return "";
2750
- memoizedUpperCase ??= memoize((v) => v.toLocaleUpperCase());
2751
- return memoizedUpperCase.run(value);
2761
+ return toCase(STRING_CASE_UPPER, value, false, false, upperCaseHandler);
2762
+ }
2763
+ function upperCaseHandler(value) {
2764
+ return value.toLocaleUpperCase();
2752
2765
  }
2753
2766
  const caseMemoizers = {};
2754
2767
  const delimiters = {
@@ -2757,10 +2770,7 @@ const delimiters = {
2757
2770
  [STRING_CASE_PASCAL]: "",
2758
2771
  [STRING_CASE_SNAKE]: "_"
2759
2772
  };
2760
- let memoizedCapitalize;
2761
- let memoizedLowerCase;
2762
- let memoizedTitleCase;
2763
- let memoizedUpperCase;
2773
+ const simpleMemoizers = {};
2764
2774
  //#endregion
2765
2775
  //#region src/models/value/value.equal.model.ts
2766
2776
  const EQUAL_ERROR_PROPERTIES = ["name", "message"];
@@ -3728,79 +3738,12 @@ function attemptPipe(initial, first, ...seconds) {
3728
3738
  attemptPipe.async = attemptAsyncPipe;
3729
3739
  //#endregion
3730
3740
  //#region src/models/string/string.fuzzy.model.ts
3741
+ const FUZZY_BOUNDARY_SEPARATOR = /[\s./:\\_-]/;
3731
3742
  const FUZZY_MESSAGE_ARRAY = "Fuzzy requires an array of items";
3732
3743
  const FUZZY_MESSAGE_HANDLER = "Fuzzy requires a key or function to stringify items";
3733
3744
  const FUZZY_PROPERTY = "$fuzzy";
3734
3745
  const FUZZY_SYMBOL = Symbol(FUZZY_PROPERTY);
3735
3746
  //#endregion
3736
- //#region src/string/match.ts
3737
- /**
3738
- * Check if a string ends with a specified substring
3739
- *
3740
- * @param haystack String to look in
3741
- * @param needle String to look for
3742
- * @param ignoreCase Ignore case when matching? _(defaults to `false`)_
3743
- * @returns `true` if the string ends with the given substring, otherwise `false`
3744
- *
3745
- * @example
3746
- * ```typescript
3747
- * endsWith('this is a test', 'test'); // => true
3748
- * endsWith('this is a test', 'Test'); // => false
3749
- * endsWith('this is a test', 'Test', true); // => true
3750
- * endsWith('this is a test', 'not'); // => false
3751
- * ```
3752
- */
3753
- function endsWith(haystack, needle, ignoreCase) {
3754
- return match(STRING_MATCH_ENDS_WITH, haystack, needle, ignoreCase === true);
3755
- }
3756
- /**
3757
- * Check if a string includes a specified substring
3758
- *
3759
- * @param haystack String to look in
3760
- * @param needle String to look for
3761
- * @param ignoreCase Ignore case when matching? _(defaults to `false`)_
3762
- * @returns `true` if the string includes the given substring, otherwise `false`
3763
- *
3764
- * @example
3765
- * ```typescript
3766
- * includes('this is a test', 'is'); // => true
3767
- * includes('this is a test', 'Is'); // => false
3768
- * includes('this is a test', 'Is', true); // => true
3769
- * includes('this is a test', 'not'); // => false
3770
- * ```
3771
- */
3772
- function includes(haystack, needle, ignoreCase) {
3773
- return match(STRING_MATCH_INCLUDES, haystack, needle, ignoreCase === true);
3774
- }
3775
- function match(type, haystack, needle, ignoreCase) {
3776
- if (typeof haystack !== "string" || typeof needle !== "string") return false;
3777
- matchMemoizers[type] ??= memoize(matchCallback.bind(type));
3778
- return matchMemoizers[type].run(haystack, needle, ignoreCase);
3779
- }
3780
- function matchCallback(haystack, needle, ignoreCase) {
3781
- return (ignoreCase ? haystack.toLocaleLowerCase() : haystack)[this](ignoreCase ? needle.toLocaleLowerCase() : needle);
3782
- }
3783
- /**
3784
- * Check if a string starts with a specified substring
3785
- *
3786
- * @param haystack String to look in
3787
- * @param needle String to look for
3788
- * @param ignoreCase Ignore case when matching? _(defaults to `false`)_
3789
- * @returns `true` if the string starts with the given substring, otherwise `false`
3790
- *
3791
- * @example
3792
- * ```typescript
3793
- * startsWith('this is a test', 'this'); // => true
3794
- * startsWith('this is a test', 'This'); // => false
3795
- * startsWith('this is a test', 'This', true); // => true
3796
- * startsWith('this is a test', 'not'); // => false
3797
- * ```
3798
- */
3799
- function startsWith(haystack, needle, ignoreCase) {
3800
- return match(STRING_MATCH_STARTS_WITH, haystack, needle, ignoreCase === true);
3801
- }
3802
- const matchMemoizers = {};
3803
- //#endregion
3804
3747
  //#region src/string/fuzzy.ts
3805
3748
  function Fuzzy(state) {
3806
3749
  this[FUZZY_SYMBOL] = state;
@@ -3810,39 +3753,76 @@ Fuzzy.prototype.search = search;
3810
3753
  Object.defineProperties(Fuzzy.prototype, {
3811
3754
  items: {
3812
3755
  enumerable: true,
3813
- get: getFuzzyItems,
3814
- set: setFuzzyItems
3756
+ get: getItems,
3757
+ set: setItems
3815
3758
  },
3816
3759
  strings: {
3817
3760
  enumerable: true,
3818
- get: getFuzzyStrings
3761
+ get: getStrings
3819
3762
  }
3820
3763
  });
3821
- function createFuzzyItems(items) {
3822
- return items.toSorted((first, second) => first.haystack.localeCompare(second.haystack)).map(({ item }) => item);
3764
+ function compareItem(sorter) {
3765
+ return (first, second) => sorter(first.string.original, second.string.original);
3823
3766
  }
3824
- function createFuzzyOptions(input, state) {
3825
- const options = isPlainObject(input) ? input : {};
3826
- const limit = typeof input === "number" ? input : options.limit;
3767
+ function compareRanked(sorter) {
3768
+ return (first, second) => first.rank - second.rank || first.position - second.position || sorter(first.string.original, second.string.original);
3769
+ }
3770
+ function createItems(state, items) {
3771
+ state.items.original = items.map((item) => ({
3772
+ data: item,
3773
+ string: createString(state.handler(item))
3774
+ }));
3775
+ state.items.sorted = void 0;
3776
+ }
3777
+ function createOptions(input, state) {
3778
+ const options = {};
3779
+ const original = isPlainObject(input) ? input : {};
3780
+ const limit = typeof input === "number" ? input : original.limit;
3827
3781
  if (typeof limit === "number" && !Number.isNaN(limit) && limit >= 1) options.limit = floor(limit);
3828
- else options.limit = state?.limit;
3829
- options.tolerance = getTolerance(options.tolerance, state);
3782
+ else options.limit = state?.limit ?? Number.POSITIVE_INFINITY;
3783
+ options.tolerance = getTolerance(original.tolerance, state);
3830
3784
  return options;
3831
3785
  }
3832
- function createFuzzyState(items, input) {
3833
- const handler = getHandler(input);
3834
- const options = createFuzzyOptions(input);
3786
+ function createRanked(item, needle, position) {
3787
+ let boundary = position;
3788
+ while (boundary > -1 && !isBoundary(item.string, boundary)) boundary = item.string.lowercase.indexOf(needle, boundary + 1);
3789
+ if (boundary === -1) return {
3790
+ ...item,
3791
+ position,
3792
+ rank: 2
3793
+ };
3835
3794
  return {
3795
+ ...item,
3796
+ position: boundary,
3797
+ rank: boundary === 0 ? 0 : 1
3798
+ };
3799
+ }
3800
+ function createState(items, input) {
3801
+ const handler = getHandler(input);
3802
+ const options = createOptions(input);
3803
+ const sorter = getComparator(input);
3804
+ const state = {
3805
+ compare: {
3806
+ item: compareItem(sorter),
3807
+ ranked: compareRanked(sorter)
3808
+ },
3836
3809
  handler,
3837
- items: items.slice(),
3810
+ items: { original: [] },
3838
3811
  limit: options.limit,
3839
- strings: items.map(handler),
3840
3812
  tolerance: options.tolerance
3841
3813
  };
3814
+ createItems(state, items);
3815
+ return state;
3816
+ }
3817
+ function createString(original) {
3818
+ return {
3819
+ original,
3820
+ lowercase: lowerCase(original)
3821
+ };
3842
3822
  }
3843
3823
  function fuzzy(items, configuration) {
3844
3824
  if (!Array.isArray(items)) throw new TypeError(FUZZY_MESSAGE_ARRAY);
3845
- return new Fuzzy(createFuzzyState(items, configuration));
3825
+ return new Fuzzy(createState(items, configuration));
3846
3826
  }
3847
3827
  /**
3848
3828
  * Does the needle match the haystack in a fuzzy way?
@@ -3856,53 +3836,117 @@ function fuzzy(items, configuration) {
3856
3836
  function fuzzyMatch(haystack, needle) {
3857
3837
  if (typeof haystack !== "string" || typeof needle !== "string") return false;
3858
3838
  const trimmed = needle.trim();
3859
- if (includes(haystack, trimmed, true)) return true;
3860
- return getScore(haystack, trimmed) > -1;
3839
+ const lowercaseHaystack = lowerCase(haystack);
3840
+ const lowercaseNeedle = lowerCase(trimmed);
3841
+ if (lowercaseHaystack.includes(lowercaseNeedle)) return true;
3842
+ return getScore({
3843
+ lowercase: lowercaseHaystack,
3844
+ original: haystack
3845
+ }, lowercaseNeedle) > -1;
3846
+ }
3847
+ function getComparator(input) {
3848
+ if (isPlainObject(input) && typeof input.compare === "function") return input.compare;
3849
+ defaultSorter ??= Intl.Collator().compare;
3850
+ return defaultSorter;
3861
3851
  }
3862
3852
  function getHandler(input) {
3863
3853
  if (input == null || input === getString) return getString;
3864
3854
  switch (typeof input) {
3865
- case "function": return input;
3866
- case "string": return (item) => item[input];
3855
+ case "function": return (item) => getString(input(item));
3856
+ case "number":
3857
+ case "string":
3858
+ case "symbol": return (item) => getString(item?.[input]);
3867
3859
  default:
3868
3860
  if (isPlainObject(input)) return getHandler(input.key ?? input.handler);
3869
3861
  throw new TypeError(FUZZY_MESSAGE_HANDLER);
3870
3862
  }
3871
3863
  }
3872
- function getFuzzyItems() {
3873
- return this[FUZZY_SYMBOL].items.slice();
3874
- }
3875
- function getFuzzyStrings() {
3876
- return this[FUZZY_SYMBOL].strings.slice();
3864
+ function getItems() {
3865
+ return this[FUZZY_SYMBOL].items.original.map((item) => item.data);
3877
3866
  }
3878
- function getScore(haystack, needle) {
3879
- if (!isSubsequence(haystack, needle)) return -1;
3880
- const lowerCaseHaystack = lowerCase(haystack);
3881
- const lowerCaseNeedle = lowerCase(needle);
3882
- const needleLength = lowerCaseNeedle.length;
3867
+ function getMatchEnd(haystack, needle, cursor) {
3868
+ const haystackLength = haystack.length;
3869
+ const needleLength = needle.length;
3883
3870
  let needleIndex = 0;
3884
- let previousMatchIndex = -1;
3885
- let score = 0;
3886
- for (let haystackIndex = 0; haystackIndex < lowerCaseHaystack.length; haystackIndex += 1) {
3887
- if (lowerCaseHaystack[haystackIndex] === lowerCaseNeedle[needleIndex]) {
3888
- score += 1;
3889
- if (haystackIndex === 0) score += 1;
3890
- if (previousMatchIndex !== -1) {
3891
- const gap = haystackIndex - previousMatchIndex - 1;
3892
- score += max([0, 5 - gap]);
3871
+ for (let index = cursor; index < haystackLength; index += 1) if (haystack[index] === needle[needleIndex]) {
3872
+ needleIndex += 1;
3873
+ if (needleIndex === needleLength) return index;
3874
+ }
3875
+ return -1;
3876
+ }
3877
+ function getResult(state, exact, included, scored, options) {
3878
+ const result = {
3879
+ exact: [],
3880
+ included: [],
3881
+ similar: []
3882
+ };
3883
+ exact.sort(state.compare.item);
3884
+ result.exact = exact.slice(0, options.limit).map((item) => item.data);
3885
+ included.sort(state.compare.ranked);
3886
+ result.included = included.slice(0, options.limit - result.exact.length).map((item) => item.data);
3887
+ const remaining = options.limit - result.exact.length - result.included.length;
3888
+ if (remaining === 0) return result;
3889
+ const keys = Object.keys(scored).map(Number).sort((first, second) => second - first);
3890
+ const keysLength = keys.length;
3891
+ if (keysLength === 0) return result;
3892
+ const maxScore = keys[0];
3893
+ outer: for (let index = 0; index < keysLength; index += 1) {
3894
+ const key = keys[index];
3895
+ if (maxScore - key > options.tolerance) break;
3896
+ const keyed = scored[key];
3897
+ const keyedLength = keyed.length;
3898
+ keyed.sort(state.compare.item);
3899
+ for (let keyedIndex = 0; keyedIndex < keyedLength; keyedIndex += 1) {
3900
+ result.similar.push(keyed[keyedIndex].data);
3901
+ if (result.similar.length >= remaining) break outer;
3902
+ }
3903
+ }
3904
+ return result;
3905
+ }
3906
+ function getScore(item, needle) {
3907
+ const haystackLength = item.lowercase.length;
3908
+ const needleLength = needle.length;
3909
+ if (needleLength > haystackLength) return -1;
3910
+ let best = -1;
3911
+ let cursor = 0;
3912
+ while (cursor < haystackLength) {
3913
+ const end = getMatchEnd(item.lowercase, needle, cursor);
3914
+ if (end === -1) break;
3915
+ let index = end;
3916
+ let needleIndex = needleLength - 1;
3917
+ let next = -1;
3918
+ let score = 0;
3919
+ let start = end;
3920
+ while (needleIndex >= 0) {
3921
+ if (item.lowercase[index] === needle[needleIndex]) {
3922
+ score += 1;
3923
+ if (next !== -1) score += max([0, 5 - (next - index - 1)]);
3924
+ if (isBoundary(item, index)) score += 2;
3925
+ next = index;
3926
+ start = index;
3927
+ needleIndex -= 1;
3893
3928
  }
3894
- previousMatchIndex = haystackIndex;
3895
- needleIndex += 1;
3929
+ index -= 1;
3896
3930
  }
3897
- if (needleIndex === needleLength) break;
3931
+ if (start === 0) score += 1;
3932
+ if (score > best) best = score;
3933
+ cursor = start + 1;
3898
3934
  }
3899
- score -= floor(lowerCaseHaystack.length / 3);
3900
- return max([0, score]);
3935
+ if (best === -1) return -1;
3936
+ return max([0, best - floor(haystackLength / 3)]);
3937
+ }
3938
+ function getStrings() {
3939
+ return this[FUZZY_SYMBOL].items.original.map((item) => item.string.original);
3901
3940
  }
3902
3941
  function getTolerance(input, state) {
3903
3942
  if (typeof input === "number" && !Number.isNaN(input)) return input < 0 ? 0 : floor(input);
3904
3943
  return state?.tolerance ?? 5;
3905
3944
  }
3945
+ function isBoundary(string, index) {
3946
+ if (index === 0 || FUZZY_BOUNDARY_SEPARATOR.test(string.lowercase[index - 1])) return true;
3947
+ if (string.original.length !== string.lowercase.length) return false;
3948
+ return string.original[index] !== string.lowercase[index] && string.original[index - 1] === string.lowercase[index - 1];
3949
+ }
3906
3950
  /**
3907
3951
  * Is the value a fuzzy searcher?
3908
3952
  *
@@ -3914,74 +3958,48 @@ function getTolerance(input, state) {
3914
3958
  function isFuzzy(value) {
3915
3959
  return typeof value === "object" && value !== null && "$fuzzy" in value && value["$fuzzy"] === true;
3916
3960
  }
3917
- function isSubsequence(haystack, needle) {
3918
- const lowerCaseHaystack = lowerCase(haystack);
3919
- const lowerCaseNeedle = lowerCase(needle);
3920
- const haystackLength = lowerCaseHaystack.length;
3921
- const needleLength = lowerCaseNeedle.length;
3922
- let needleIndex = 0;
3923
- for (let haystackIndex = 0; haystackIndex < haystackLength; haystackIndex += 1) {
3924
- if (lowerCaseHaystack[haystackIndex] === lowerCaseNeedle[needleIndex]) needleIndex += 1;
3925
- if (needleIndex === needleLength) return true;
3926
- }
3927
- return false;
3928
- }
3929
3961
  function search(input, option) {
3930
3962
  const state = this[FUZZY_SYMBOL];
3931
- const options = option == null ? state : createFuzzyOptions(option, state);
3932
- const result = {
3933
- exact: [],
3934
- similar: []
3935
- };
3963
+ const options = option == null ? state : createOptions(option, state);
3936
3964
  const value = typeof input === "string" ? input.trim() : "";
3965
+ const lowercaseValue = lowerCase(value);
3937
3966
  if (value.length === 0) {
3938
- result.exact = state.items.slice(0, options.limit);
3939
- return result;
3967
+ state.items.sorted ??= state.items.original.toSorted(state.compare.item);
3968
+ return {
3969
+ exact: state.items.sorted.slice(0, options.limit).map((item) => item.data),
3970
+ included: [],
3971
+ similar: []
3972
+ };
3940
3973
  }
3941
- let { length } = state.items;
3942
3974
  const exact = [];
3943
- const similar = [];
3975
+ const included = [];
3944
3976
  const scored = {};
3977
+ const { length } = state.items.original;
3945
3978
  for (let index = 0; index < length; index += 1) {
3946
- const item = state.items[index];
3947
- const haystack = state.strings[index];
3948
- if (includes(haystack, value, true)) {
3949
- exact.push({
3950
- item,
3951
- haystack
3952
- });
3979
+ const item = state.items.original[index];
3980
+ const { string } = item;
3981
+ if (string.lowercase === lowercaseValue) {
3982
+ exact.push(item);
3953
3983
  continue;
3954
3984
  }
3955
- const score = getScore(haystack, value);
3985
+ const position = string.lowercase.indexOf(lowercaseValue);
3986
+ if (position > -1) {
3987
+ included.push(createRanked(item, lowercaseValue, position));
3988
+ continue;
3989
+ }
3990
+ const score = getScore(string, lowercaseValue);
3956
3991
  if (score > -1) {
3957
3992
  scored[score] ??= [];
3958
- scored[score].push({
3959
- item,
3960
- haystack
3961
- });
3993
+ scored[score].push(item);
3962
3994
  }
3963
3995
  }
3964
- const keys = Object.keys(scored).map(Number).sort((first, second) => second - first);
3965
- length = keys.length;
3966
- if (length > 0 && options.tolerance > 0) {
3967
- const maxScore = keys[0];
3968
- for (let index = 0; index < length; index += 1) {
3969
- const key = keys[index];
3970
- if (maxScore - key > options.tolerance) break;
3971
- similar.push(...createFuzzyItems(scored[key]));
3972
- }
3973
- }
3974
- result.exact = createFuzzyItems(options.limit == null ? exact : exact.slice(0, options.limit));
3975
- if (options.limit == null) result.similar = similar;
3976
- else result.similar = similar.slice(0, options.limit - result.exact.length);
3977
- return result;
3996
+ return getResult(state, exact, included, scored, options);
3978
3997
  }
3979
- function setFuzzyItems(value) {
3998
+ function setItems(value) {
3980
3999
  if (!Array.isArray(value)) throw new TypeError(FUZZY_MESSAGE_ARRAY);
3981
- const state = this[FUZZY_SYMBOL];
3982
- state.items = value.slice();
3983
- state.strings = value.map((item) => state.handler(item));
4000
+ createItems(this[FUZZY_SYMBOL], value);
3984
4001
  }
4002
+ let defaultSorter;
3985
4003
  fuzzy.is = isFuzzy;
3986
4004
  fuzzy.match = fuzzyMatch;
3987
4005
  //#endregion
@@ -4089,6 +4107,74 @@ function truncate(value, length, suffix) {
4089
4107
  return `${value.slice(0, truncatedLength)}${actualSuffix}`;
4090
4108
  }
4091
4109
  //#endregion
4110
+ //#region src/string/match.ts
4111
+ /**
4112
+ * Check if a string ends with a specified substring
4113
+ *
4114
+ * @param haystack String to look in
4115
+ * @param needle String to look for
4116
+ * @param ignoreCase Ignore case when matching? _(defaults to `false`)_
4117
+ * @returns `true` if the string ends with the given substring, otherwise `false`
4118
+ *
4119
+ * @example
4120
+ * ```typescript
4121
+ * endsWith('this is a test', 'test'); // => true
4122
+ * endsWith('this is a test', 'Test'); // => false
4123
+ * endsWith('this is a test', 'Test', true); // => true
4124
+ * endsWith('this is a test', 'not'); // => false
4125
+ * ```
4126
+ */
4127
+ function endsWith(haystack, needle, ignoreCase) {
4128
+ return match(STRING_MATCH_ENDS_WITH, haystack, needle, ignoreCase === true);
4129
+ }
4130
+ /**
4131
+ * Check if a string includes a specified substring
4132
+ *
4133
+ * @param haystack String to look in
4134
+ * @param needle String to look for
4135
+ * @param ignoreCase Ignore case when matching? _(defaults to `false`)_
4136
+ * @returns `true` if the string includes the given substring, otherwise `false`
4137
+ *
4138
+ * @example
4139
+ * ```typescript
4140
+ * includes('this is a test', 'is'); // => true
4141
+ * includes('this is a test', 'Is'); // => false
4142
+ * includes('this is a test', 'Is', true); // => true
4143
+ * includes('this is a test', 'not'); // => false
4144
+ * ```
4145
+ */
4146
+ function includes(haystack, needle, ignoreCase) {
4147
+ return match(STRING_MATCH_INCLUDES, haystack, needle, ignoreCase === true);
4148
+ }
4149
+ function match(type, haystack, needle, ignoreCase) {
4150
+ if (typeof haystack !== "string" || typeof needle !== "string") return false;
4151
+ matchMemoizers[type] ??= memoize(matchCallback.bind(type));
4152
+ return matchMemoizers[type].run(haystack, needle, ignoreCase);
4153
+ }
4154
+ function matchCallback(haystack, needle, ignoreCase) {
4155
+ return (ignoreCase ? haystack.toLocaleLowerCase() : haystack)[this](ignoreCase ? needle.toLocaleLowerCase() : needle);
4156
+ }
4157
+ /**
4158
+ * Check if a string starts with a specified substring
4159
+ *
4160
+ * @param haystack String to look in
4161
+ * @param needle String to look for
4162
+ * @param ignoreCase Ignore case when matching? _(defaults to `false`)_
4163
+ * @returns `true` if the string starts with the given substring, otherwise `false`
4164
+ *
4165
+ * @example
4166
+ * ```typescript
4167
+ * startsWith('this is a test', 'this'); // => true
4168
+ * startsWith('this is a test', 'This'); // => false
4169
+ * startsWith('this is a test', 'This', true); // => true
4170
+ * startsWith('this is a test', 'not'); // => false
4171
+ * ```
4172
+ */
4173
+ function startsWith(haystack, needle, ignoreCase) {
4174
+ return match(STRING_MATCH_STARTS_WITH, haystack, needle, ignoreCase === true);
4175
+ }
4176
+ const matchMemoizers = {};
4177
+ //#endregion
4092
4178
  //#region src/models/string/string.normalize.model.ts
4093
4179
  const NORMALIZE_DEBURR_CHARACTERS = {
4094
4180
  Æ: "AE",
@@ -4195,7 +4281,7 @@ let deburrMemoizer;
4195
4281
  normalize.initialize = initializeNormalizer;
4196
4282
  //#endregion
4197
4283
  //#region src/models/string/string.template.model.ts
4198
- const TEMPLATE_EXPRESSION_VARIABLE = /{{([\s\S]+?)}}/g;
4284
+ const TEMPLATE_EXPRESSION_VARIABLE = /{{([^{}]+)}}/g;
4199
4285
  const TEMPLATE_SYMBOL = Symbol("template");
4200
4286
  //#endregion
4201
4287
  //#region src/string/template.ts
@@ -5002,9 +5088,7 @@ function addSubscription(instance) {
5002
5088
  return;
5003
5089
  }
5004
5090
  /* istanbul ignore if */
5005
- if (items.keyed == null || values.from.keyed == null || values.to.keyed == null)
5006
- // istanbul ignore next
5007
- return;
5091
+ if (items.keyed == null || values.from.keyed == null || values.to.keyed == null) return;
5008
5092
  let keyedItems = items.keyed.get(key);
5009
5093
  if (keyedItems == null) {
5010
5094
  keyedItems = /* @__PURE__ */ new Set();
@@ -5154,9 +5238,7 @@ function removeSubscription() {
5154
5238
  }
5155
5239
  const keyed = items.keyed?.get(key);
5156
5240
  /* istanbul ignore if */
5157
- if (items.keyed == null || keyed == null)
5158
- // istanbul ignore next
5159
- return;
5241
+ if (items.keyed == null || keyed == null) return;
5160
5242
  removeFromStore(keyed, values, this, value, key);
5161
5243
  if (keyed.size === 0) items.keyed.delete(key);
5162
5244
  }
@@ -7578,4 +7660,4 @@ var SizedSet = class extends Set {
7578
7660
  }
7579
7661
  };
7580
7662
  //#endregion
7581
- export { CancelablePromise, Logger, PromiseTimeoutError, RetryError, SORT_DIRECTION_ASCENDING, SORT_DIRECTION_DESCENDING, SizedMap, SizedSet, push as append, push, assert, assertCondition, assertDefined, assertInstanceOf, assertIs, assertProperty, assign, asyncAttempt, asyncAttemptRun, asyncDebounce, asyncFail, asyncFlow, asyncMatchResult, asyncOnce, asyncPipe, asyncRun, asyncSucceed, asyncThrottle, asyncUse, attempt, attemptAsyncFlow, attemptAsyncPipe, attemptFlow, attemptPipe, attemptPromise, attemptRun, average, beacon, between, camelCase, cancelable, capitalize, ceil, chunk, clamp, clone, compact, compare, copy, count, debounce, deburr, dedent, delay, deregisterCloner, deregisterComparator, deregisterEqualizer, diff, difference, drop, endsWith, endsWithArray, equal, error, exclude, exists, fail, filter, filterAndFlatMap, filterAndMap, filterAndMap as select, find, findLast, first, firstOrDefault, flatFreeze, flatMapAndFilter, flatten, floor, flow, freeze, fromQuery, toPromise as fromResult, toPromise, fuzzy, fuzzyMatch, getArray, getArrayComparison, getColor, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getHwbColor, getHwbaColor, getNormalizedHex, getNumber, getRandom, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloatingNumber as getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getSortedIndex, getString, getTimedPromise, getUuid, getValue, groupArraysBy, groupBy, handleResult, hasValue, hasValueResult, herald, hexToHsl, hexToHsla, hexToHwb, hexToHwba, hexToRgb, hexToRgba, hslToHex, hslToHwb, hslToHwba, hslToRgb, hslToRgba, hwbToHex, hwbToHsl, hwbToHsla, hwbToRgb, hwbToRgba, ignoreKey, inMap, inSet, includes, includesArray, indexOf, indexOfArray, initializeAssigner, initializeCloner, initializeEqualizer, initializeMerger, initializeNormalizer, initializeSorter, initializeTemplater, initializeTransformer, insert, interpolate, intersection, isArrayOrPlainObject, isAsyncFailure, isAsyncGenerator, isAsyncGeneratorFunction, isAsyncOnce, isAsyncPlan, isAsyncStep, isAsyncSuccess, isBeacon, isBeaconSubscription, isColor, isConstructor, isEmpty, isError, isFailure, isFrozen, isFulfilled, isFuzzy, isGenerator, isGeneratorFunction, isHerald, isHeraldEvents, isHeraldSubscription, isHexColor, isHslColor, isHslaColor, isHwbColor, isHwbaColor, isInstanceOf, isKey, isKeyedQueue, isLogger, isNonArrayOrPlainObject, isNonConstructor, isNonEmpty, isNonInstanceOf, isNonKey, isNonNullable, isNonNullableOrEmpty, isNonNullableOrWhitespace, isNonNumber, isNonNumerical, isNonObject, isNonPlainObject, isNonPrimitive, isNonTemplateStringsArray, isNonTypedArray, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isObservable, isOk, isOnce, isPlainObject, isPlan, isPrimitive, isQueue, isRejected, isResult, isRgbColor, isRgbaColor, isSorted, isStep, isSubscription, isSubscriptions, isSuccess, isTemplateStringsArray, isTimeLogger, isTypedArray, join, kebabCase, keyedQueue, last, lastIndexOf, lastOrDefault, lowerCase, mapAndFilter, mapAndFilter as reverseSelect, matchResult, max, median, memoize, merge, min, move, moveIndices, moveToIndex, noop, normalize, ok, omit, once, parse, partition, pascalCase, pick, pipe, plan, promises, queue, range, registerCloner, registerComparator, registerEqualizer, resultPromises, retry, reverse, rgbToHex, rgbToHsl, rgbToHsla, rgbToHwb, rgbToHwba, round, run, setValue, settlePromise, shake, shuffle, single, slice, smush, snakeCase, sort, splice, startsWith, startsWithArray, subscriptions, succeed, sum, swap, take, template, throttle, timed, times, titleCase, toMap, toMapArrays, toQuery, toRecord, toRecordArrays, toResult, toSet, toggle, transform, trim, truncate, tryDecode, tryEncode, union, unique, unsmush, unwrap, update, upperCase, use, words };
7663
+ export { CancelablePromise, Logger, PromiseTimeoutError, RetryError, SORT_DIRECTION_ASCENDING, SORT_DIRECTION_DESCENDING, SizedMap, SizedSet, push as append, push, assert, assertCondition, assertDefined, assertInstanceOf, assertIs, assertProperty, assign, asyncAttempt, asyncAttemptRun, asyncDebounce, asyncFail, asyncFlow, asyncMatchResult, asyncOnce, asyncPipe, asyncRun, asyncSucceed, asyncThrottle, asyncUse, attempt, attemptAsyncFlow, attemptAsyncPipe, attemptFlow, attemptPipe, attemptPromise, attemptRun, average, beacon, between, camelCase, cancelable, capitalize, ceil, chunk, clamp, clone, compact, compare, copy, count, debounce, deburr, dedent, delay, deregisterCloner, deregisterComparator, deregisterEqualizer, diff, difference, drop, endsWith, endsWithArray, equal, error, exclude, exists, fail, filter, filterAndFlatMap, filterAndMap, filterAndMap as select, find, findLast, first, firstOrDefault, flatFreeze, flatMapAndFilter, flatten, floor, flow, freeze, fromQuery, toPromise as fromResult, toPromise, fuzzy, fuzzyMatch, getArray, getArrayComparison, getColor, getForegroundColor, getHexColor, getHexaColor, getHslColor, getHslaColor, getHwbColor, getHwbaColor, getNormalizedHex, getNumber, getRandom, getRandomBoolean, getRandomCharacters, getRandomColor, getRandomFloatingNumber as getRandomFloat, getRandomHex, getRandomInteger, getRandomItem, getRandomItems, getRgbColor, getRgbaColor, getSortedIndex, getString, getTimedPromise, getUuid, getValue, groupArraysBy, groupBy, handleResult, hasValue, hasValueResult, herald, hexToHsl, hexToHsla, hexToHwb, hexToHwba, hexToRgb, hexToRgba, hslToHex, hslToHwb, hslToHwba, hslToRgb, hslToRgba, hwbToHex, hwbToHsl, hwbToHsla, hwbToRgb, hwbToRgba, ignoreKey, inMap, inSet, includes, includesArray, indexOf, indexOfArray, initializeAssigner, initializeCloner, initializeEqualizer, initializeMerger, initializeNormalizer, initializeSorter, initializeTemplater, initializeTransformer, insert, interpolate, intersection, isArrayOrPlainObject, isAsyncFailure, isAsyncGenerator, isAsyncGeneratorFunction, isAsyncOnce, isAsyncPlan, isAsyncStep, isAsyncSuccess, isBeacon, isBeaconSubscription, isColor, isConstructor, isEmpty, isError, isFailure, isFrozen, isFulfilled, isFuzzy, isGenerator, isGeneratorFunction, isHerald, isHeraldEvents, isHeraldSubscription, isHexColor, isHslColor, isHslaColor, isHwbColor, isHwbaColor, isInstanceOf, isKey, isKeyedQueue, isLogger, isNonArrayOrPlainObject, isNonConstructor, isNonEmpty, isNonInstanceOf, isNonKey, isNonNullable, isNonNullableOrEmpty, isNonNullableOrWhitespace, isNonNumber, isNonNumerical, isNonObject, isNonPlainObject, isNonPrimitive, isNonTemplateStringsArray, isNonTypedArray, isNullable, isNullableOrEmpty, isNullableOrWhitespace, isNumber, isNumerical, isObject, isObservable, isOk, isOnce, isPlainObject, isPlan, isPrimitive, isQueue, isRejected, isResult, isRgbColor, isRgbaColor, isSorted, isStep, isSubscription, isSubscriptions, isSuccess, isTemplateStringsArray, isTimeLogger, isTypedArray, join, kebabCase, keyedQueue, last, lastIndexOf, lastOrDefault, lowerCase, mapAndFilter, mapAndFilter as reverseSelect, matchResult, max, median, memoize, merge, min, move, moveIndices, moveToIndex, noop, normalize, ok, omit, once, parse, partition, pascalCase, pick, pipe, plan, promises, queue, range, registerCloner, registerComparator, registerEqualizer, resultPromises, retry, reverse, rgbToHex, rgbToHsl, rgbToHsla, rgbToHwb, rgbToHwba, round, run, setValue, settlePromise, shake, shuffle, single, slice, smush, snakeCase, sort, splice, startsWith, startsWithArray, subscriptions, succeed, sum, swap, take, template, throttle, timed, times, titleCase, toCaseCallback, toMap, toMapArrays, toQuery, toRecord, toRecordArrays, toResult, toSet, toggle, transform, trim, truncate, tryDecode, tryEncode, union, unique, unsmush, unwrap, update, upperCase, use, words };