@naturalcycles/js-lib 15.99.0 → 15.101.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.
@@ -369,8 +369,7 @@ export function _shuffle(array, opt = {}) {
369
369
  return a;
370
370
  }
371
371
  export function _firstLast(array) {
372
- if (!array.length)
373
- throw new Error('_firstLast called on empty array');
372
+ _assert(array.length, '_firstLast called on empty array');
374
373
  return [array[0], array[array.length - 1]];
375
374
  }
376
375
  export function _firstLastOrUndefined(array) {
@@ -383,8 +382,7 @@ export function _firstLastOrUndefined(array) {
383
382
  * Throws if array is empty.
384
383
  */
385
384
  export function _last(array) {
386
- if (!array.length)
387
- throw new Error('_last called on empty array');
385
+ _assert(array.length, '_last called on empty array');
388
386
  return array[array.length - 1];
389
387
  }
390
388
  /**
@@ -398,8 +396,7 @@ export function _lastOrUndefined(array) {
398
396
  * Throws if array is empty.
399
397
  */
400
398
  export function _first(array) {
401
- if (!array.length)
402
- throw new Error('_first called on empty array');
399
+ _assert(array.length, '_first called on empty array');
403
400
  return array[0];
404
401
  }
405
402
  /**
@@ -482,8 +479,7 @@ export function _minBy(array, mapper) {
482
479
  return min;
483
480
  }
484
481
  export function _minMax(array) {
485
- if (!array.length)
486
- throw new Error('_minMax called on empty array');
482
+ _assert(array.length, '_minMax called on empty array');
487
483
  const result = _minMaxOrUndefined(array);
488
484
  _assert(result !== undefined, '_minBy returned undefined');
489
485
  return result;
@@ -510,8 +506,7 @@ export function _minMaxOrUndefined(array) {
510
506
  return [min, max];
511
507
  }
512
508
  export function _minMaxBy(array, mapper) {
513
- if (!array.length)
514
- throw new Error('_minMaxBy called on empty array');
509
+ _assert(array.length, '_minMaxBy called on empty array');
515
510
  const result = _minMaxByOrUndefined(array, mapper);
516
511
  _assert(result !== undefined, '_minMaxBy returned undefined');
517
512
  return result;
@@ -1,3 +1,4 @@
1
+ import { _assert } from '../error/assert.js';
1
2
  import { _shuffle } from './array.util.js';
2
3
  /**
3
4
  * Better Array.
@@ -16,8 +17,7 @@ export class Array2 extends Array {
16
17
  return this[0];
17
18
  }
18
19
  first() {
19
- if (!this.length)
20
- throw new Error('Array.first called on empty array');
20
+ _assert(this.length, 'Array.first called on empty array');
21
21
  return this[0];
22
22
  }
23
23
  lastOrUndefined() {
@@ -25,8 +25,7 @@ export class Array2 extends Array {
25
25
  }
26
26
  last() {
27
27
  const { length } = this;
28
- if (!length)
29
- throw new Error('Array.last called on empty array');
28
+ _assert(length, 'Array.last called on empty array');
30
29
  return this[length - 1];
31
30
  }
32
31
  uniq() {
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from './datetime/localTime.js';
1
2
  import { consoleLogger } from './log/commonLogger.js';
2
3
  class AsyncManagerImpl {
3
4
  logger = consoleLogger;
@@ -24,7 +25,7 @@ class AsyncManagerImpl {
24
25
  if (!size)
25
26
  return;
26
27
  const { logger } = this;
27
- const started = Date.now();
28
+ const started = nowUnixMillis();
28
29
  if (timeout) {
29
30
  const result = await Promise.race([
30
31
  Promise.allSettled(this.pendingOps),
@@ -38,7 +39,7 @@ class AsyncManagerImpl {
38
39
  else {
39
40
  await Promise.allSettled(this.pendingOps);
40
41
  }
41
- logger.log(`AsyncManager.allDone for ${size} op(s) in ${Date.now() - started} ms`);
42
+ logger.log(`AsyncManager.allDone for ${size} op(s) in ${nowUnixMillis() - started} ms`);
42
43
  }
43
44
  reset() {
44
45
  this.pendingOps.clear();
@@ -1,3 +1,4 @@
1
+ import { newDate, nowUnixMillis } from '../datetime/localTime.js';
1
2
  import { isServerSide } from '../env.js';
2
3
  import { loadScript } from './script.util.js';
3
4
  /**
@@ -10,7 +11,7 @@ export async function loadGTag(gtagId, enabled = true) {
10
11
  globalThis.gtag ||= function gtag() {
11
12
  globalThis.dataLayer.push(arguments);
12
13
  };
13
- globalThis.gtag('js', new Date());
14
+ globalThis.gtag('js', newDate());
14
15
  globalThis.gtag('config', gtagId);
15
16
  if (!enabled)
16
17
  return;
@@ -21,7 +22,7 @@ export async function loadGTM(gtmId, enabled = true) {
21
22
  return;
22
23
  globalThis.dataLayer ||= [];
23
24
  globalThis.dataLayer.push({
24
- 'gtm.start': Date.now(),
25
+ 'gtm.start': nowUnixMillis(),
25
26
  event: 'gtm.js',
26
27
  });
27
28
  if (!enabled)
@@ -257,10 +257,14 @@ declare class LocalTimeFactory {
257
257
  /**
258
258
  Convenience function to return current Unix timestamp in seconds.
259
259
  Like Date.now(), but in seconds.
260
+
261
+ Import nowUnix function individually in places where importing localTime is too heavy.
260
262
  */
261
263
  nowUnix(): UnixTimestamp;
262
264
  /**
263
265
  Convenience function that retuns the same as Date.now(), but with proper type of UnixTimestampMillis.
266
+
267
+ Import nowUnixMillis function individually in places where importing localTime is too heavy.
264
268
  */
265
269
  nowUnixMillis(): UnixTimestampMillis;
266
270
  /**
@@ -331,4 +335,21 @@ interface LocalTimeFn extends LocalTimeFactory {
331
335
  (d: LocalTimeInput): LocalTime;
332
336
  }
333
337
  export declare const localTime: LocalTimeFn;
338
+ /**
339
+ * Can be used instead of `oxlint-disable ...` in places where Date is intentionally needed
340
+ */
341
+ export declare function newDate(): Date;
342
+ /**
343
+ Convenience function to return current Unix timestamp in seconds.
344
+ Like Date.now(), but in seconds.
345
+
346
+ Import nowUnix function individually in places where importing localTime is too heavy.
347
+ */
348
+ export declare function nowUnix(): UnixTimestamp;
349
+ /**
350
+ Convenience function that retuns the same as Date.now(), but with proper type of UnixTimestampMillis.
351
+
352
+ Import nowUnixMillis function individually in places where importing localTime is too heavy.
353
+ */
354
+ export declare function nowUnixMillis(): UnixTimestampMillis;
334
355
  export {};
@@ -676,15 +676,19 @@ class LocalTimeFactory {
676
676
  /**
677
677
  Convenience function to return current Unix timestamp in seconds.
678
678
  Like Date.now(), but in seconds.
679
+
680
+ Import nowUnix function individually in places where importing localTime is too heavy.
679
681
  */
680
682
  nowUnix() {
681
- return Math.floor(Date.now() / 1000);
683
+ return nowUnix();
682
684
  }
683
685
  /**
684
686
  Convenience function that retuns the same as Date.now(), but with proper type of UnixTimestampMillis.
687
+
688
+ Import nowUnixMillis function individually in places where importing localTime is too heavy.
685
689
  */
686
690
  nowUnixMillis() {
687
- return Date.now();
691
+ return nowUnixMillis();
688
692
  }
689
693
  /**
690
694
  * Create LocalTime from LocalTimeInput.
@@ -1032,3 +1036,26 @@ export const localTime = localTimeFactory.fromInput.bind(localTimeFactory);
1032
1036
  // The line below is the blackest of black magic I have ever written in 2024.
1033
1037
  // And probably 2023 as well.
1034
1038
  Object.setPrototypeOf(localTime, localTimeFactory);
1039
+ /**
1040
+ * Can be used instead of `oxlint-disable ...` in places where Date is intentionally needed
1041
+ */
1042
+ export function newDate() {
1043
+ return new Date();
1044
+ }
1045
+ /**
1046
+ Convenience function to return current Unix timestamp in seconds.
1047
+ Like Date.now(), but in seconds.
1048
+
1049
+ Import nowUnix function individually in places where importing localTime is too heavy.
1050
+ */
1051
+ export function nowUnix() {
1052
+ return Math.floor(Date.now() / 1000);
1053
+ }
1054
+ /**
1055
+ Convenience function that retuns the same as Date.now(), but with proper type of UnixTimestampMillis.
1056
+
1057
+ Import nowUnixMillis function individually in places where importing localTime is too heavy.
1058
+ */
1059
+ export function nowUnixMillis() {
1060
+ return Date.now();
1061
+ }
@@ -8,6 +8,7 @@
8
8
  * @experimental
9
9
  */
10
10
  export function _blockTimer(name) {
11
+ // oxlint-disable-next-line no-restricted-globals -- localTime imports this module, so it can't import localTime back
11
12
  const started = Date.now();
12
13
  return {
13
14
  [Symbol.dispose]() {
@@ -18,7 +19,9 @@ export function _blockTimer(name) {
18
19
  /**
19
20
  * Returns time passed since `from` until `until` (default to Date.now())
20
21
  */
21
- export function _since(from, until = Date.now()) {
22
+ export function _since(from,
23
+ // oxlint-disable-next-line no-restricted-globals -- localTime imports this module, so it can't import localTime back
24
+ until = Date.now()) {
22
25
  return _ms(until - from);
23
26
  }
24
27
  /**
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js';
1
2
  import { _getTargetMethodSignature } from './decorator.util.js';
2
3
  /**
3
4
  * @example
@@ -19,7 +20,7 @@ export function _createPromiseDecorator(cfg, decoratorParams = {}) {
19
20
  const methodSignature = _getTargetMethodSignature(target, key);
20
21
  pd.value = async function (...args) {
21
22
  // console.log(`@${cfg.decoratorName} called inside function`)
22
- const started = Date.now();
23
+ const started = nowUnixMillis();
23
24
  try {
24
25
  // Before function
25
26
  // console.log(`@${cfg.decoratorName} Before`)
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js';
1
2
  import { _anyToError } from '../error/error.util.js';
2
3
  import { pDefer } from '../promise/pDefer.js';
3
4
  export function _debounce(func, wait, opt = {}) {
@@ -31,7 +32,7 @@ export function _debounce(func, wait, opt = {}) {
31
32
  return leading ? invokeFunc(time) : result;
32
33
  }
33
34
  function timerExpired() {
34
- const time = Date.now();
35
+ const time = nowUnixMillis();
35
36
  if (shouldInvoke(time, state)) {
36
37
  trailingEdge(time);
37
38
  return;
@@ -58,13 +59,13 @@ export function _debounce(func, wait, opt = {}) {
58
59
  lastArgs = lastThis = timerId = undefined;
59
60
  }
60
61
  function flush() {
61
- return timerId === undefined ? result : trailingEdge(Date.now());
62
+ return timerId === undefined ? result : trailingEdge(nowUnixMillis());
62
63
  }
63
64
  function pending() {
64
65
  return timerId !== undefined;
65
66
  }
66
67
  function debounced(...args) {
67
- const time = Date.now();
68
+ const time = nowUnixMillis();
68
69
  const isInvoking = shouldInvoke(time, state);
69
70
  lastArgs = args;
70
71
  lastThis = this;
@@ -151,7 +152,7 @@ export function _asyncDebounce(func, wait, opt = {}) {
151
152
  return d;
152
153
  }
153
154
  function timerExpired() {
154
- const time = Date.now();
155
+ const time = nowUnixMillis();
155
156
  if (shouldInvoke(time, state)) {
156
157
  void trailingEdge(time);
157
158
  return;
@@ -195,14 +196,14 @@ export function _asyncDebounce(func, wait, opt = {}) {
195
196
  }
196
197
  const d = deferred;
197
198
  cancelTimer(timerId);
198
- await trailingEdge(Date.now());
199
+ await trailingEdge(nowUnixMillis());
199
200
  return d;
200
201
  }
201
202
  function pending() {
202
203
  return timerId !== undefined;
203
204
  }
204
205
  async function debounced(...args) {
205
- const time = Date.now();
206
+ const time = nowUnixMillis();
206
207
  const isInvoking = shouldInvoke(time, state);
207
208
  lastArgs = args;
208
209
  lastThis = this;
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js';
1
2
  import { _ms } from '../datetime/time.util.js';
2
3
  import { _assert } from '../error/assert.js';
3
4
  import { consoleLogger } from '../log/commonLogger.js';
@@ -35,7 +36,7 @@ export function _LogMethod(opt = {}) {
35
36
  const sma = avg ? new SimpleMovingAverage(avg) : undefined;
36
37
  let count = 0;
37
38
  descriptor.value = function (...args) {
38
- const started = Date.now();
39
+ const started = nowUnixMillis();
39
40
  const ctx = this;
40
41
  // e.g `NameOfYourClass.methodName`
41
42
  // or `NameOfYourClass(instanceId).methodName`
@@ -72,7 +73,7 @@ export function _LogMethod(opt = {}) {
72
73
  }
73
74
  // oxlint-disable-next-line max-params
74
75
  function logFinished(logger, callSignature, started, sma, logResultFn, res, err) {
75
- const millis = Date.now() - started;
76
+ const millis = nowUnixMillis() - started;
76
77
  const t = ['<<', callSignature, 'took', _ms(millis)];
77
78
  if (sma) {
78
79
  t.push(`(avg ${_ms(sma.pushGetAvg(millis))})`);
package/dist/enum.util.js CHANGED
@@ -234,6 +234,7 @@ export function _numberEnumKeyOrUndefined(en, v) {
234
234
  export function _numberEnumKey(en, v) {
235
235
  const key = en[v];
236
236
  // This prevents passing a Key (not a Value) of enum here, which returns unexpected result (number, not string)
237
+ // oxlint-disable-next-line unicorn/prefer-type-error
237
238
  if (typeof key !== 'string')
238
239
  throw new Error(`_numberEnumKey not found for: ${v}`);
239
240
  return key;
@@ -1,3 +1,5 @@
1
+ // `_assert` and friends are implemented by throwing in an if
2
+ // oxlint-disable nc/prefer-assert
1
3
  import { _deepEquals } from '../object/deepEquals.js';
2
4
  import { _stringify } from '../string/stringify.js';
3
5
  import { _isBackendErrorResponseObject, _isErrorObject, AssertionError } from './error.util.js';
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js';
1
2
  import { _since } from '../datetime/time.util.js';
2
3
  import { consoleLogger } from '../log/commonLogger.js';
3
4
  import { _anyToError } from './error.util.js';
@@ -13,7 +14,7 @@ export function _tryCatch(fn, opt = {}) {
13
14
  const { onError, logError = true, logSuccess = false, logger = consoleLogger } = opt;
14
15
  const fname = fn.name || 'anonymous';
15
16
  return async function (...args) {
16
- const started = Date.now();
17
+ const started = nowUnixMillis();
17
18
  try {
18
19
  const r = await fn.apply(this, args);
19
20
  if (logSuccess) {
@@ -2,9 +2,10 @@
2
2
  /// <reference lib="dom" preserve="true" />
3
3
  /// <reference lib="dom.iterable" preserve="true" />
4
4
  import { abortSignalAnyOrUndefined, abortSignalTimeoutOrUndefined } from '../abort.js';
5
+ import { nowUnixMillis } from '../datetime/localTime.js';
5
6
  import { _ms, _since } from '../datetime/time.util.js';
6
7
  import { isServerSide } from '../env.js';
7
- import { _assertErrorClassOrRethrow, _assertIsError } from '../error/assert.js';
8
+ import { _assert, _assertErrorClassOrRethrow, _assertIsError } from '../error/assert.js';
8
9
  import { _anyToError, _anyToErrorObject, _errorDataAppend, _errorLikeToErrorObject, HttpRequestError, UnexpectedPassError, } from '../error/error.util.js';
9
10
  import { consoleLogger } from '../log/commonLogger.js';
10
11
  import { _clamp } from '../number/number.util.js';
@@ -184,7 +185,7 @@ export class Fetcher {
184
185
  requestSignature: res.signature,
185
186
  requestName: res.req.requestName,
186
187
  fetcherName: this.cfg.name,
187
- requestDuration: Date.now() - res.req.started,
188
+ requestDuration: nowUnixMillis() - res.req.started,
188
189
  });
189
190
  }
190
191
  const { data } = res.body;
@@ -314,7 +315,7 @@ export class Fetcher {
314
315
  signature,
315
316
  };
316
317
  while (!res.retryStatus.retryStopped) {
317
- req.started = Date.now();
318
+ req.started = nowUnixMillis();
318
319
  res.body = undefined;
319
320
  req.init.signal = abortSignalAnyOrUndefined([
320
321
  abortSignalTimeoutOrUndefined(timeoutMillis),
@@ -454,10 +455,8 @@ export class Fetcher {
454
455
  }
455
456
  else if (responseType === 'readableStream') {
456
457
  res.body = res.fetchResponse.body;
457
- if (res.body === null) {
458
- // Error is to be handled upstream
459
- throw new Error('fetchResponse.body is null');
460
- }
458
+ // Error is to be handled upstream
459
+ _assert(res.body !== null, 'fetchResponse.body is null');
461
460
  }
462
461
  res.retryStatus.retryStopped = true;
463
462
  if (req.logResponse) {
@@ -534,7 +533,7 @@ export class Fetcher {
534
533
  requestSignature: res.signature,
535
534
  requestName: res.req.requestName,
536
535
  fetcherName: this.cfg.name,
537
- requestDuration: Date.now() - res.req.started,
536
+ requestDuration: nowUnixMillis() - res.req.started,
538
537
  }), {
539
538
  cause,
540
539
  });
@@ -611,16 +610,17 @@ export class Fetcher {
611
610
  if (retryAfterNumber > 10 ** 9) {
612
611
  // Value is too large to be a "seconds from now" delay,
613
612
  // treat it as a UnixTimestamp instead (e.g GitHub sends `x-ratelimit-reset` like that)
614
- timeout = retryAfterNumber * 1000 - Date.now();
613
+ timeout = retryAfterNumber * 1000 - nowUnixMillis();
615
614
  }
616
615
  else {
617
616
  timeout = retryAfterNumber * 1000;
618
617
  }
619
618
  }
620
619
  else {
620
+ // oxlint-disable-next-line no-restricted-globals -- parses an HTTP-date, which localTime doesn't support
621
621
  const date = new Date(retryAfterStr);
622
622
  if (!Number.isNaN(date.getTime())) {
623
- timeout = date.getTime() - Date.now();
623
+ timeout = date.getTime() - nowUnixMillis();
624
624
  }
625
625
  }
626
626
  this.cfg.logger.log(`retry-after: ${retryAfterStr}`);
@@ -780,7 +780,7 @@ export class Fetcher {
780
780
  'fetchFn',
781
781
  'overrideFetchFn',
782
782
  ]),
783
- started: Date.now(),
783
+ started: nowUnixMillis(),
784
784
  ..._omit(opt, ['method', 'headers', 'credentials']),
785
785
  inputUrl: opt.url || '',
786
786
  fullUrl: opt.url || '',
@@ -115,6 +115,7 @@ export function _deepJsonEquals(a, b) {
115
115
  a = undefined;
116
116
  }
117
117
  else if (a && typeof a === 'object') {
118
+ // oxlint-disable-next-line no-restricted-globals -- Date instances are compared by value
118
119
  if (a instanceof Date) {
119
120
  ;
120
121
  a = a.valueOf();
@@ -132,6 +133,7 @@ export function _deepJsonEquals(a, b) {
132
133
  b = undefined;
133
134
  }
134
135
  else if (b && typeof b === 'object') {
136
+ // oxlint-disable-next-line no-restricted-globals -- Date instances are compared by value
135
137
  if (b instanceof Date) {
136
138
  ;
137
139
  b = b.valueOf();
@@ -1,3 +1,4 @@
1
+ import { _assert } from '../error/assert.js';
1
2
  /**
2
3
  * Like Set, but serializes to JSON as an array.
3
4
  *
@@ -20,8 +21,7 @@ export class Set2 extends Set {
20
21
  return this;
21
22
  }
22
23
  first() {
23
- if (!this.size)
24
- throw new Error('Set.first called on empty set');
24
+ _assert(this.size, 'Set.first called on empty set');
25
25
  return this.firstOrUndefined();
26
26
  }
27
27
  firstOrUndefined() {
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js';
1
2
  import { ErrorMode } from '../error/errorMode.js';
2
3
  import { createCommonLoggerAtLevel } from '../log/commonLogger.js';
3
4
  import { pDefer } from './pDefer.js';
@@ -34,7 +35,7 @@ export class PGradualQueue {
34
35
  // Fast-path: warmup complete
35
36
  if (this.warmupComplete)
36
37
  return this.cfg.concurrency;
37
- const elapsed = Date.now() - this.startTime;
38
+ const elapsed = nowUnixMillis() - this.startTime;
38
39
  if (elapsed >= this.warmupMs) {
39
40
  this.warmupComplete = true;
40
41
  this.logger.debug('warmup complete');
@@ -51,7 +52,7 @@ export class PGradualQueue {
51
52
  async push(fn) {
52
53
  // Initialize start time on first push
53
54
  if (this.startTime === 0) {
54
- this.startTime = Date.now();
55
+ this.startTime = nowUnixMillis();
55
56
  }
56
57
  const job = { fn, defer: pDefer() };
57
58
  const concurrency = this.getCurrentConcurrency();
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js';
1
2
  import { _since } from '../datetime/time.util.js';
2
3
  import { _errorDataAppend } from '../error/error.util.js';
3
4
  import { consoleLogger } from '../log/commonLogger.js';
@@ -26,7 +27,7 @@ export async function pRetry(fn, opt = {}) {
26
27
  let delay = initialDelay;
27
28
  let attempt = 0;
28
29
  while (true) {
29
- const started = Date.now();
30
+ const started = nowUnixMillis();
30
31
  try {
31
32
  attempt++;
32
33
  if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
3
  "type": "module",
4
- "version": "15.99.0",
4
+ "version": "15.101.0",
5
5
  "dependencies": {
6
6
  "tslib": "^2"
7
7
  },
@@ -14,7 +14,7 @@
14
14
  }
15
15
  },
16
16
  "devDependencies": {
17
- "@naturalcycles/dev-lib": "0.0.0",
17
+ "@naturalcycles/dev-lib": "20.71.0",
18
18
  "@types/node": "^26",
19
19
  "@types/semver": "^7",
20
20
  "typescript": "^7",
@@ -435,7 +435,7 @@ export function _shuffle<T>(array: T[], opt: MutateOptions = {}): T[] {
435
435
  }
436
436
 
437
437
  export function _firstLast<T>(array: readonly T[]): [first: T, last: T] {
438
- if (!array.length) throw new Error('_firstLast called on empty array')
438
+ _assert(array.length, '_firstLast called on empty array')
439
439
  return [array[0]!, array[array.length - 1]!]
440
440
  }
441
441
 
@@ -449,7 +449,7 @@ export function _firstLastOrUndefined<T>(array: readonly T[]): [first: T, last:
449
449
  * Throws if array is empty.
450
450
  */
451
451
  export function _last<T>(array: readonly T[]): T {
452
- if (!array.length) throw new Error('_last called on empty array')
452
+ _assert(array.length, '_last called on empty array')
453
453
  return array[array.length - 1]!
454
454
  }
455
455
 
@@ -465,7 +465,7 @@ export function _lastOrUndefined<T>(array: readonly T[]): T | undefined {
465
465
  * Throws if array is empty.
466
466
  */
467
467
  export function _first<T>(array: readonly T[]): T {
468
- if (!array.length) throw new Error('_first called on empty array')
468
+ _assert(array.length, '_first called on empty array')
469
469
  return array[0]!
470
470
  }
471
471
 
@@ -561,7 +561,7 @@ export function _minBy<T>(array: readonly T[], mapper: Mapper<T, number | string
561
561
  }
562
562
 
563
563
  export function _minMax<T>(array: readonly T[]): [min: NonNullable<T>, max: NonNullable<T>] {
564
- if (!array.length) throw new Error('_minMax called on empty array')
564
+ _assert(array.length, '_minMax called on empty array')
565
565
  const result = _minMaxOrUndefined(array)
566
566
  _assert(result !== undefined, '_minBy returned undefined')
567
567
  return result
@@ -592,7 +592,7 @@ export function _minMaxBy<T>(
592
592
  array: readonly T[],
593
593
  mapper: Mapper<T, number | string | undefined>,
594
594
  ): [min: NonNullable<T>, max: NonNullable<T>] {
595
- if (!array.length) throw new Error('_minMaxBy called on empty array')
595
+ _assert(array.length, '_minMaxBy called on empty array')
596
596
  const result = _minMaxByOrUndefined(array, mapper)
597
597
  _assert(result !== undefined, '_minMaxBy returned undefined')
598
598
  return result
@@ -1,3 +1,4 @@
1
+ import { _assert } from '../error/assert.js'
1
2
  import { _shuffle } from './array.util.js'
2
3
 
3
4
  /**
@@ -20,7 +21,7 @@ export class Array2<T> extends Array<T> {
20
21
  }
21
22
 
22
23
  first(): T {
23
- if (!this.length) throw new Error('Array.first called on empty array')
24
+ _assert(this.length, 'Array.first called on empty array')
24
25
  return this[0]!
25
26
  }
26
27
 
@@ -30,7 +31,7 @@ export class Array2<T> extends Array<T> {
30
31
 
31
32
  last(): T {
32
33
  const { length } = this
33
- if (!length) throw new Error('Array.last called on empty array')
34
+ _assert(length, 'Array.last called on empty array')
34
35
  return this[length - 1]!
35
36
  }
36
37
 
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from './datetime/localTime.js'
1
2
  import { consoleLogger } from './log/commonLogger.js'
2
3
  import type { CommonLogger } from './log/commonLogger.js'
3
4
  import type { NumberOfMilliseconds } from './types.js'
@@ -32,7 +33,7 @@ class AsyncManagerImpl {
32
33
  if (!size) return
33
34
  const { logger } = this
34
35
 
35
- const started = Date.now()
36
+ const started = nowUnixMillis()
36
37
  if (timeout) {
37
38
  const result = await Promise.race([
38
39
  Promise.allSettled(this.pendingOps),
@@ -47,7 +48,7 @@ class AsyncManagerImpl {
47
48
  } else {
48
49
  await Promise.allSettled(this.pendingOps)
49
50
  }
50
- logger.log(`AsyncManager.allDone for ${size} op(s) in ${Date.now() - started} ms`)
51
+ logger.log(`AsyncManager.allDone for ${size} op(s) in ${nowUnixMillis() - started} ms`)
51
52
  }
52
53
 
53
54
  reset(): void {
@@ -1,3 +1,4 @@
1
+ import { newDate, nowUnixMillis } from '../datetime/localTime.js'
1
2
  import { isServerSide } from '../env.js'
2
3
  import { loadScript } from './script.util.js'
3
4
 
@@ -17,7 +18,7 @@ export async function loadGTag(gtagId: string, enabled = true): Promise<void> {
17
18
  globalThis.gtag ||= function gtag() {
18
19
  globalThis.dataLayer.push(arguments)
19
20
  }
20
- globalThis.gtag('js', new Date())
21
+ globalThis.gtag('js', newDate())
21
22
  globalThis.gtag('config', gtagId)
22
23
 
23
24
  if (!enabled) return
@@ -30,7 +31,7 @@ export async function loadGTM(gtmId: string, enabled = true): Promise<void> {
30
31
 
31
32
  globalThis.dataLayer ||= []
32
33
  globalThis.dataLayer.push({
33
- 'gtm.start': Date.now(),
34
+ 'gtm.start': nowUnixMillis(),
34
35
  event: 'gtm.js',
35
36
  })
36
37
 
@@ -14,6 +14,8 @@ import type {
14
14
  import type { DateObject, ISODayOfWeek, LocalTime } from './localTime.js'
15
15
  import { localTime, VALID_DAYS_OF_WEEK } from './localTime.js'
16
16
 
17
+ // oxlint-disable no-restricted-globals -- LocalDate wraps the native Date
18
+
17
19
  export type LocalDateUnit = LocalDateUnitStrict | 'week'
18
20
  export type LocalDateUnitStrict = 'year' | 'month' | 'day'
19
21
 
@@ -21,6 +21,8 @@ import { _ms } from './time.util.js'
21
21
  // oxlint-disable-next-line import/no-cycle -- intentional cycle
22
22
  import { WallTime } from './wallTime.js'
23
23
 
24
+ // oxlint-disable no-restricted-globals -- LocalTime wraps the native Date
25
+
24
26
  export type LocalTimeUnit = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second'
25
27
 
26
28
  export enum ISODayOfWeek {
@@ -814,16 +816,20 @@ class LocalTimeFactory {
814
816
  /**
815
817
  Convenience function to return current Unix timestamp in seconds.
816
818
  Like Date.now(), but in seconds.
819
+
820
+ Import nowUnix function individually in places where importing localTime is too heavy.
817
821
  */
818
822
  nowUnix(): UnixTimestamp {
819
- return Math.floor(Date.now() / 1000) as UnixTimestamp
823
+ return nowUnix()
820
824
  }
821
825
 
822
826
  /**
823
827
  Convenience function that retuns the same as Date.now(), but with proper type of UnixTimestampMillis.
828
+
829
+ Import nowUnixMillis function individually in places where importing localTime is too heavy.
824
830
  */
825
831
  nowUnixMillis(): UnixTimestampMillis {
826
- return Date.now() as UnixTimestampMillis
832
+ return nowUnixMillis()
827
833
  }
828
834
 
829
835
  /**
@@ -1213,3 +1219,29 @@ export const localTime = localTimeFactory.fromInput.bind(localTimeFactory) as Lo
1213
1219
  // The line below is the blackest of black magic I have ever written in 2024.
1214
1220
  // And probably 2023 as well.
1215
1221
  Object.setPrototypeOf(localTime, localTimeFactory)
1222
+
1223
+ /**
1224
+ * Can be used instead of `oxlint-disable ...` in places where Date is intentionally needed
1225
+ */
1226
+ export function newDate(): Date {
1227
+ return new Date()
1228
+ }
1229
+
1230
+ /**
1231
+ Convenience function to return current Unix timestamp in seconds.
1232
+ Like Date.now(), but in seconds.
1233
+
1234
+ Import nowUnix function individually in places where importing localTime is too heavy.
1235
+ */
1236
+ export function nowUnix(): UnixTimestamp {
1237
+ return Math.floor(Date.now() / 1000) as UnixTimestamp
1238
+ }
1239
+
1240
+ /**
1241
+ Convenience function that retuns the same as Date.now(), but with proper type of UnixTimestampMillis.
1242
+
1243
+ Import nowUnixMillis function individually in places where importing localTime is too heavy.
1244
+ */
1245
+ export function nowUnixMillis(): UnixTimestampMillis {
1246
+ return Date.now() as UnixTimestampMillis
1247
+ }
@@ -10,6 +10,7 @@ import type { NumberOfMilliseconds, UnixTimestampMillis } from '../types.js'
10
10
  * @experimental
11
11
  */
12
12
  export function _blockTimer(name?: string): Disposable {
13
+ // oxlint-disable-next-line no-restricted-globals -- localTime imports this module, so it can't import localTime back
13
14
  const started = Date.now() as UnixTimestampMillis
14
15
  return {
15
16
  [Symbol.dispose](): void {
@@ -23,6 +24,7 @@ export function _blockTimer(name?: string): Disposable {
23
24
  */
24
25
  export function _since(
25
26
  from: UnixTimestampMillis,
27
+ // oxlint-disable-next-line no-restricted-globals -- localTime imports this module, so it can't import localTime back
26
28
  until: UnixTimestampMillis = Date.now() as UnixTimestampMillis,
27
29
  ): string {
28
30
  return _ms(until - from)
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js'
1
2
  import { _getTargetMethodSignature } from './decorator.util.js'
2
3
 
3
4
  export interface PromiseDecoratorCfg<RES = any, PARAMS = any> {
@@ -72,7 +73,7 @@ export function _createPromiseDecorator<RES = any, PARAMS = any>(
72
73
 
73
74
  pd.value = async function (this: typeof target, ...args: any[]): Promise<any> {
74
75
  // console.log(`@${cfg.decoratorName} called inside function`)
75
- const started = Date.now()
76
+ const started = nowUnixMillis()
76
77
 
77
78
  try {
78
79
  // Before function
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js'
1
2
  import { _anyToError } from '../error/error.util.js'
2
3
  import { pDefer } from '../promise/pDefer.js'
3
4
  import type { DeferredPromise } from '../promise/pDefer.js'
@@ -43,7 +44,7 @@ export function _debounce<T extends AnyFunction>(
43
44
  }
44
45
 
45
46
  function timerExpired(): void {
46
- const time = Date.now()
47
+ const time = nowUnixMillis()
47
48
  if (shouldInvoke(time, state)) {
48
49
  trailingEdge(time)
49
50
  return
@@ -74,7 +75,7 @@ export function _debounce<T extends AnyFunction>(
74
75
  }
75
76
 
76
77
  function flush(): ReturnType<T> {
77
- return timerId === undefined ? result : trailingEdge(Date.now())
78
+ return timerId === undefined ? result : trailingEdge(nowUnixMillis())
78
79
  }
79
80
 
80
81
  function pending(): boolean {
@@ -82,7 +83,7 @@ export function _debounce<T extends AnyFunction>(
82
83
  }
83
84
 
84
85
  function debounced(this: ThisParameterType<T>, ...args: Parameters<T>): ReturnType<T> {
85
- const time = Date.now()
86
+ const time = nowUnixMillis()
86
87
  const isInvoking = shouldInvoke(time, state)
87
88
 
88
89
  lastArgs = args
@@ -189,7 +190,7 @@ export function _asyncDebounce<T extends AnyAsyncFunction>(
189
190
  }
190
191
 
191
192
  function timerExpired(): void {
192
- const time = Date.now()
193
+ const time = nowUnixMillis()
193
194
  if (shouldInvoke(time, state)) {
194
195
  void trailingEdge(time)
195
196
  return
@@ -237,7 +238,7 @@ export function _asyncDebounce<T extends AnyAsyncFunction>(
237
238
  }
238
239
  const d = deferred
239
240
  cancelTimer(timerId)
240
- await trailingEdge(Date.now())
241
+ await trailingEdge(nowUnixMillis())
241
242
  return d
242
243
  }
243
244
 
@@ -249,7 +250,7 @@ export function _asyncDebounce<T extends AnyAsyncFunction>(
249
250
  this: ThisParameterType<T>,
250
251
  ...args: Parameters<T>
251
252
  ): Promise<Awaited<ReturnType<T>> | undefined> {
252
- const time = Date.now()
253
+ const time = nowUnixMillis()
253
254
  const isInvoking = shouldInvoke(time, state)
254
255
 
255
256
  lastArgs = args
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js'
1
2
  import { _ms } from '../datetime/time.util.js'
2
3
  import { _assert } from '../error/assert.js'
3
4
  import { consoleLogger } from '../log/commonLogger.js'
@@ -100,7 +101,7 @@ export function _LogMethod(opt: LogMethodOptions = {}): MethodDecorator {
100
101
  let count = 0
101
102
 
102
103
  descriptor.value = function (this: typeof _target, ...args: any[]) {
103
- const started = Date.now()
104
+ const started = nowUnixMillis()
104
105
  const ctx = this
105
106
 
106
107
  // e.g `NameOfYourClass.methodName`
@@ -148,7 +149,7 @@ function logFinished(
148
149
  res?: any,
149
150
  err?: any,
150
151
  ): void {
151
- const millis = Date.now() - started
152
+ const millis = nowUnixMillis() - started
152
153
 
153
154
  const t = ['<<', callSignature, 'took', _ms(millis)]
154
155
 
package/src/enum.util.ts CHANGED
@@ -284,6 +284,7 @@ export function _numberEnumKey<T extends NumberEnum>(
284
284
  ): keyof T {
285
285
  const key = (en as any)[v]
286
286
  // This prevents passing a Key (not a Value) of enum here, which returns unexpected result (number, not string)
287
+ // oxlint-disable-next-line unicorn/prefer-type-error
287
288
  if (typeof key !== 'string') throw new Error(`_numberEnumKey not found for: ${v}`)
288
289
  return key
289
290
  }
@@ -1,3 +1,5 @@
1
+ // `_assert` and friends are implemented by throwing in an if
2
+ // oxlint-disable nc/prefer-assert
1
3
  import { _deepEquals } from '../object/deepEquals.js'
2
4
  import { _stringify } from '../string/stringify.js'
3
5
  import type { Class, NonEmptyArray, UnixTimestamp } from '../types.js'
@@ -1,7 +1,8 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js'
1
2
  import { _since } from '../datetime/time.util.js'
2
3
  import { consoleLogger } from '../log/commonLogger.js'
3
4
  import type { CommonLogger } from '../log/commonLogger.js'
4
- import type { AnyFunction, UnixTimestampMillis } from '../types.js'
5
+ import type { AnyFunction } from '../types.js'
5
6
  import { _anyToError } from './error.util.js'
6
7
 
7
8
  export interface TryCatchOptions {
@@ -41,7 +42,7 @@ export function _tryCatch<T extends AnyFunction>(fn: T, opt: TryCatchOptions = {
41
42
  const fname = fn.name || 'anonymous'
42
43
 
43
44
  return async function (this: any, ...args: any[]) {
44
- const started = Date.now() as UnixTimestampMillis
45
+ const started = nowUnixMillis()
45
46
 
46
47
  try {
47
48
  const r = await fn.apply(this, args)
@@ -4,9 +4,10 @@
4
4
 
5
5
  import type { ReadableStream as WebReadableStream } from 'node:stream/web'
6
6
  import { abortSignalAnyOrUndefined, abortSignalTimeoutOrUndefined } from '../abort.js'
7
+ import { nowUnixMillis } from '../datetime/localTime.js'
7
8
  import { _ms, _since } from '../datetime/time.util.js'
8
9
  import { isServerSide } from '../env.js'
9
- import { _assertErrorClassOrRethrow, _assertIsError } from '../error/assert.js'
10
+ import { _assert, _assertErrorClassOrRethrow, _assertIsError } from '../error/assert.js'
10
11
  import type { ErrorLike, ErrorObject } from '../error/error.model.js'
11
12
  import {
12
13
  _anyToError,
@@ -31,12 +32,7 @@ import { pDelaySignal } from '../promise/pDelay.js'
31
32
  import { pTimeout } from '../promise/pTimeout.js'
32
33
  import { _toUrlOrNull } from '../string/index.js'
33
34
  import { _jsonParse, _jsonParseIfPossible } from '../string/json.util.js'
34
- import type {
35
- AnyObject,
36
- ErrorDataTuple,
37
- NumberOfMilliseconds,
38
- UnixTimestampMillis,
39
- } from '../types.js'
35
+ import type { AnyObject, ErrorDataTuple, NumberOfMilliseconds } from '../types.js'
40
36
  import type {
41
37
  FetcherAfterResponseHook,
42
38
  FetcherBeforeRequestHook,
@@ -250,7 +246,7 @@ export class Fetcher {
250
246
  requestSignature: res.signature,
251
247
  requestName: res.req.requestName,
252
248
  fetcherName: this.cfg.name,
253
- requestDuration: Date.now() - res.req.started,
249
+ requestDuration: nowUnixMillis() - res.req.started,
254
250
  })
255
251
  }
256
252
 
@@ -400,7 +396,7 @@ export class Fetcher {
400
396
  } as FetcherResponse<any>
401
397
 
402
398
  while (!res.retryStatus.retryStopped) {
403
- req.started = Date.now() as UnixTimestampMillis
399
+ req.started = nowUnixMillis()
404
400
  res.body = undefined
405
401
 
406
402
  req.init.signal = abortSignalAnyOrUndefined([
@@ -560,10 +556,8 @@ export class Fetcher {
560
556
  } else if (responseType === 'readableStream') {
561
557
  res.body = res.fetchResponse.body
562
558
 
563
- if (res.body === null) {
564
- // Error is to be handled upstream
565
- throw new Error('fetchResponse.body is null')
566
- }
559
+ // Error is to be handled upstream
560
+ _assert(res.body !== null, 'fetchResponse.body is null')
567
561
  }
568
562
 
569
563
  res.retryStatus.retryStopped = true
@@ -655,7 +649,7 @@ export class Fetcher {
655
649
  requestSignature: res.signature,
656
650
  requestName: res.req.requestName,
657
651
  fetcherName: this.cfg.name,
658
- requestDuration: Date.now() - res.req.started,
652
+ requestDuration: nowUnixMillis() - res.req.started,
659
653
  }),
660
654
  {
661
655
  cause,
@@ -748,14 +742,15 @@ export class Fetcher {
748
742
  if (retryAfterNumber > 10 ** 9) {
749
743
  // Value is too large to be a "seconds from now" delay,
750
744
  // treat it as a UnixTimestamp instead (e.g GitHub sends `x-ratelimit-reset` like that)
751
- timeout = retryAfterNumber * 1000 - Date.now()
745
+ timeout = retryAfterNumber * 1000 - nowUnixMillis()
752
746
  } else {
753
747
  timeout = retryAfterNumber * 1000
754
748
  }
755
749
  } else {
750
+ // oxlint-disable-next-line no-restricted-globals -- parses an HTTP-date, which localTime doesn't support
756
751
  const date = new Date(retryAfterStr)
757
752
  if (!Number.isNaN(date.getTime())) {
758
- timeout = date.getTime() - Date.now()
753
+ timeout = date.getTime() - nowUnixMillis()
759
754
  }
760
755
  }
761
756
 
@@ -930,7 +925,7 @@ export class Fetcher {
930
925
  'fetchFn',
931
926
  'overrideFetchFn',
932
927
  ]),
933
- started: Date.now() as UnixTimestampMillis,
928
+ started: nowUnixMillis(),
934
929
  ..._omit(opt, ['method', 'headers', 'credentials']),
935
930
  inputUrl: opt.url || '',
936
931
  fullUrl: opt.url || '',
@@ -112,6 +112,7 @@ export function _deepJsonEquals<T>(a: T, b: T): boolean {
112
112
  } else if (typeof a === 'function') {
113
113
  ;(a as any) = undefined
114
114
  } else if (a && typeof a === 'object') {
115
+ // oxlint-disable-next-line no-restricted-globals -- Date instances are compared by value
115
116
  if (a instanceof Date) {
116
117
  ;(a as any) = a.valueOf()
117
118
  } else if ('toJSON' in a) {
@@ -123,6 +124,7 @@ export function _deepJsonEquals<T>(a: T, b: T): boolean {
123
124
  } else if (typeof b === 'function') {
124
125
  ;(b as any) = undefined
125
126
  } else if (b && typeof b === 'object') {
127
+ // oxlint-disable-next-line no-restricted-globals -- Date instances are compared by value
126
128
  if (b instanceof Date) {
127
129
  ;(b as any) = b.valueOf()
128
130
  } else if ('toJSON' in b) {
@@ -1,3 +1,5 @@
1
+ import { _assert } from '../error/assert.js'
2
+
1
3
  /**
2
4
  * Like Set, but serializes to JSON as an array.
3
5
  *
@@ -22,7 +24,7 @@ export class Set2<T = any> extends Set<T> {
22
24
  }
23
25
 
24
26
  first(): T {
25
- if (!this.size) throw new Error('Set.first called on empty set')
27
+ _assert(this.size, 'Set.first called on empty set')
26
28
  return this.firstOrUndefined()!
27
29
  }
28
30
 
@@ -1,3 +1,4 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js'
1
2
  import { ErrorMode } from '../error/errorMode.js'
2
3
  import { createCommonLoggerAtLevel } from '../log/commonLogger.js'
3
4
  import type { CommonLogger, CommonLogLevel } from '../log/commonLogger.js'
@@ -73,7 +74,7 @@ export class PGradualQueue {
73
74
  // Fast-path: warmup complete
74
75
  if (this.warmupComplete) return this.cfg.concurrency
75
76
 
76
- const elapsed = Date.now() - this.startTime
77
+ const elapsed = nowUnixMillis() - this.startTime
77
78
  if (elapsed >= this.warmupMs) {
78
79
  this.warmupComplete = true
79
80
  this.logger.debug('warmup complete')
@@ -92,7 +93,7 @@ export class PGradualQueue {
92
93
  async push<R>(fn: AsyncFunction<R>): Promise<R> {
93
94
  // Initialize start time on first push
94
95
  if (this.startTime === 0) {
95
- this.startTime = Date.now()
96
+ this.startTime = nowUnixMillis()
96
97
  }
97
98
 
98
99
  const job: PGradualQueueJob<R> = { fn, defer: pDefer<R>() }
@@ -1,9 +1,10 @@
1
+ import { nowUnixMillis } from '../datetime/localTime.js'
1
2
  import { _since } from '../datetime/time.util.js'
2
3
  import type { ErrorData } from '../error/error.model.js'
3
4
  import { _errorDataAppend } from '../error/error.util.js'
4
5
  import { consoleLogger } from '../log/commonLogger.js'
5
6
  import type { CommonLogger } from '../log/commonLogger.js'
6
- import type { AnyAsyncFunction, UnixTimestampMillis } from '../types.js'
7
+ import type { AnyAsyncFunction } from '../types.js'
7
8
  import { pDelay } from './pDelay.js'
8
9
  import { pTimeout } from './pTimeout.js'
9
10
 
@@ -141,7 +142,7 @@ export async function pRetry<T>(
141
142
  let attempt = 0
142
143
 
143
144
  while (true) {
144
- const started = Date.now() as UnixTimestampMillis
145
+ const started = nowUnixMillis()
145
146
 
146
147
  try {
147
148
  attempt++