@superutils/core 1.0.5 → 1.0.6

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/README.md CHANGED
@@ -222,7 +222,7 @@ search(data, { query: { age: 28, name: 've' } })
222
222
  // [4, { age: 28, name: 'Dave' }],
223
223
  // ])
224
224
 
225
- // Fuzzy search accross all properties
225
+ // Search across all properties
226
226
  search(data, { query: 'li' })
227
227
  search(data, { query: /li/i }) // Using regular expression
228
228
  // Result:
@@ -255,7 +255,7 @@ search(data, {
255
255
  age: /(2[5-9])|(3[0-5])/, // match ages 25-35
256
256
  name: /ali|ob|ve/i,
257
257
  },
258
- // transform the property values (or item itself when in fuzzy search mode)
258
+ // transform the property values (or item itself when searching all properties in global search mode using `query: string | RegExp`)
259
259
  transform: (item, value, property) => {
260
260
  // exclude items by returning undefined or emptry string
261
261
  if (item.age < 18) return ''
package/dist/index.d.ts CHANGED
@@ -966,7 +966,7 @@ type SortOptions = {
966
966
  undefinedFirst?: boolean;
967
967
  };
968
968
  /** Search criteria for searcheing iterables */
969
- type SearchOptions<K, V, AsMap extends boolean = false> = {
969
+ type SearchOptions<K, V, MatchExact extends boolean = false, AsMap extends boolean = false> = {
970
970
  /** Whethere to return the result as a map (`true`) or array (`false`). Default: `true` */
971
971
  asMap?: AsMap;
972
972
  /** case-insensitive search for strings. Default: `false` */
@@ -974,21 +974,33 @@ type SearchOptions<K, V, AsMap extends boolean = false> = {
974
974
  /** limit number of results. Default: `Infinity` */
975
975
  limit?: number;
976
976
  /** partial match for values. Default: `false` */
977
- matchExact?: boolean;
977
+ matchExact?: MatchExact;
978
978
  /** match all supplied key-value pairs. Default: `false` */
979
979
  matchAll?: boolean;
980
980
  /** key-value pairs */
981
- query: Record<string, unknown> | string | RegExp;
981
+ query: Record<PropertyKey, unknown> | RegExp | string;
982
+ /** If `true`, the results are sorted by relevance (match index). Default: `false` */
983
+ ranked?: boolean;
982
984
  /** Map to store results in. Default: `new Map()` */
983
985
  result?: Map<K, V>;
984
- /** Callback to convert item/item-property to string */
985
- transform?: (
986
+ /**
987
+ * Boolean or Callback to prepare item or individual property for search by converting to string.
988
+ *
989
+ * - `true`: value will be stringified
990
+ * - `false`: value will not be stringified when `matchExact = true`
991
+ * - `function`: transformed value will be used to search
992
+ *
993
+ * Returning "empty" (`undefined | null | [] | '' | ...`) value will ignore the item/property.
994
+ *
995
+ * Default: `true`
996
+ */
997
+ transform?: boolean | ((
986
998
  /** List item */
987
999
  item: V,
988
- /** Item property value or `undefined` for fuzzy search. */
1000
+ /** Item property value or `undefined` for global search across all properties. */
989
1001
  value?: V[keyof V],
990
- /** Item property key provided by query or `undefined` for fuzzy search. */
991
- key?: keyof V) => string | undefined;
1002
+ /** Item property key provided by query or `undefined` for global search across all properties. */
1003
+ key?: keyof V) => MatchExact extends true ? unknown : string | undefined);
992
1004
  };
993
1005
 
994
1006
  /**
@@ -1085,8 +1097,8 @@ declare const getValues: <K, V>(data: IterableList<K, V>) => V[];
1085
1097
  declare const reverse: <K, V, T extends IterableList<K, V>>(data: T, reverse?: boolean, newInstance?: boolean) => V[] | [K, V][] | Map<K, V> | Set<V> | (T & Record<"clear", unknown>);
1086
1098
 
1087
1099
  /**
1088
- * A versatile utility for searching through an iterable list (e.g., Array, Map, Set) of objects.
1089
- * It supports both a simple "fuzzy" search with a string query across all properties and a
1100
+ * A versatile utility for searching through an iterable list (Array, Map, or Set) of objects.
1101
+ * It supports both a global search (using a string or RegExp) across all properties of an item, and a
1090
1102
  * detailed, field-specific search using a query object.
1091
1103
  *
1092
1104
  * @param data The list of objects to search within. Compatible types include:
@@ -1097,6 +1109,8 @@ declare const reverse: <K, V, T extends IterableList<K, V>>(data: T, reverse?: b
1097
1109
  * - `HTMLCollection` (in DOM environments): should accompany `options.transform()`
1098
1110
  * @param options The search criteria.
1099
1111
  * @param options.query The search query. Can be a string to search all fields, or an object for field-specific
1112
+ * @param options.ranked (optional) If `true`, the results are sorted by relevance (match index). Default: `false`.
1113
+ *
1100
1114
  * searches (e.g., `{ name: 'John', city: 'New York' }`).
1101
1115
  * @param options.asMap (optional) If `true`, returns a `Map`. If `false`, returns an `Array`. Default: `true`.
1102
1116
  * @param options.ignoreCase (optional) If `true`, performs a case-insensitive search for strings. Default: `true`.
@@ -1131,12 +1145,16 @@ declare const reverse: <K, V, T extends IterableList<K, V>>(data: T, reverse?: b
1131
1145
  * ```
1132
1146
  */
1133
1147
  declare const search: {
1134
- <K, V, AsMap extends boolean = true, Result = AsMap extends true ? Map<K, V> : V[]>(data: IterableList<K, V>, options: SearchOptions<K, V, AsMap>): Result;
1135
- defaultOptions: Pick<Required<SearchOptions<unknown, unknown, true>>, "matchAll" | "limit" | "asMap" | "ignoreCase" | "matchExact">;
1148
+ <K, V, MatchExact extends boolean = false, AsMap extends boolean = true, Result = AsMap extends true ? Map<K, V> : V[]>(data: IterableList<K, V>, options: SearchOptions<K, V, MatchExact, AsMap>): Result;
1149
+ defaultOptions: Pick<Required<SearchOptions<unknown, unknown, false, true>>, "matchAll" | "limit" | "asMap" | "ignoreCase" | "ranked" | "transform">;
1136
1150
  };
1137
- /** Utility for use with {@link search} function */
1138
- declare function matchItemOrProp<K, V>(// extends Record<string, unknown>
1139
- { query, ignoreCase, matchExact, transform, }: Pick<SearchOptions<K, V, boolean>, 'transform' | 'query' | 'ignoreCase' | 'matchExact'>, item: V, propertyName?: string): boolean;
1151
+ /**
1152
+ * Utility for use with {@link search} function
1153
+ *
1154
+ * @returns match index (`-1` means didn't match)
1155
+ */
1156
+ declare function matchObjOrProp<K, V>(// extends Record<string, unknown>
1157
+ { query, ignoreCase, matchExact, transform, }: Pick<SearchOptions<K, V, boolean>, 'transform' | 'query' | 'ignoreCase' | 'matchExact'>, item: V, propertyName?: string): number;
1140
1158
 
1141
1159
  type SliceMapTransform<Data, Value, Key> = (item: Value, key: Key, data: Data) => Value;
1142
1160
  type SliceMapOptions<Data, Value, Key, AsMap extends boolean = false> = {
@@ -1356,4 +1374,4 @@ declare const HASH_REGEX: RegExp;
1356
1374
  */
1357
1375
  declare const strToArr: (value: unknown, seperator?: string) => string[];
1358
1376
 
1359
- export { type ArrayComparator, type AsyncFn, type CreateTuple, type CurriedArgs, type Curry, type DeferredOptions, type DropFirst, type DropFirstN, type DropLast, EMAIL_REGEX, type EntryComparator, type FindOptions, HASH_REGEX, HEX_REGEX, type IfPromiseAddValue, type IsFiniteTuple, type IsOptional, type IterableList, type KeepFirst, type KeepFirstN, type KeepOptionals, type KeepRequired, type MakeOptional, type MinLength, type NegativeNumber, type OptionalIf, type PositiveNumber, type PositiveNumberWithZero, type ReadOnlyAllowAddFn, ReadOnlyArrayHelper, type ReadOnlyConfig, type SearchOptions, type Slice, type SliceMapOptions, type SliceMapTransform, type SortOptions, type ThrottleOptions, type TimeoutId, type TupleMaxLength, type TupleWithAlt, type ValueOrFunc, type ValueOrPromise, arrReadOnly, arrReverse, arrToMap, arrUnique, asAny, clearClutter, copyToClipboard, curry, debounce, deferred, fallbackIfFails, filter, find, forceCast, getEntries, getKeys, getSize, getUrlParam, getValues, is, isArr, isArr2D, isArrLike, isArrLikeSafe, isArrObj, isArrUnique, isAsyncFn, isBool, isDate, isDateValid, isDefined, isEmpty, isEmptySafe, isEnvBrowser, isEnvNode, isEnvTouchable, isError, isFn, isInteger, isMap, isMapObj, isNumber, isObj, isPositiveInteger, isPositiveNumber, isPromise, isRegExp, isSet, isStr, isSymbol, isUint8Arr, isUrl, isUrlValid, mapJoin, matchItemOrProp, noop, noopAsync, objClean, objCopy, objCreate, objHasKeys, objKeys, objReadOnly, objSetProp, objSetPropUndefined, objSort, objWithoutKeys, randomInt, reverse, search, sliceMap, sort, strToArr, throttled, toDatetimeLocal };
1377
+ export { type ArrayComparator, type AsyncFn, type CreateTuple, type CurriedArgs, type Curry, type DeferredOptions, type DropFirst, type DropFirstN, type DropLast, EMAIL_REGEX, type EntryComparator, type FindOptions, HASH_REGEX, HEX_REGEX, type IfPromiseAddValue, type IsFiniteTuple, type IsOptional, type IterableList, type KeepFirst, type KeepFirstN, type KeepOptionals, type KeepRequired, type MakeOptional, type MinLength, type NegativeNumber, type OptionalIf, type PositiveNumber, type PositiveNumberWithZero, type ReadOnlyAllowAddFn, ReadOnlyArrayHelper, type ReadOnlyConfig, type SearchOptions, type Slice, type SliceMapOptions, type SliceMapTransform, type SortOptions, type ThrottleOptions, type TimeoutId, type TupleMaxLength, type TupleWithAlt, type ValueOrFunc, type ValueOrPromise, arrReadOnly, arrReverse, arrToMap, arrUnique, asAny, clearClutter, copyToClipboard, curry, debounce, deferred, fallbackIfFails, filter, find, forceCast, getEntries, getKeys, getSize, getUrlParam, getValues, is, isArr, isArr2D, isArrLike, isArrLikeSafe, isArrObj, isArrUnique, isAsyncFn, isBool, isDate, isDateValid, isDefined, isEmpty, isEmptySafe, isEnvBrowser, isEnvNode, isEnvTouchable, isError, isFn, isInteger, isMap, isMapObj, isNumber, isObj, isPositiveInteger, isPositiveNumber, isPromise, isRegExp, isSet, isStr, isSymbol, isUint8Arr, isUrl, isUrlValid, mapJoin, matchObjOrProp, noop, noopAsync, objClean, objCopy, objCreate, objHasKeys, objKeys, objReadOnly, objSetProp, objSetPropUndefined, objSort, objWithoutKeys, randomInt, reverse, search, sliceMap, sort, strToArr, throttled, toDatetimeLocal };
package/dist/index.js CHANGED
@@ -579,11 +579,23 @@ var search = (data, options) => {
579
579
  const result = isMap(options == null ? void 0 : options.result) ? options.result : /* @__PURE__ */ new Map();
580
580
  const asMap = (_a = options == null ? void 0 : options.asMap) != null ? _a : search.defaultOptions.asMap;
581
581
  if (ignore) return asMap ? result : getValues_default(result);
582
- options = objCopy(search.defaultOptions, options, [], "empty");
583
- const { ignoreCase, limit = Infinity, matchAll, matchExact } = options;
582
+ options = objCopy(
583
+ search.defaultOptions,
584
+ options,
585
+ [],
586
+ "empty"
587
+ // override `option` property with default value when "empty" (undefined, null, '',....)
588
+ );
589
+ const {
590
+ ignoreCase,
591
+ limit = Infinity,
592
+ matchAll,
593
+ matchExact,
594
+ ranked
595
+ } = options;
584
596
  let { query } = options;
585
597
  const qIsStr = isStr(query);
586
- const qIsRegExp = !qIsStr && isRegExp(query);
598
+ const qIsRegExp = isRegExp(query);
587
599
  const qKeys = fallbackIfFails_default(Object.keys, [query], []);
588
600
  if (ignoreCase && !matchExact && !qIsRegExp) {
589
601
  query = qIsStr ? query.toLowerCase() : objCreate(
@@ -594,15 +606,45 @@ var search = (data, options) => {
594
606
  );
595
607
  }
596
608
  options.query = query;
597
- const entries = data.entries();
598
- for (const [dataKey, dataValue] of entries) {
599
- if (result.size >= limit) break;
600
- const matched = qIsStr || qIsRegExp ? matchItemOrProp(options, dataValue, void 0) : qKeys[matchAll ? "every" : "some"](
601
- (key) => matchItemOrProp(options, dataValue, key)
602
- // search specific properties
603
- );
604
- if (!matched) continue;
605
- result.set(dataKey, dataValue);
609
+ if (!ranked) {
610
+ for (const [dataKey, dataValue] of data.entries()) {
611
+ if (result.size >= limit) break;
612
+ const matched = qIsStr || qIsRegExp ? (
613
+ // global search across all properties
614
+ matchObjOrProp(options, dataValue, void 0) >= 0
615
+ ) : (
616
+ // field-specific search
617
+ qKeys[matchAll ? "every" : "some"](
618
+ (key) => matchObjOrProp(options, dataValue, key) >= 0
619
+ )
620
+ );
621
+ if (!matched) continue;
622
+ result.set(dataKey, dataValue);
623
+ }
624
+ } else {
625
+ const preRankedResults = [];
626
+ for (const [dataKey, dataValue] of data.entries()) {
627
+ let matchIndex = -1;
628
+ if (qIsStr || qIsRegExp) {
629
+ matchIndex = matchObjOrProp(options, dataValue, void 0);
630
+ matchIndex >= 0 && preRankedResults.push([matchIndex, dataKey, dataValue]);
631
+ continue;
632
+ }
633
+ const indexes = [];
634
+ const match = qKeys[matchAll ? "every" : "some"](
635
+ // field-specific search
636
+ (key) => {
637
+ const index = matchObjOrProp(options, dataValue, key);
638
+ indexes.push(index);
639
+ return index >= 0;
640
+ }
641
+ );
642
+ if (!match) continue;
643
+ matchIndex = // eslint-disable-next-line @typescript-eslint/prefer-find
644
+ indexes.sort((a, b) => a - b).filter((n) => n !== -1)[0];
645
+ matchIndex >= 0 && preRankedResults.push([matchIndex, dataKey, dataValue]);
646
+ }
647
+ preRankedResults.sort((a, b) => a[0] - b[0]).slice(0, limit).forEach(([_, key, value]) => result.set(key, value));
606
648
  }
607
649
  return asMap ? result : getValues_default(result);
608
650
  };
@@ -611,36 +653,37 @@ search.defaultOptions = {
611
653
  ignoreCase: true,
612
654
  limit: Infinity,
613
655
  matchAll: false,
614
- matchExact: false
656
+ ranked: false,
657
+ transform: true
615
658
  };
616
- function matchItemOrProp({
659
+ function matchObjOrProp({
617
660
  query,
618
661
  ignoreCase,
619
662
  matchExact,
620
- transform
663
+ transform = true
621
664
  }, item, propertyName) {
622
- const fuzzy = isStr(query) || isRegExp(query) || propertyName === void 0;
623
- const keyword = fuzzy ? query : query[propertyName];
624
- const propVal = fuzzy || !isObj(item) ? item : item[propertyName];
625
- let value = fallbackIfFails_default(
626
- () => {
627
- var _a;
628
- return isFn(transform) ? transform(
629
- item,
630
- fuzzy ? void 0 : propVal,
631
- propertyName
632
- ) : isObj(propVal, false) ? JSON.stringify(
633
- isArrLike(propVal) ? [...propVal.values()] : Object.values(propVal)
634
- ) : (_a = propVal == null ? void 0 : propVal.toString) == null ? void 0 : _a.call(propVal);
635
- },
665
+ var _a, _b;
666
+ const global = isStr(query) || isRegExp(query) || propertyName === void 0;
667
+ const keyword = global ? query : query[propertyName];
668
+ const propVal = global || !isObj(item) ? item : item[propertyName];
669
+ const value = fallbackIfFails_default(
670
+ () => isFn(transform) ? transform(
671
+ item,
672
+ global ? void 0 : propVal,
673
+ propertyName
674
+ ) : matchExact && transform === false ? propVal : isObj(propVal, false) ? JSON.stringify(
675
+ isArrLike(propVal) ? [...propVal.values()] : Object.values(propVal)
676
+ ) : String(propVal != null ? propVal : ""),
636
677
  [],
637
678
  ""
638
679
  );
639
- if (!(value == null ? void 0 : value.trim())) return false;
640
- if (isRegExp(keyword)) return keyword.test(`${value}`);
641
- if (ignoreCase && !matchExact) value = value.toLowerCase();
642
- if (value === keyword) return true;
643
- return !matchExact && `${value}`.includes(String(keyword));
680
+ if (value === keyword) return 0;
681
+ if (matchExact) return -1;
682
+ let valueStr = String(value);
683
+ if (!valueStr.trim()) return -1;
684
+ if (isRegExp(keyword)) return (_b = (_a = valueStr.match(keyword)) == null ? void 0 : _a.index) != null ? _b : -1;
685
+ if (ignoreCase) valueStr = valueStr.toLowerCase();
686
+ return valueStr.indexOf(String(keyword));
644
687
  }
645
688
  var search_default = search;
646
689
 
@@ -924,7 +967,7 @@ export {
924
967
  isUrl,
925
968
  isUrlValid,
926
969
  mapJoin,
927
- matchItemOrProp,
970
+ matchObjOrProp,
928
971
  noop,
929
972
  noopAsync,
930
973
  objClean,
package/package.json CHANGED
@@ -42,5 +42,5 @@
42
42
  "sideEffects": false,
43
43
  "type": "module",
44
44
  "types": "dist/index.d.ts",
45
- "version": "1.0.5"
45
+ "version": "1.0.6"
46
46
  }