@naturalcycles/js-lib 15.73.1 → 15.75.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 (48) hide show
  1. package/dist/array/array.util.js +7 -7
  2. package/dist/array/array2.js +1 -1
  3. package/dist/array/sort.js +2 -1
  4. package/dist/browser/analytics.util.js +2 -2
  5. package/dist/browser/bot.js +2 -2
  6. package/dist/datetime/localDate.js +2 -1
  7. package/dist/datetime/localTime.js +4 -8
  8. package/dist/decorators/asyncMemo.decorator.js +1 -1
  9. package/dist/decorators/memoFnAsync.js +2 -2
  10. package/dist/error/error.util.js +1 -0
  11. package/dist/http/fetcher.js +3 -1
  12. package/dist/http/fetcher.model.d.ts +7 -0
  13. package/dist/iter/iterable2.js +1 -1
  14. package/dist/math/math.util.js +4 -1
  15. package/dist/object/keySortedMap.js +1 -1
  16. package/dist/object/lazyKeySortedMap.js +1 -1
  17. package/dist/object/object.util.js +1 -1
  18. package/dist/object/set2.js +3 -3
  19. package/dist/promise/pFilter.js +1 -1
  20. package/dist/promise/pMap.js +1 -1
  21. package/dist/semver.js +2 -1
  22. package/dist/string/stringify.js +2 -1
  23. package/dist/typeFest.js +1 -1
  24. package/package.json +3 -3
  25. package/src/array/array.util.ts +7 -7
  26. package/src/array/array2.ts +1 -1
  27. package/src/array/sort.ts +2 -1
  28. package/src/browser/analytics.util.ts +2 -2
  29. package/src/browser/bot.ts +2 -2
  30. package/src/datetime/localDate.ts +2 -1
  31. package/src/datetime/localTime.ts +4 -7
  32. package/src/datetime/wallTime.ts +1 -0
  33. package/src/decorators/asyncMemo.decorator.ts +1 -1
  34. package/src/decorators/memoFnAsync.ts +2 -6
  35. package/src/error/error.util.ts +1 -0
  36. package/src/http/fetcher.model.ts +8 -0
  37. package/src/http/fetcher.ts +3 -1
  38. package/src/iter/iterable2.ts +1 -1
  39. package/src/math/math.util.ts +4 -1
  40. package/src/object/keySortedMap.ts +1 -1
  41. package/src/object/lazyKeySortedMap.ts +1 -1
  42. package/src/object/object.util.ts +1 -1
  43. package/src/object/set2.ts +3 -3
  44. package/src/promise/pFilter.ts +1 -1
  45. package/src/promise/pMap.ts +1 -1
  46. package/src/semver.ts +2 -1
  47. package/src/string/stringify.ts +2 -1
  48. package/src/typeFest.ts +1 -1
@@ -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]];
@@ -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,6 +1,8 @@
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';
5
+ // oxlint-disable-next-line import/no-cycle -- intentional cycle
4
6
  import { WallTime } from './wallTime.js';
5
7
  export var ISODayOfWeek;
