@naturalcycles/js-lib 15.74.0 → 15.76.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 (43) hide show
  1. package/dist/array/array.util.d.ts +9 -0
  2. package/dist/array/array.util.js +20 -7
  3. package/dist/array/array2.js +1 -1
  4. package/dist/array/sort.js +2 -1
  5. package/dist/browser/analytics.util.js +2 -2
  6. package/dist/browser/bot.js +2 -2
  7. package/dist/datetime/localDate.js +2 -1
  8. package/dist/datetime/localTime.js +3 -8
  9. package/dist/decorators/asyncMemo.decorator.js +1 -1
  10. package/dist/is.util.js +17 -3
  11. package/dist/iter/iterable2.js +1 -1
  12. package/dist/math/math.util.js +4 -1
  13. package/dist/object/keySortedMap.js +1 -1
  14. package/dist/object/lazyKeySortedMap.js +1 -1
  15. package/dist/object/object.util.d.ts +23 -0
  16. package/dist/object/object.util.js +42 -1
  17. package/dist/object/set2.js +3 -3
  18. package/dist/promise/pFilter.js +1 -1
  19. package/dist/promise/pMap.js +1 -1
  20. package/dist/semver.js +2 -1
  21. package/dist/string/stringify.js +1 -1
  22. package/dist/typeFest.js +1 -1
  23. package/package.json +2 -2
  24. package/src/array/array.util.ts +20 -7
  25. package/src/array/array2.ts +1 -1
  26. package/src/array/sort.ts +2 -1
  27. package/src/browser/analytics.util.ts +2 -2
  28. package/src/browser/bot.ts +2 -2
  29. package/src/datetime/localDate.ts +2 -1
  30. package/src/datetime/localTime.ts +3 -7
  31. package/src/decorators/asyncMemo.decorator.ts +1 -1
  32. package/src/is.util.ts +14 -3
  33. package/src/iter/iterable2.ts +1 -1
  34. package/src/math/math.util.ts +4 -1
  35. package/src/object/keySortedMap.ts +1 -1
  36. package/src/object/lazyKeySortedMap.ts +1 -1
  37. package/src/object/object.util.ts +42 -1
  38. package/src/object/set2.ts +3 -3
  39. package/src/promise/pFilter.ts +1 -1
  40. package/src/promise/pMap.ts +1 -1
  41. package/src/semver.ts +2 -1
  42. package/src/string/stringify.ts +1 -1
  43. package/src/typeFest.ts +1 -1
@@ -217,6 +217,15 @@ export declare function _first<T>(array: readonly T[]): T;
217
217
  * Returns first item of the array (or undefined if array is empty).
218
218
  */
219
219
  export declare function _firstOrUndefined<T>(array: readonly T[]): T | undefined;
220
+ /**
221
+ * Returns the first item of an iterable (Set, Map.values(), generator, etc.),
222
+ * or `undefined` if the iterable is empty.
223
+ *
224
+ * Avoids the `Array.from(iter)[0]` pattern that materialises the entire iterable.
225
+ * `for...of` with an early return advances the iterator exactly once; if the
226
+ * iterator implements `return()` (generators, etc.), it is invoked for cleanup.
227
+ */
228
+ export declare function _firstFromIterable<T>(iter: Iterable<T>): T | undefined;
220
229
  export declare function _minOrUndefined<T>(array: readonly T[]): NonNullable<T> | undefined;
221
230
  /**
222
231
  * Filters out nullish values (undefined and null).
@@ -23,7 +23,7 @@ export function _chunk(array, size = 1) {
23
23
  * Removes duplicates from given array.
24
24
  */
25
25
  export function _uniq(a) {
26
- return [...new Set(a)];
26
+ return Array.from(new Set(a));
27
27
  }
28
28
  /**
29
29
  * Pushes an item to an array if it's not already there.
@@ -84,7 +84,7 @@ export function _uniqBy(arr, mapper) {
84
84
  if (!map.has(key))
85
85
  map.set(key, item);
86
86
  }
87
- return [...map.values()];
87
+ return Array.from(map.values());
88
88
  }
89
89
  /**
90
90
  * const a = [
@@ -174,7 +174,7 @@ export function _find(items, predicate) {
174
174
  * - in iOS Safari since 15.4
175
175
  */
176
176
  export function _findLast(items, predicate) {
177
- return _find(items.slice().reverse(), predicate);
177
+ return _find(items.toReversed(), predicate);
178
178
  }
179
179
  export function _takeWhile(items, predicate) {
180
180
  let proceed = true;
@@ -182,7 +182,7 @@ export function _takeWhile(items, predicate) {
182
182
  }
183
183
  export function _takeRightWhile(items, predicate) {
184
184
  let proceed = true;
185
- return [...items].reverse().filter((v, index) => (proceed &&= predicate(v, index)));
185
+ return items.toReversed().filter((v, index) => (proceed &&= predicate(v, index)));
186
186
  }
187
187
  export function _dropWhile(items, predicate) {
188
188
  let proceed = false;
@@ -190,8 +190,8 @@ export function _dropWhile(items, predicate) {
190
190
  }
191
191
  export function _dropRightWhile(items, predicate) {
192
192
  let proceed = false;
193
- return [...items]
194
- .reverse()
193
+ return items
194
+ .toReversed()
195
195
  .filter((v, index) => (proceed ||= !predicate(v, index)))
196
196
  .reverse();
197
197
  }
@@ -356,7 +356,7 @@ export function _mapToObject(array, mapper) {
356
356
  * Based on: https://stackoverflow.com/a/12646864/4919972
357
357
  */
358
358
  export function _shuffle(array, opt = {}) {
359
- const a = opt.mutate ? array : [...array];
359
+ const a = opt.mutate ? array : array.slice();
360
360
  for (let i = a.length - 1; i > 0; i--) {
361
361
  const j = Math.floor(Math.random() * (i + 1));
362
362
  [a[i], a[j]] = [a[j], a[i]];
@@ -403,6 +403,19 @@ export function _first(array) {
403
403
  export function _firstOrUndefined(array) {
404
404
  return array[0];
405
405
  }
406
+ /**
407
+ * Returns the first item of an iterable (Set, Map.values(), generator, etc.),
408
+ * or `undefined` if the iterable is empty.
409
+ *
410
+ * Avoids the `Array.from(iter)[0]` pattern that materialises the entire iterable.
411
+ * `for...of` with an early return advances the iterator exactly once; if the
412
+ * iterator implements `return()` (generators, etc.), it is invoked for cleanup.
413
+ */
414
+ export function _firstFromIterable(iter) {
415
+ for (const item of iter)
416
+ return item;
417
+ return undefined;
418
+ }
406
419
  export function _minOrUndefined(array) {
407
420
  let min;
408
421
  for (const item of array) {
@@ -8,7 +8,7 @@ export class Array2 extends Array {
8
8
  static of(...items) {
9
9
  return new Array2(...items);
10
10
  }
11
- // eslint-disable-next-line @typescript-eslint/class-literal-property-style
11
+ // oxlint-disable-next-line @typescript-eslint/class-literal-property-style
12
12
  get [Symbol.toStringTag]() {
13
13
  return 'Array2';
14
14
  }
@@ -51,7 +51,8 @@ export const comparators = new Comparators();
51
51
  * _sortBy([{age: 20}, {age: 10}], o => o.age)
52
52
  */
53
53
  export function _sortBy(items, mapper, opt = {}) {
54
- return (opt.mutate ? items : [...items]).sort(comparators.by(mapper, opt));
54
+ const cmp = comparators.by(mapper, opt);
55
+ return opt.mutate ? items.sort(cmp) : items.toSorted(cmp);
55
56
  }
56
57
  /**
57
58
  * Like _stringMapValues, but values are sorted.
@@ -32,7 +32,7 @@ export function loadHotjar(hjid) {
32
32
  if (isServerSide()) {
33
33
  return;
34
34
  }
35
- /* eslint-disable */
35
+ /* oxlint-disable */
36
36
  // oxfmt-ignore
37
37
  ;
38
38
  ((h, o, t, j, a, r) => {
@@ -49,5 +49,5 @@ export function loadHotjar(hjid) {
49
49
  r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
50
50
  a.append(r);
51
51
  })(globalThis, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=');
52
- /* eslint-enable */
52
+ /* oxlint-enable */
53
53
  }
@@ -98,7 +98,7 @@ export class BotDetectionService {
98
98
  return false;
99
99
  let cdpCheck1 = false;
100
100
  try {
101
- /* eslint-disable */
101
+ /* oxlint-disable */
102
102
  const e = new window.Error();
103
103
  window.Object.defineProperty(e, 'stack', {
104
104
  configurable: false,
@@ -110,7 +110,7 @@ export class BotDetectionService {
110
110
  });
111
111
  // This is part of the detection and shouldn't be deleted
112
112
  window.console.debug(e);
113
- /* eslint-enable */
113
+ /* oxlint-enable */
114
114
  }
115
115
  catch { }
116
116
  return cdpCheck1;
@@ -615,7 +615,8 @@ class LocalDateFactory {
615
615
  */
616
616
  sort(items, opt = {}) {
617
617
  const mod = opt.dir === 'desc' ? -1 : 1;
618
- return (opt.mutate ? items : [...items]).sort((a, b) => a.compare(b) * mod);
618
+ const cmp = (a, b) => a.compare(b) * mod;
619
+ return opt.mutate ? items.sort(cmp) : items.toSorted(cmp);
619
620
  }
620
621
  /**
621
622
  * Returns the earliest (min) LocalDate from the array, or undefined if the array is empty.
@@ -1,3 +1,4 @@
1
+ import { comparators } from '../array/sort.js';
1
2
  import { _assert } from '../error/assert.js';
2
3
  import { localDate } from './localDate.js';
3
4
  import { _ms } from './time.util.js';
@@ -868,14 +869,8 @@ class LocalTimeFactory {
868
869
  return Intl.supportedValuesOf('timeZone').includes(tz);
869
870
  }
870
871
  sort(items, dir = 'asc', opt = {}) {
871
- const mod = dir === 'desc' ? -1 : 1;
872
- return (opt.mutate ? items : [...items]).sort((a, b) => {
873
- const v1 = a.$date.valueOf();
874
- const v2 = b.$date.valueOf();
875
- if (v1 === v2)
876
- return 0;
877
- return (v1 < v2 ? -1 : 1) * mod;
878
- });
872
+ const cmp = comparators.by((lt) => lt.$date.valueOf(), { dir });
873
+ return opt.mutate ? items.sort(cmp) : items.toSorted(cmp);
879
874
  }
880
875
  minOrUndefined(items) {
881
876
  let min;
@@ -92,7 +92,7 @@ export const _AsyncMemo = (opt) => (target, key, descriptor) => {
92
92
  _objectAssign(descriptor.value, {
93
93
  clear: async () => {
94
94
  logger.log(`${methodSignature} @_AsyncMemo.clear()`);
95
- await Promise.all([...instanceCache.values()].map(c => c.clear()));
95
+ await Promise.all(Array.from(instanceCache.values(), c => c.clear()));
96
96
  instanceCache.clear();
97
97
  },
98
98
  getInstanceCache: () => instanceCache,
package/dist/is.util.js CHANGED
@@ -33,10 +33,20 @@ export function _isPrimitive(v) {
33
33
  typeof v === 'symbol');
34
34
  }
35
35
  export function _isEmptyObject(obj) {
36
- return Object.keys(obj).length === 0;
36
+ // for...in with early return avoids allocating the full Object.keys array.
37
+ // Object.hasOwn matches Object.keys() semantics (own enumerable keys only).
38
+ for (const k in obj) {
39
+ if (Object.hasOwn(obj, k))
40
+ return false;
41
+ }
42
+ return true;
37
43
  }
38
44
  export function _isNotEmptyObject(obj) {
39
- return Object.keys(obj).length > 0;
45
+ for (const k in obj) {
46
+ if (Object.hasOwn(obj, k))
47
+ return true;
48
+ }
49
+ return false;
40
50
  }
41
51
  /**
42
52
  * Object is considered empty if it's one of:
@@ -58,7 +68,11 @@ export function _isEmpty(obj) {
58
68
  return obj.size === 0;
59
69
  }
60
70
  if (typeof obj === 'object') {
61
- return Object.keys(obj).length === 0;
71
+ for (const k in obj) {
72
+ if (Object.hasOwn(obj, k))
73
+ return false;
74
+ }
75
+ return true;
62
76
  }
63
77
  return false;
64
78
  }
@@ -22,7 +22,7 @@ export class Iterable2 {
22
22
  return this.it[Symbol.iterator]();
23
23
  }
24
24
  toArray() {
25
- return [...this.it];
25
+ return Array.from(this.it);
26
26
  }
27
27
  forEach(cb) {
28
28
  let i = 0;
@@ -104,5 +104,8 @@ export function _percentiles(values, pcs) {
104
104
  * // 2.5
105
105
  */
106
106
  export function _median(values) {
107
- return _percentile(values, 50);
107
+ const sorted = values.toSorted(comparators.numericAsc);
108
+ const len = sorted.length;
109
+ const mid = Math.floor(len / 2);
110
+ return len % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
108
111
  }
@@ -5,7 +5,7 @@ export class KeySortedMap {
5
5
  constructor(entries = [], opt = {}) {
6
6
  this.#comparator = opt.comparator;
7
7
  this.map = new Map(entries);
8
- this.#sortedKeys = [...this.map.keys()];
8
+ this.#sortedKeys = Array.from(this.map.keys());
9
9
  this.sortKeys();
10
10
  }
11
11
  #comparator;
@@ -6,7 +6,7 @@ export class LazyKeySortedMap {
6
6
  constructor(entries = [], opt = {}) {
7
7
  this.#comparator = opt.comparator;
8
8
  this.map = new Map(entries);
9
- this.maybeSortedKeys = [...this.map.keys()];
9
+ this.maybeSortedKeys = Array.from(this.map.keys());
10
10
  }
11
11
  #comparator;
12
12
  /**
@@ -87,6 +87,29 @@ export declare function _mapKeys<T extends AnyObject>(obj: T, mapper: ObjectMapp
87
87
  */
88
88
  export declare function _mapObject<OUT = unknown, IN extends AnyObject = AnyObject>(obj: IN, mapper: ObjectMapper<IN, KeyValueTuple<string, any> | typeof SKIP>): OUT;
89
89
  export declare function _findKeyByValue<T extends AnyObject>(obj: T, v: ValueOf<T>): keyof T | undefined;
90
+ /**
91
+ * Returns the first key of the object, or `undefined` if the object is empty.
92
+ *
93
+ * Performance-optimised: uses `for...in` with an early return to avoid
94
+ * allocating the full `Object.keys(obj)` array. The `Object.hasOwn` filter
95
+ * matches `Object.keys()` semantics (own enumerable string keys only) and
96
+ * satisfies the `guard-for-in` lint rule; cost is a single check before
97
+ * the early return. Iteration order matches `Object.keys()` for plain
98
+ * objects (integer-like keys ascending first, then string keys in
99
+ * insertion order).
100
+ */
101
+ export declare function _firstKey<T extends AnyObject>(obj: T): keyof T | undefined;
102
+ /**
103
+ * Returns the first value of the object, or `undefined` if the object is empty.
104
+ * See `_firstKey` for the iteration-order contract.
105
+ */
106
+ export declare function _firstValue<T extends AnyObject>(obj: T): ValueOf<T> | undefined;
107
+ /**
108
+ * Returns the first [key, value] tuple of the object,
109
+ * or `undefined` if the object is empty.
110
+ * See `_firstKey` for the iteration-order contract.
111
+ */
112
+ export declare function _firstEntry<T extends AnyObject>(obj: T): [keyof T, ValueOf<T>] | undefined;
90
113
  export declare function _objectNullValuesToUndefined<T extends AnyObject>(obj: T, opt?: MutateOptions): T;
91
114
  /**
92
115
  * Deep copy object (by json parse/stringify, since it has unbeatable performance+simplicity combo).
@@ -191,6 +191,47 @@ export function _mapObject(obj, mapper) {
191
191
  export function _findKeyByValue(obj, v) {
192
192
  return Object.entries(obj).find(([_, value]) => value === v)?.[0];
193
193
  }
194
+ /**
195
+ * Returns the first key of the object, or `undefined` if the object is empty.
196
+ *
197
+ * Performance-optimised: uses `for...in` with an early return to avoid
198
+ * allocating the full `Object.keys(obj)` array. The `Object.hasOwn` filter
199
+ * matches `Object.keys()` semantics (own enumerable string keys only) and
200
+ * satisfies the `guard-for-in` lint rule; cost is a single check before
201
+ * the early return. Iteration order matches `Object.keys()` for plain
202
+ * objects (integer-like keys ascending first, then string keys in
203
+ * insertion order).
204
+ */
205
+ export function _firstKey(obj) {
206
+ for (const k in obj) {
207
+ if (Object.hasOwn(obj, k))
208
+ return k;
209
+ }
210
+ return undefined;
211
+ }
212
+ /**
213
+ * Returns the first value of the object, or `undefined` if the object is empty.
214
+ * See `_firstKey` for the iteration-order contract.
215
+ */
216
+ export function _firstValue(obj) {
217
+ for (const k in obj) {
218
+ if (Object.hasOwn(obj, k))
219
+ return obj[k];
220
+ }
221
+ return undefined;
222
+ }
223
+ /**
224
+ * Returns the first [key, value] tuple of the object,
225
+ * or `undefined` if the object is empty.
226
+ * See `_firstKey` for the iteration-order contract.
227
+ */
228
+ export function _firstEntry(obj) {
229
+ for (const k in obj) {
230
+ if (Object.hasOwn(obj, k))
231
+ return [k, obj[k]];
232
+ }
233
+ return undefined;
234
+ }
194
235
  export function _objectNullValuesToUndefined(obj, opt = {}) {
195
236
  return _mapValues(obj, (_k, v) => (v === null ? undefined : v), opt);
196
237
  }
@@ -392,7 +433,7 @@ export function _set(obj, path, value) {
392
433
  a[c]
393
434
  : // No: create the key. Is the next key a potential array-index?
394
435
  (a[c] =
395
- // oxlint-disable-next-line no-bitwise no-implicit-coercion unicorn/prefer-math-trunc
436
+ // oxlint-disable-next-line no-bitwise, no-implicit-coercion, unicorn/prefer-math-trunc
396
437
  Math.abs(path[i + 1]) >> 0 === +path[i + 1]
397
438
  ? [] // Yes: assign a new array object
398
439
  : {}), // No: assign a new plain object
@@ -30,12 +30,12 @@ export class Set2 extends Set {
30
30
  // Last is not implemented, because it requires to traverse the whole Set - not optimal
31
31
  // last(): T {
32
32
  toArray() {
33
- return [...this];
33
+ return Array.from(this);
34
34
  }
35
35
  toJSON() {
36
- return [...this];
36
+ return Array.from(this);
37
37
  }
38
38
  toString() {
39
- return `Set2(${this.size}) ${JSON.stringify([...this])}`;
39
+ return `Set2(${this.size}) ${JSON.stringify(Array.from(this))}`;
40
40
  }
41
41
  }
@@ -1,5 +1,5 @@
1
1
  export async function pFilter(iterable, filterFn) {
2
- const items = [...iterable];
2
+ const items = Array.from(iterable);
3
3
  const predicates = await Promise.all(items.map((item, i) => filterFn(item, i)));
4
4
  return items.filter((_item, i) => predicates[i]);
5
5
  }
@@ -35,7 +35,7 @@ import { END, SKIP } from '../types.js';
35
35
  * })();
36
36
  */
37
37
  export async function pMap(iterable, mapper, opt = {}) {
38
- const items = [...iterable];
38
+ const items = Array.from(iterable);
39
39
  const itemsLength = items.length;
40
40
  if (itemsLength === 0)
41
41
  return []; // short circuit
package/dist/semver.js CHANGED
@@ -120,7 +120,8 @@ class SemverFactory {
120
120
  */
121
121
  sort(items, opt = {}) {
122
122
  const mod = opt.dir === 'desc' ? -1 : 1;
123
- return (opt.mutate ? items : [...items]).sort((a, b) => a.compare(b) * mod);
123
+ const cmp = (a, b) => a.compare(b) * mod;
124
+ return opt.mutate ? items.sort(cmp) : items.toSorted(cmp);
124
125
  }
125
126
  }
126
127
  const semverFactory = new SemverFactory();
@@ -88,7 +88,7 @@ export function _stringify(obj, opt = {}) {
88
88
  obj = Object.fromEntries(obj);
89
89
  }
90
90
  else if (obj instanceof Set) {
91
- obj = [...obj];
91
+ obj = Array.from(obj);
92
92
  }
93
93
  try {
94
94
  const { stringifyFn = globalStringifyFunction } = opt;
package/dist/typeFest.js CHANGED
@@ -1,2 +1,2 @@
1
- /* eslint-disable */
1
+ /* oxlint-disable */
2
2
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
3
  "type": "module",
4
- "version": "15.74.0",
4
+ "version": "15.76.0",
5
5
  "dependencies": {
6
6
  "tslib": "^2"
7
7
  },
@@ -17,7 +17,7 @@
17
17
  "@types/crypto-js": "^4",
18
18
  "@types/node": "^25",
19
19
  "@types/semver": "^7",
20
- "@typescript/native-preview": "7.0.0-dev.20260401.1",
20
+ "@typescript/native-preview": "beta",
21
21
  "crypto-js": "^4",
22
22
  "dayjs": "^1",
23
23
  "@naturalcycles/dev-lib": "18.4.2"
@@ -33,7 +33,7 @@ export function _chunk<T>(array: readonly T[], size = 1): T[][] {
33
33
  * Removes duplicates from given array.
34
34
  */
35
35
  export function _uniq<T>(a: readonly T[]): T[] {
36
- return [...new Set(a)]
36
+ return Array.from(new Set(a))
37
37
  }
38
38
 
39
39
  /**
@@ -95,7 +95,7 @@ export function _uniqBy<T>(arr: readonly T[], mapper: Mapper<T, any>): T[] {
95
95
  const key = item === undefined || item === null ? item : mapper(item)
96
96
  if (!map.has(key)) map.set(key, item)
97
97
  }
98
- return [...map.values()]
98
+ return Array.from(map.values())
99
99
  }
100
100
 
101
101
  /**
@@ -192,7 +192,7 @@ export function _find<T>(items: readonly T[], predicate: AbortablePredicate<T>):
192
192
  * - in iOS Safari since 15.4
193
193
  */
194
194
  export function _findLast<T>(items: readonly T[], predicate: AbortablePredicate<T>): T | undefined {
195
- return _find(items.slice().reverse(), predicate)
195
+ return _find(items.toReversed(), predicate)
196
196
  }
197
197
 
198
198
  export function _takeWhile<T>(items: readonly T[], predicate: Predicate<T>): T[] {
@@ -202,7 +202,7 @@ export function _takeWhile<T>(items: readonly T[], predicate: Predicate<T>): T[]
202
202
 
203
203
  export function _takeRightWhile<T>(items: readonly T[], predicate: Predicate<T>): T[] {
204
204
  let proceed = true
205
- return [...items].reverse().filter((v, index) => (proceed &&= predicate(v, index)))
205
+ return items.toReversed().filter((v, index) => (proceed &&= predicate(v, index)))
206
206
  }
207
207
 
208
208
  export function _dropWhile<T>(items: readonly T[], predicate: Predicate<T>): T[] {
@@ -212,8 +212,8 @@ export function _dropWhile<T>(items: readonly T[], predicate: Predicate<T>): T[]
212
212
 
213
213
  export function _dropRightWhile<T>(items: readonly T[], predicate: Predicate<T>): T[] {
214
214
  let proceed = false
215
- return [...items]
216
- .reverse()
215
+ return items
216
+ .toReversed()
217
217
  .filter((v, index) => (proceed ||= !predicate(v, index)))
218
218
  .reverse()
219
219
  }
@@ -416,7 +416,7 @@ export function _mapToObject<T, V>(
416
416
  * Based on: https://stackoverflow.com/a/12646864/4919972
417
417
  */
418
418
  export function _shuffle<T>(array: T[], opt: MutateOptions = {}): T[] {
419
- const a = opt.mutate ? array : [...array]
419
+ const a = opt.mutate ? array : array.slice()
420
420
 
421
421
  for (let i = a.length - 1; i > 0; i--) {
422
422
  const j = Math.floor(Math.random() * (i + 1))
@@ -468,6 +468,19 @@ export function _firstOrUndefined<T>(array: readonly T[]): T | undefined {
468
468
  return array[0]
469
469
  }
470
470
 
471
+ /**
472
+ * Returns the first item of an iterable (Set, Map.values(), generator, etc.),
473
+ * or `undefined` if the iterable is empty.
474
+ *
475
+ * Avoids the `Array.from(iter)[0]` pattern that materialises the entire iterable.
476
+ * `for...of` with an early return advances the iterator exactly once; if the
477
+ * iterator implements `return()` (generators, etc.), it is invoked for cleanup.
478
+ */
479
+ export function _firstFromIterable<T>(iter: Iterable<T>): T | undefined {
480
+ for (const item of iter) return item
481
+ return undefined
482
+ }
483
+
471
484
  export function _minOrUndefined<T>(array: readonly T[]): NonNullable<T> | undefined {
472
485
  let min: NonNullable<T> | undefined
473
486
  for (const item of array) {
@@ -10,7 +10,7 @@ export class Array2<T> extends Array<T> {
10
10
  return new Array2(...items)
11
11
  }
12
12
 
13
- // eslint-disable-next-line @typescript-eslint/class-literal-property-style
13
+ // oxlint-disable-next-line @typescript-eslint/class-literal-property-style
14
14
  get [Symbol.toStringTag](): string {
15
15
  return 'Array2'
16
16
  }
package/src/array/sort.ts CHANGED
@@ -76,7 +76,8 @@ export function _sortBy<T, COMPARE_TYPE extends string | number>(
76
76
  mapper: Mapper<T, COMPARE_TYPE>,
77
77
  opt: SortOptions = {},
78
78
  ): T[] {
79
- return (opt.mutate ? items : [...items]).sort(comparators.by(mapper, opt))
79
+ const cmp = comparators.by(mapper, opt)
80
+ return opt.mutate ? items.sort(cmp) : items.toSorted(cmp)
80
81
  }
81
82
 
82
83
  /**
@@ -44,7 +44,7 @@ export function loadHotjar(hjid: number): void {
44
44
  return
45
45
  }
46
46
 
47
- /* eslint-disable */
47
+ /* oxlint-disable */
48
48
  // oxfmt-ignore
49
49
  ;((h: any, o, t, j, a?: any, r?: any) => {
50
50
  h.hj =
@@ -59,5 +59,5 @@ export function loadHotjar(hjid: number): void {
59
59
  r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv
60
60
  a.append(r)
61
61
  })(globalThis, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv=')
62
- /* eslint-enable */
62
+ /* oxlint-enable */
63
63
  }
@@ -131,7 +131,7 @@ export class BotDetectionService {
131
131
  if (isServerSide()) return false
132
132
  let cdpCheck1 = false
133
133
  try {
134
- /* eslint-disable */
134
+ /* oxlint-disable */
135
135
  const e = new window.Error()
136
136
  window.Object.defineProperty(e, 'stack', {
137
137
  configurable: false,
@@ -143,7 +143,7 @@ export class BotDetectionService {
143
143
  })
144
144
  // This is part of the detection and shouldn't be deleted
145
145
  window.console.debug(e)
146
- /* eslint-enable */
146
+ /* oxlint-enable */
147
147
  } catch {}
148
148
  return cdpCheck1
149
149
  }
@@ -721,7 +721,8 @@ class LocalDateFactory {
721
721
  */
722
722
  sort(items: LocalDate[], opt: SortOptions = {}): LocalDate[] {
723
723
  const mod = opt.dir === 'desc' ? -1 : 1
724
- return (opt.mutate ? items : [...items]).sort((a, b) => a.compare(b) * mod)
724
+ const cmp = (a: LocalDate, b: LocalDate): number => a.compare(b) * mod
725
+ return opt.mutate ? items.sort(cmp) : items.toSorted(cmp)
725
726
  }
726
727
 
727
728
  /**
@@ -1,3 +1,4 @@
1
+ import { comparators } from '../array/sort.js'
1
2
  import { _assert } from '../error/assert.js'
2
3
  import type {
3
4
  IANATimezone,
@@ -1017,13 +1018,8 @@ class LocalTimeFactory {
1017
1018
  }
1018
1019
 
1019
1020
  sort(items: LocalTime[], dir: SortDirection = 'asc', opt: MutateOptions = {}): LocalTime[] {
1020
- const mod = dir === 'desc' ? -1 : 1
1021
- return (opt.mutate ? items : [...items]).sort((a, b) => {
1022
- const v1 = a.$date.valueOf()
1023
- const v2 = b.$date.valueOf()
1024
- if (v1 === v2) return 0
1025
- return (v1 < v2 ? -1 : 1) * mod
1026
- })
1021
+ const cmp = comparators.by((lt: LocalTime) => lt.$date.valueOf(), { dir })
1022
+ return opt.mutate ? items.sort(cmp) : items.toSorted(cmp)
1027
1023
  }
1028
1024
 
1029
1025
  minOrUndefined(items: LocalTimeInputNullable[]): LocalTime | undefined {
@@ -148,7 +148,7 @@ export const _AsyncMemo =
148
148
  _objectAssign(descriptor.value as AsyncMemoInstance, {
149
149
  clear: async () => {
150
150
  logger.log(`${methodSignature} @_AsyncMemo.clear()`)
151
- await Promise.all([...instanceCache.values()].map(c => c.clear()))
151
+ await Promise.all(Array.from(instanceCache.values(), c => c.clear()))
152
152
  instanceCache.clear()
153
153
  },
154
154
  getInstanceCache: () => instanceCache,
package/src/is.util.ts CHANGED
@@ -45,11 +45,19 @@ export function _isPrimitive(v: any): v is Primitive {
45
45
  }
46
46
 
47
47
  export function _isEmptyObject(obj: AnyObject): boolean {
48
- return Object.keys(obj).length === 0
48
+ // for...in with early return avoids allocating the full Object.keys array.
49
+ // Object.hasOwn matches Object.keys() semantics (own enumerable keys only).
50
+ for (const k in obj) {
51
+ if (Object.hasOwn(obj, k)) return false
52
+ }
53
+ return true
49
54
  }
50
55
 
51
56
  export function _isNotEmptyObject(obj: AnyObject): boolean {
52
- return Object.keys(obj).length > 0
57
+ for (const k in obj) {
58
+ if (Object.hasOwn(obj, k)) return true
59
+ }
60
+ return false
53
61
  }
54
62
 
55
63
  /**
@@ -74,7 +82,10 @@ export function _isEmpty(obj: any): boolean {
74
82
  }
75
83
 
76
84
  if (typeof obj === 'object') {
77
- return Object.keys(obj).length === 0
85
+ for (const k in obj) {
86
+ if (Object.hasOwn(obj, k)) return false
87
+ }
88
+ return true
78
89
  }
79
90
 
80
91
  return false
@@ -25,7 +25,7 @@ export class Iterable2<T> implements Iterable<T> {
25
25
  }
26
26
 
27
27
  toArray(): T[] {
28
- return [...this.it]
28
+ return Array.from(this.it)
29
29
  }
30
30
 
31
31
  forEach(cb: (v: T, i: number) => any | typeof END): void {
@@ -117,5 +117,8 @@ export function _percentiles(values: number[], pcs: number[]): Record<number, nu
117
117
  * // 2.5
118
118
  */
119
119
  export function _median(values: number[]): number {
120
- return _percentile(values, 50)
120
+ const sorted = values.toSorted(comparators.numericAsc)
121
+ const len = sorted.length
122
+ const mid = Math.floor(len / 2)
123
+ return len % 2 ? sorted[mid]! : (sorted[mid - 1]! + sorted[mid]!) / 2
121
124
  }
@@ -32,7 +32,7 @@ export class KeySortedMap<K, V> {
32
32
  constructor(entries: [K, V][] = [], opt: KeySortedMapOptions<K> = {}) {
33
33
  this.#comparator = opt.comparator
34
34
  this.map = new Map(entries)
35
- this.#sortedKeys = [...this.map.keys()]
35
+ this.#sortedKeys = Array.from(this.map.keys())
36
36
  this.sortKeys()
37
37
  }
38
38
 
@@ -28,7 +28,7 @@ export class LazyKeySortedMap<K, V> {
28
28
  constructor(entries: [K, V][] = [], opt: LazyKeySortedMapOptions<K> = {}) {
29
29
  this.#comparator = opt.comparator
30
30
  this.map = new Map(entries)
31
- this.maybeSortedKeys = [...this.map.keys()]
31
+ this.maybeSortedKeys = Array.from(this.map.keys())
32
32
  }
33
33
 
34
34
  readonly #comparator: Comparator<K> | undefined
@@ -242,6 +242,47 @@ export function _findKeyByValue<T extends AnyObject>(obj: T, v: ValueOf<T>): key
242
242
  return Object.entries(obj).find(([_, value]) => value === v)?.[0] as keyof T
243
243
  }
244
244
 
245
+ /**
246
+ * Returns the first key of the object, or `undefined` if the object is empty.
247
+ *
248
+ * Performance-optimised: uses `for...in` with an early return to avoid
249
+ * allocating the full `Object.keys(obj)` array. The `Object.hasOwn` filter
250
+ * matches `Object.keys()` semantics (own enumerable string keys only) and
251
+ * satisfies the `guard-for-in` lint rule; cost is a single check before
252
+ * the early return. Iteration order matches `Object.keys()` for plain
253
+ * objects (integer-like keys ascending first, then string keys in
254
+ * insertion order).
255
+ */
256
+ export function _firstKey<T extends AnyObject>(obj: T): keyof T | undefined {
257
+ for (const k in obj) {
258
+ if (Object.hasOwn(obj, k)) return k as keyof T
259
+ }
260
+ return undefined
261
+ }
262
+
263
+ /**
264
+ * Returns the first value of the object, or `undefined` if the object is empty.
265
+ * See `_firstKey` for the iteration-order contract.
266
+ */
267
+ export function _firstValue<T extends AnyObject>(obj: T): ValueOf<T> | undefined {
268
+ for (const k in obj) {
269
+ if (Object.hasOwn(obj, k)) return obj[k] as ValueOf<T>
270
+ }
271
+ return undefined
272
+ }
273
+
274
+ /**
275
+ * Returns the first [key, value] tuple of the object,
276
+ * or `undefined` if the object is empty.
277
+ * See `_firstKey` for the iteration-order contract.
278
+ */
279
+ export function _firstEntry<T extends AnyObject>(obj: T): [keyof T, ValueOf<T>] | undefined {
280
+ for (const k in obj) {
281
+ if (Object.hasOwn(obj, k)) return [k as keyof T, obj[k] as ValueOf<T>]
282
+ }
283
+ return undefined
284
+ }
285
+
245
286
  export function _objectNullValuesToUndefined<T extends AnyObject>(
246
287
  obj: T,
247
288
  opt: MutateOptions = {},
@@ -467,7 +508,7 @@ export function _set<T extends AnyObject>(obj: T, path: PropertyPath, value: any
467
508
  a[c]
468
509
  : // No: create the key. Is the next key a potential array-index?
469
510
  (a[c] =
470
- // oxlint-disable-next-line no-bitwise no-implicit-coercion unicorn/prefer-math-trunc
511
+ // oxlint-disable-next-line no-bitwise, no-implicit-coercion, unicorn/prefer-math-trunc
471
512
  Math.abs(path[i + 1]) >> 0 === +path[i + 1]
472
513
  ? [] // Yes: assign a new array object
473
514
  : {}), // No: assign a new plain object
@@ -34,15 +34,15 @@ export class Set2<T = any> extends Set<T> {
34
34
  // last(): T {
35
35
 
36
36
  toArray(): T[] {
37
- return [...this]
37
+ return Array.from(this)
38
38
  }
39
39
 
40
40
  toJSON(): T[] {
41
- return [...this]
41
+ return Array.from(this)
42
42
  }
43
43
 
44
44
  override toString(): string {
45
- return `Set2(${this.size}) ${JSON.stringify([...this])}`
45
+ return `Set2(${this.size}) ${JSON.stringify(Array.from(this))}`
46
46
  }
47
47
 
48
48
  // todo: consider more helpful .toString() ?
@@ -1,7 +1,7 @@
1
1
  import type { AsyncPredicate } from '../types.js'
2
2
 
3
3
  export async function pFilter<T>(iterable: Iterable<T>, filterFn: AsyncPredicate<T>): Promise<T[]> {
4
- const items = [...iterable]
4
+ const items = Array.from(iterable)
5
5
  const predicates = await Promise.all(items.map((item, i) => filterFn(item, i)))
6
6
  return items.filter((_item, i) => predicates[i])
7
7
  }
@@ -67,7 +67,7 @@ export async function pMap<IN, OUT>(
67
67
  mapper: AbortableAsyncMapper<IN, OUT>,
68
68
  opt: PMapOptions = {},
69
69
  ): Promise<OUT[]> {
70
- const items = [...iterable]
70
+ const items = Array.from(iterable)
71
71
  const itemsLength = items.length
72
72
  if (itemsLength === 0) return [] // short circuit
73
73
 
package/src/semver.ts CHANGED
@@ -136,7 +136,8 @@ class SemverFactory {
136
136
  */
137
137
  sort(items: Semver[], opt: SortOptions = {}): Semver[] {
138
138
  const mod = opt.dir === 'desc' ? -1 : 1
139
- return (opt.mutate ? items : [...items]).sort((a, b) => a.compare(b) * mod)
139
+ const cmp = (a: Semver, b: Semver): number => a.compare(b) * mod
140
+ return opt.mutate ? items.sort(cmp) : items.toSorted(cmp)
140
141
  }
141
142
  }
142
143
 
@@ -131,7 +131,7 @@ export function _stringify(obj: any, opt: StringifyOptions = {}): string {
131
131
 
132
132
  obj = Object.fromEntries(obj)
133
133
  } else if (obj instanceof Set) {
134
- obj = [...obj]
134
+ obj = Array.from(obj)
135
135
  }
136
136
 
137
137
  try {
package/src/typeFest.ts CHANGED
@@ -1,4 +1,4 @@
1
- /* eslint-disable */
1
+ /* oxlint-disable */
2
2
 
3
3
  /**
4
4
  * @file Old inclusion of 3rd party `type-fest` lib inlined here.