6
8
  (function (ISODayOfWeek) {
@@ -867,14 +869,8 @@ class LocalTimeFactory {
867
869
  return Intl.supportedValuesOf('timeZone').includes(tz);
868
870
  }
869
871
  sort(items, dir = 'asc', opt = {}) {
870
- const mod = dir === 'desc' ? -1 : 1;
871
- return (opt.mutate ? items : [...items]).sort((a, b) => {
872
- const v1 = a.$date.valueOf();
873
- const v2 = b.$date.valueOf();
874
- if (v1 === v2)
875
- return 0;
876
- return (v1 < v2 ? -1 : 1) * mod;
877
- });
872
+ const cmp = comparators.by((lt) => lt.$date.valueOf(), { dir });
873
+ return opt.mutate ? items.sort(cmp) : items.toSorted(cmp);
878
874
  }
879
875
  minOrUndefined(items) {
880
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,
@@ -1,10 +1,10 @@
1
1
  import { MISS } from '../types.js';
2
- import { jsonMemoSerializer, MapMemoCache } from './memo.util.js';
2
+ import { jsonMemoSerializer } from './memo.util.js';
3
3
  /**
4
4
  * @experimental
5
5
  */
6
6
  export function _memoFnAsync(fn, opt) {
7
- const { logger = console, cacheFactory = () => new MapMemoCache(), cacheKeyFn = jsonMemoSerializer, } = opt;
7
+ const { logger = console, cacheFactory, cacheKeyFn = jsonMemoSerializer } = opt;
8
8
  const cache = cacheFactory();
9
9
  const memoizedFn = async function (...args) {
10
10
  const ctx = this;
@@ -2,6 +2,7 @@ import { isServerSide } from '../env.js';
2
2
  // oxlint-disable-next-line import/no-cycle -- intentional cycle
3
3
  import { _jsonParseIfPossible } from '../string/json.util.js';
4
4
  import { _truncate, _truncateMiddle } from '../string/string.util.js';
5
+ // oxlint-disable-next-line import/no-cycle -- intentional cycle
5
6
  import { _stringify } from '../string/stringify.js';
6
7
  /**
7
8
  * Useful to ensure that error in `catch (err) { ... }`
@@ -610,11 +610,12 @@ export class Fetcher {
610
610
  credentials: cfg.credentials,
611
611
  redirect: cfg.redirect,
612
612
  dispatcher: cfg.dispatcher,
613
+ keepalive: cfg.keepalive,
613
614
  },
614
615
  hooks: {},
615
616
  throwHttpErrors: true,
616
617
  errorData: {},
617
- }, _omit(cfg, ['method', 'credentials', 'headers', 'redirect', 'logger', 'name']));
618
+ }, _omit(cfg, ['method', 'credentials', 'headers', 'redirect', 'logger', 'name', 'keepalive']));
618
619
  norm.init.headers = _mapKeys(norm.init.headers, k => k.toLowerCase());
619
620
  return norm;
620
621
  }
@@ -663,6 +664,7 @@ export class Fetcher {
663
664
  method: opt.method || this.cfg.init.method,
664
665
  credentials: opt.credentials || this.cfg.init.credentials,
665
666
  redirect: opt.redirect || this.cfg.init.redirect || 'follow',
667
+ keepalive: opt.keepalive ?? this.cfg.init.keepalive,
666
668
  }, {
667
669
  headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
668
670
  }),
@@ -205,6 +205,12 @@ export interface FetcherOptions {
205
205
  * 'manual' will not throw, but return !ok response with 3xx status.
206
206
  */
207
207
  redirect?: RequestRedirect;
208
+ /**
209
+ * Default to false.
210
+ * When set to true, the request will not be aborted when the page is unloaded.
211
+ * Useful for sending analytics or tracking events that need to complete even if the user navigates away.
212
+ */
213
+ keepalive?: boolean;
208
214
  headers?: Record<string, any>;
209
215
  responseType?: FetcherResponseType;
210
216
  searchParams?: Record<string, any>;
@@ -287,6 +293,7 @@ export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
287
293
  method: HttpMethod;
288
294
  headers: Record<string, any>;
289
295
  dispatcher?: Dispatcher;
296
+ keepalive?: boolean;
290
297
  };
291
298
  export interface FetcherSuccessResponse<BODY = unknown> {
292
299
  ok: true;
@@ -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
  /**
@@ -392,7 +392,7 @@ export function _set(obj, path, value) {
392
392
  a[c]
393
393
  : // No: create the key. Is the next key a potential array-index?
394
394
  (a[c] =
395
- // oxlint-disable-next-line no-bitwise no-implicit-coercion unicorn/prefer-math-trunc
395
+ // oxlint-disable-next-line no-bitwise, no-implicit-coercion, unicorn/prefer-math-trunc
396
396
  Math.abs(path[i + 1]) >> 0 === +path[i + 1]
397
397
  ? [] // Yes: assign a new array object
398
398
  : {}), // 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();
@@ -1,5 +1,6 @@
1
1
  // oxlint-disable-next-line import/no-cycle -- intentional cycle
2
2
  import { _isBackendErrorResponseObject, _isErrorLike, _isErrorObject } from '../error/error.util.js';
3
+ // oxlint-disable-next-line import/no-cycle -- intentional cycle
3
4
  import { _jsonParseIfPossible } from './json.util.js';
4
5
  import { _safeJsonStringify } from './safeJsonStringify.js';
5
6
  import { _truncateMiddle } from './string.util.js';
@@ -87,7 +88,7 @@ export function _stringify(obj, opt = {}) {
87
88
  obj = Object.fromEntries(obj);
88
89
  }
89
90
  else if (obj instanceof Set) {
90
- obj = [...obj];
91
+ obj = Array.from(obj);
91
92
  }
92
93
  try {
93
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.73.1",
4
+ "version": "15.75.0",
5
5
  "dependencies": {
6
6
  "tslib": "^2"
7
7
  },
@@ -17,10 +17,10 @@
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
- "@naturalcycles/dev-lib": "18.4.2"
23
+ "@naturalcycles/dev-lib": "20.48.0"
24
24
  },
25
25
  "exports": {
26
26
  ".": "./dist/index.js",
@@ -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))
@@ -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,
@@ -16,6 +17,7 @@ import type {
16
17
  import type { LocalDate } from './localDate.js'
17
18
  import { localDate } from './localDate.js'
18
19
  import { _ms } from './time.util.js'
20
+ // oxlint-disable-next-line import/no-cycle -- intentional cycle
19
21
  import { WallTime } from './wallTime.js'
20
22
 
21
23
  export type LocalTimeUnit = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second'
@@ -1016,13 +1018,8 @@ class LocalTimeFactory {
1016
1018
  }
1017
1019
 
1018
1020
  sort(items: LocalTime[], dir: SortDirection = 'asc', opt: MutateOptions = {}): LocalTime[] {
1019
- const mod = dir === 'desc' ? -1 : 1
1020
- return (opt.mutate ? items : [...items]).sort((a, b) => {
1021
- const v1 = a.$date.valueOf()
1022
- const v2 = b.$date.valueOf()
1023
- if (v1 === v2) return 0
1024
- return (v1 < v2 ? -1 : 1) * mod
1025
- })
1021
+ const cmp = comparators.by((lt: LocalTime) => lt.$date.valueOf(), { dir })
1022
+ return opt.mutate ? items.sort(cmp) : items.toSorted(cmp)
1026
1023
  }
1027
1024
 
1028
1025
  minOrUndefined(items: LocalTimeInputNullable[]): LocalTime | undefined {
@@ -1,6 +1,7 @@
1
1
  import type { IsoDate, IsoDateTime } from '../types.js'
2
2
  // oxlint-disable-next-line import/no-cycle -- intentional cycle
3
3
  import { LocalDate } from './localDate.js'
4
+ // oxlint-disable-next-line import/no-cycle -- intentional cycle
4
5
  import type { DateTimeObject, LocalTime } from './localTime.js'
5
6
  import { localTime } from './localTime.js'
6
7
 
@@ -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,
@@ -2,7 +2,7 @@ import type { AnyAsyncFunction, MaybeParameters } from '../types.js'
2
2
  import { MISS } from '../types.js'
3
3
  import type { AsyncMemoOptions } from './asyncMemo.decorator.js'
4
4
  import type { AsyncMemoCache } from './memo.util.js'
5
- import { jsonMemoSerializer, MapMemoCache } from './memo.util.js'
5
+ import { jsonMemoSerializer } from './memo.util.js'
6
6
 
7
7
  export interface MemoizedAsyncFunction {
8
8
  cache: AsyncMemoCache
@@ -15,11 +15,7 @@ export function _memoFnAsync<FN extends AnyAsyncFunction>(
15
15
  fn: FN,
16
16
  opt: AsyncMemoOptions<FN>,
17
17
  ): FN & MemoizedAsyncFunction {
18
- const {
19
- logger = console,
20
- cacheFactory = () => new MapMemoCache(),
21
- cacheKeyFn = jsonMemoSerializer,
22
- } = opt
18
+ const { logger = console, cacheFactory, cacheKeyFn = jsonMemoSerializer } = opt
23
19
 
24
20
  const cache = cacheFactory()
25
21
 
@@ -2,6 +2,7 @@ import { isServerSide } from '../env.js'
2
2
  // oxlint-disable-next-line import/no-cycle -- intentional cycle
3
3
  import { _jsonParseIfPossible } from '../string/json.util.js'
4
4
  import { _truncate, _truncateMiddle } from '../string/string.util.js'
5
+ // oxlint-disable-next-line import/no-cycle -- intentional cycle
5
6
  import { _stringify } from '../string/stringify.js'
6
7
  import type { Class } from '../types.js'
7
8
  import type {
@@ -263,6 +263,13 @@ export interface FetcherOptions {
263
263
  */
264
264
  redirect?: RequestRedirect
265
265
 
266
+ /**
267
+ * Default to false.
268
+ * When set to true, the request will not be aborted when the page is unloaded.
269
+ * Useful for sending analytics or tracking events that need to complete even if the user navigates away.
270
+ */
271
+ keepalive?: boolean
272
+
266
273
  // Removing RequestInit from options to simplify FetcherOptions interface.
267
274
  // Will instead only add hand-picked useful options, such as `credentials`.
268
275
  // init?: Partial<RequestInitNormalized>
@@ -361,6 +368,7 @@ export type RequestInitNormalized = Omit<RequestInit, 'method' | 'headers'> & {
361
368
  method: HttpMethod
362
369
  headers: Record<string, any>
363
370
  dispatcher?: Dispatcher
371
+ keepalive?: boolean
364
372
  }
365
373
 
366
374
  export interface FetcherSuccessResponse<BODY = unknown> {
@@ -744,12 +744,13 @@ export class Fetcher {
744
744
  credentials: cfg.credentials,
745
745
  redirect: cfg.redirect,
746
746
  dispatcher: cfg.dispatcher,
747
+ keepalive: cfg.keepalive,
747
748
  },
748
749
  hooks: {},
749
750
  throwHttpErrors: true,
750
751
  errorData: {},
751
752
  },
752
- _omit(cfg, ['method', 'credentials', 'headers', 'redirect', 'logger', 'name']),
753
+ _omit(cfg, ['method', 'credentials', 'headers', 'redirect', 'logger', 'name', 'keepalive']),
753
754
  )
754
755
 
755
756
  norm.init.headers = _mapKeys(norm.init.headers, k => k.toLowerCase())
@@ -804,6 +805,7 @@ export class Fetcher {
804
805
  method: opt.method || this.cfg.init.method,
805
806
  credentials: opt.credentials || this.cfg.init.credentials,
806
807
  redirect: opt.redirect || this.cfg.init.redirect || 'follow',
808
+ keepalive: opt.keepalive ?? this.cfg.init.keepalive,
807
809
  },
808
810
  {
809
811
  headers: _mapKeys(opt.headers || {}, k => k.toLowerCase()),
@@ -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
@@ -467,7 +467,7 @@ export function _set<T extends AnyObject>(obj: T, path: PropertyPath, value: any
467
467
  a[c]
468
468
  : // No: create the key. Is the next key a potential array-index?
469
469
  (a[c] =
470
- // oxlint-disable-next-line no-bitwise no-implicit-coercion unicorn/prefer-math-trunc
470
+ // oxlint-disable-next-line no-bitwise, no-implicit-coercion, unicorn/prefer-math-trunc
471
471
  Math.abs(path[i + 1]) >> 0 === +path[i + 1]
472
472
  ? [] // Yes: assign a new array object
473
473
  : {}), // 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
 
@@ -2,6 +2,7 @@
2
2
  import { _isBackendErrorResponseObject, _isErrorLike, _isErrorObject } from '../error/error.util.js'
3
3
  import type { ErrorLike } from '../error/index.js'
4
4
  import type { Reviver } from '../types.js'
5
+ // oxlint-disable-next-line import/no-cycle -- intentional cycle
5
6
  import { _jsonParseIfPossible } from './json.util.js'
6
7
  import { _safeJsonStringify } from './safeJsonStringify.js'
7
8
  import { _truncateMiddle } from './string.util.js'
@@ -130,7 +131,7 @@ export function _stringify(obj: any, opt: StringifyOptions = {}): string {
130
131
 
131
132
  obj = Object.fromEntries(obj)
132
133
  } else if (obj instanceof Set) {
133
- obj = [...obj]
134
+ obj = Array.from(obj)
134
135
  }
135
136
 
136
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.