@naturalcycles/js-lib 14.162.0 → 14.164.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.
@@ -16,6 +16,25 @@ export declare function _chunk<T>(array: readonly T[], size?: number): T[][];
16
16
  * Removes duplicates from given array.
17
17
  */
18
18
  export declare function _uniq<T>(a: readonly T[]): T[];
19
+ /**
20
+ * Pushes an item to an array if it's not already there.
21
+ * Mutates the array (same as normal `push`) and also returns it for chaining convenience.
22
+ *
23
+ * _pushUniq([1, 2, 3], 2) // => [1, 2, 3]
24
+ *
25
+ * Shortcut for:
26
+ * if (!a.includes(item)) a.push(item)
27
+ * // or
28
+ * a = [...new Set(a).add(item)]
29
+ * // or
30
+ * a = _uniq([...a, item])
31
+ */
32
+ export declare function _pushUniq<T>(a: T[], ...items: T[]): T[];
33
+ /**
34
+ * Like _pushUniq but uses a mapper to determine uniqueness (like _uniqBy).
35
+ * Mutates the array (same as normal `push`).
36
+ */
37
+ export declare function _pushUniqBy<T>(a: T[], mapper: Mapper<T, any>, ...items: T[]): T[];
19
38
  /**
20
39
  * This method is like `_.uniq` except that it accepts `iteratee` which is
21
40
  * invoked for each element in `array` to generate the criterion by which
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._minByOrUndefined = exports._maxByOrUndefined = exports._minBy = exports._maxBy = exports._max = exports._maxOrUndefined = exports._min = exports._minOrUndefined = exports._lastOrUndefined = exports._last = exports._shuffle = exports._mapToObject = exports._sumBy = exports._sum = exports._difference = exports._intersection = exports._countBy = exports._dropRightWhile = exports._dropWhile = exports._takeRightWhile = exports._takeWhile = exports._findLast = exports._sortBy = exports._groupBy = exports._by = exports._uniqBy = exports._uniq = exports._chunk = void 0;
3
+ exports._minByOrUndefined = exports._maxByOrUndefined = exports._minBy = exports._maxBy = exports._max = exports._maxOrUndefined = exports._min = exports._minOrUndefined = exports._lastOrUndefined = exports._last = exports._shuffle = exports._mapToObject = exports._sumBy = exports._sum = exports._difference = exports._intersection = exports._countBy = exports._dropRightWhile = exports._dropWhile = exports._takeRightWhile = exports._takeWhile = exports._findLast = exports._sortBy = exports._groupBy = exports._by = exports._uniqBy = exports._pushUniqBy = exports._pushUniq = exports._uniq = exports._chunk = void 0;
4
4
  const is_util_1 = require("../is.util");
5
5
  /**
6
6
  * Creates an array of elements split into groups the length of size. If collection can’t be split evenly, the
@@ -27,6 +27,43 @@ function _uniq(a) {
27
27
  return [...new Set(a)];
28
28
  }
29
29
  exports._uniq = _uniq;
30
+ /**
31
+ * Pushes an item to an array if it's not already there.
32
+ * Mutates the array (same as normal `push`) and also returns it for chaining convenience.
33
+ *
34
+ * _pushUniq([1, 2, 3], 2) // => [1, 2, 3]
35
+ *
36
+ * Shortcut for:
37
+ * if (!a.includes(item)) a.push(item)
38
+ * // or
39
+ * a = [...new Set(a).add(item)]
40
+ * // or
41
+ * a = _uniq([...a, item])
42
+ */
43
+ function _pushUniq(a, ...items) {
44
+ items.forEach(item => {
45
+ if (!a.includes(item))
46
+ a.push(item);
47
+ });
48
+ return a;
49
+ }
50
+ exports._pushUniq = _pushUniq;
51
+ /**
52
+ * Like _pushUniq but uses a mapper to determine uniqueness (like _uniqBy).
53
+ * Mutates the array (same as normal `push`).
54
+ */
55
+ function _pushUniqBy(a, mapper, ...items) {
56
+ const mappedSet = new Set(a.map((item, i) => mapper(item, i)));
57
+ items.forEach((item, i) => {
58
+ const mapped = mapper(item, i);
59
+ if (!mappedSet.has(mapped)) {
60
+ a.push(item);
61
+ mappedSet.add(mapped);
62
+ }
63
+ });
64
+ return a;
65
+ }
66
+ exports._pushUniqBy = _pushUniqBy;
30
67
  /**
31
68
  * This method is like `_.uniq` except that it accepts `iteratee` which is
32
69
  * invoked for each element in `array` to generate the criterion by which
@@ -23,16 +23,14 @@ class LocalDate {
23
23
  */
24
24
  static of(d) {
25
25
  const t = this.parseOrNull(d);
26
- if (t === null) {
27
- throw new Error(`Cannot parse "${d}" into LocalDate`);
28
- }
26
+ (0, assert_1._assert)(t !== null, `Cannot parse "${d}" into LocalDate`, {
27
+ input: d,
28
+ });
29
29
  return t;
30
30
  }
31
31
  static parseCompact(d) {
32
32
  const [year, month, day] = [d.slice(0, 4), d.slice(4, 2), d.slice(6, 2)].map(Number);
33
- if (!day || !month || !year) {
34
- throw new Error(`Cannot parse "${d}" into LocalDate`);
35
- }
33
+ (0, assert_1._assert)(day && month && year, `Cannot parse "${d}" into LocalDate`);
36
34
  return new LocalDate(year, month, day);
37
35
  }
38
36
  static fromDate(d) {
@@ -33,9 +33,9 @@ class LocalTime {
33
33
  */
34
34
  static of(d) {
35
35
  const t = this.parseOrNull(d);
36
- if (t === null) {
37
- throw new TypeError(`Cannot parse "${d}" into LocalTime`);
38
- }
36
+ (0, assert_1._assert)(t !== null, `Cannot parse "${d}" into LocalTime`, {
37
+ input: d,
38
+ });
39
39
  return t;
40
40
  }
41
41
  /**
@@ -82,9 +82,9 @@ class LocalTime {
82
82
  if (d instanceof Date)
83
83
  return d;
84
84
  const date = typeof d === 'number' ? new Date(d * 1000) : new Date(d);
85
- if (isNaN(date.getDate())) {
86
- throw new TypeError(`Cannot parse "${d}" to Date`);
87
- }
85
+ (0, assert_1._assert)(!isNaN(date.getDate()), `Cannot parse "${d}" to Date`, {
86
+ input: d,
87
+ });
88
88
  return date;
89
89
  }
90
90
  static parseToUnixTimestamp(d) {
@@ -93,9 +93,9 @@ class LocalTime {
93
93
  if (d instanceof LocalTime)
94
94
  return d.unix();
95
95
  const date = d instanceof Date ? d : new Date(d);
96
- if (isNaN(date.getDate())) {
97
- throw new TypeError(`Cannot parse "${d}" to UnixTimestamp`);
98
- }
96
+ (0, assert_1._assert)(!isNaN(date.getDate()), `Cannot parse "${d}" to UnixTimestamp`, {
97
+ input: d,
98
+ });
99
99
  return date.valueOf() / 1000;
100
100
  }
101
101
  static isValid(d) {
@@ -171,8 +171,7 @@ class LocalTime {
171
171
  if (v === undefined) {
172
172
  return dow;
173
173
  }
174
- if (!VALID_DAYS_OF_WEEK.has(v))
175
- throw new Error(`Invalid dayOfWeek: ${v}`);
174
+ (0, assert_1._assert)(VALID_DAYS_OF_WEEK.has(v), `Invalid dayOfWeek: ${v}`);
176
175
  return this.add(v - dow, 'day');
177
176
  }
178
177
  hour(v) {
@@ -44,12 +44,16 @@ export interface ErrorData {
44
44
  * Can be used by error-reporting tools (e.g Sentry).
45
45
  * If fingerprint is defined - it'll be used INSTEAD of default fingerprint of a tool.
46
46
  * Can be used to force-group errors that are NOT needed to be split by endpoint or calling function.
47
+ *
48
+ * Sentry takes string[], but for convenience we allow to pass a singe string.
47
49
  */
48
- fingerprint?: string[];
50
+ fingerprint?: string | string[];
49
51
  /**
50
52
  * If set to true - it'll use error.message as fingerprint,
51
53
  * so, all errors with the same message will be grouped together, even if they occurred in different places.
52
54
  * Defaults to false.
55
+ *
56
+ * @experimental
53
57
  */
54
58
  fingerprintByMessage?: boolean;
55
59
  /**
package/dist/index.d.ts CHANGED
@@ -81,6 +81,7 @@ export * from './http/fetcher.model';
81
81
  export * from './string/hash.util';
82
82
  export * from './env/buildInfo';
83
83
  export * from './form.util';
84
+ export * from './semver';
84
85
  export * from './zod/zod.util';
85
86
  export * from './zod/zod.shared.schemas';
86
87
  import { z, ZodSchema, ZodError, ZodIssue } from 'zod';
package/dist/index.js CHANGED
@@ -85,6 +85,7 @@ tslib_1.__exportStar(require("./http/fetcher.model"), exports);
85
85
  tslib_1.__exportStar(require("./string/hash.util"), exports);
86
86
  tslib_1.__exportStar(require("./env/buildInfo"), exports);
87
87
  tslib_1.__exportStar(require("./form.util"), exports);
88
+ tslib_1.__exportStar(require("./semver"), exports);
88
89
  tslib_1.__exportStar(require("./zod/zod.util"), exports);
89
90
  tslib_1.__exportStar(require("./zod/zod.shared.schemas"), exports);
90
91
  const zod_1 = require("zod");
@@ -0,0 +1,44 @@
1
+ export type SemverInput = string | Semver;
2
+ export type SemverTokens = [major: number, minor: number, patch: number];
3
+ /**
4
+ * Simple Semver implementation.
5
+ *
6
+ * Suitable for Browser usage, unlike npm `semver` which is Node-targeted and simply too big for the Browser.
7
+ *
8
+ * Parsing algorithm is simple:
9
+ * 1. Split by `.`
10
+ * 2. parseInt each of 3 tokens, set to 0 if falsy
11
+ *
12
+ * toString returns `major.minor.patch`
13
+ * Missing tokens are replaced with 0.
14
+ *
15
+ * _semver('1').toString() === '1.0.0'
16
+ *
17
+ * @experimental
18
+ */
19
+ export declare class Semver {
20
+ tokens: SemverTokens;
21
+ private constructor();
22
+ get major(): number;
23
+ get minor(): number;
24
+ get patch(): number;
25
+ static of(input: SemverInput): Semver;
26
+ static parseOrNull(input: SemverInput | undefined | null): Semver | null;
27
+ isAfter: (other: SemverInput) => boolean;
28
+ isSameOrAfter: (other: SemverInput) => boolean;
29
+ isBefore: (other: SemverInput) => boolean;
30
+ isSameOrBefore: (other: SemverInput) => boolean;
31
+ isSame: (other: SemverInput) => boolean;
32
+ /**
33
+ * Returns 1 if this > other
34
+ * returns 0 if they are equal
35
+ * returns -1 if this < other
36
+ */
37
+ cmp(other: SemverInput): -1 | 0 | 1;
38
+ toJSON: () => string;
39
+ toString(): string;
40
+ }
41
+ /**
42
+ * Shortcut for Semver.of(input)
43
+ */
44
+ export declare function _semver(input: SemverInput): Semver;
package/dist/semver.js ADDED
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports._semver = exports.Semver = void 0;
4
+ const range_1 = require("./array/range");
5
+ const assert_1 = require("./error/assert");
6
+ /**
7
+ * Simple Semver implementation.
8
+ *
9
+ * Suitable for Browser usage, unlike npm `semver` which is Node-targeted and simply too big for the Browser.
10
+ *
11
+ * Parsing algorithm is simple:
12
+ * 1. Split by `.`
13
+ * 2. parseInt each of 3 tokens, set to 0 if falsy
14
+ *
15
+ * toString returns `major.minor.patch`
16
+ * Missing tokens are replaced with 0.
17
+ *
18
+ * _semver('1').toString() === '1.0.0'
19
+ *
20
+ * @experimental
21
+ */
22
+ class Semver {
23
+ constructor(tokens) {
24
+ this.tokens = tokens;
25
+ this.isAfter = (other) => this.cmp(other) > 0;
26
+ this.isSameOrAfter = (other) => this.cmp(other) >= 0;
27
+ this.isBefore = (other) => this.cmp(other) < 0;
28
+ this.isSameOrBefore = (other) => this.cmp(other) <= 0;
29
+ this.isSame = (other) => this.cmp(other) === 0;
30
+ this.toJSON = () => this.toString();
31
+ }
32
+ get major() {
33
+ return this.tokens[0];
34
+ }
35
+ get minor() {
36
+ return this.tokens[1];
37
+ }
38
+ get patch() {
39
+ return this.tokens[2];
40
+ }
41
+ static of(input) {
42
+ const s = this.parseOrNull(input);
43
+ (0, assert_1._assert)(s !== null, `Cannot parse "${input}" into Semver`, {
44
+ userFriendly: true,
45
+ input,
46
+ });
47
+ return s;
48
+ }
49
+ static parseOrNull(input) {
50
+ if (!input)
51
+ return null;
52
+ if (input instanceof Semver)
53
+ return input;
54
+ const t = input.split('.');
55
+ return new Semver((0, range_1._range)(3).map(i => parseInt(t[i]) || 0));
56
+ }
57
+ /**
58
+ * Returns 1 if this > other
59
+ * returns 0 if they are equal
60
+ * returns -1 if this < other
61
+ */
62
+ cmp(other) {
63
+ const { tokens } = Semver.of(other);
64
+ for (let i = 0; i < 3; i++) {
65
+ if (this.tokens[i] < tokens[i])
66
+ return -1;
67
+ if (this.tokens[i] > tokens[i])
68
+ return 1;
69
+ }
70
+ return 0;
71
+ }
72
+ toString() {
73
+ return this.tokens.join('.');
74
+ }
75
+ }
76
+ exports.Semver = Semver;
77
+ /**
78
+ * Shortcut for Semver.of(input)
79
+ */
80
+ function _semver(input) {
81
+ return Semver.of(input);
82
+ }
83
+ exports._semver = _semver;
@@ -22,6 +22,41 @@ export function _chunk(array, size = 1) {
22
22
  export function _uniq(a) {
23
23
  return [...new Set(a)];
24
24
  }
25
+ /**
26
+ * Pushes an item to an array if it's not already there.
27
+ * Mutates the array (same as normal `push`) and also returns it for chaining convenience.
28
+ *
29
+ * _pushUniq([1, 2, 3], 2) // => [1, 2, 3]
30
+ *
31
+ * Shortcut for:
32
+ * if (!a.includes(item)) a.push(item)
33
+ * // or
34
+ * a = [...new Set(a).add(item)]
35
+ * // or
36
+ * a = _uniq([...a, item])
37
+ */
38
+ export function _pushUniq(a, ...items) {
39
+ items.forEach(item => {
40
+ if (!a.includes(item))
41
+ a.push(item);
42
+ });
43
+ return a;
44
+ }
45
+ /**
46
+ * Like _pushUniq but uses a mapper to determine uniqueness (like _uniqBy).
47
+ * Mutates the array (same as normal `push`).
48
+ */
49
+ export function _pushUniqBy(a, mapper, ...items) {
50
+ const mappedSet = new Set(a.map((item, i) => mapper(item, i)));
51
+ items.forEach((item, i) => {
52
+ const mapped = mapper(item, i);
53
+ if (!mappedSet.has(mapped)) {
54
+ a.push(item);
55
+ mappedSet.add(mapped);
56
+ }
57
+ });
58
+ return a;
59
+ }
25
60
  /**
26
61
  * This method is like `_.uniq` except that it accepts `iteratee` which is
27
62
  * invoked for each element in `array` to generate the criterion by which
@@ -20,16 +20,14 @@ export class LocalDate {
20
20
  */
21
21
  static of(d) {
22
22
  const t = this.parseOrNull(d);
23
- if (t === null) {
24
- throw new Error(`Cannot parse "${d}" into LocalDate`);
25
- }
23
+ _assert(t !== null, `Cannot parse "${d}" into LocalDate`, {
24
+ input: d,
25
+ });
26
26
  return t;
27
27
  }
28
28
  static parseCompact(d) {
29
29
  const [year, month, day] = [d.slice(0, 4), d.slice(4, 2), d.slice(6, 2)].map(Number);
30
- if (!day || !month || !year) {
31
- throw new Error(`Cannot parse "${d}" into LocalDate`);
32
- }
30
+ _assert(day && month && year, `Cannot parse "${d}" into LocalDate`);
33
31
  return new LocalDate(year, month, day);
34
32
  }
35
33
  static fromDate(d) {
@@ -30,9 +30,9 @@ export class LocalTime {
30
30
  */
31
31
  static of(d) {
32
32
  const t = this.parseOrNull(d);
33
- if (t === null) {
34
- throw new TypeError(`Cannot parse "${d}" into LocalTime`);
35
- }
33
+ _assert(t !== null, `Cannot parse "${d}" into LocalTime`, {
34
+ input: d,
35
+ });
36
36
  return t;
37
37
  }
38
38
  /**
@@ -79,9 +79,9 @@ export class LocalTime {
79
79
  if (d instanceof Date)
80
80
  return d;
81
81
  const date = typeof d === 'number' ? new Date(d * 1000) : new Date(d);
82
- if (isNaN(date.getDate())) {
83
- throw new TypeError(`Cannot parse "${d}" to Date`);
84
- }
82
+ _assert(!isNaN(date.getDate()), `Cannot parse "${d}" to Date`, {
83
+ input: d,
84
+ });
85
85
  return date;
86
86
  }
87
87
  static parseToUnixTimestamp(d) {
@@ -90,9 +90,9 @@ export class LocalTime {
90
90
  if (d instanceof LocalTime)
91
91
  return d.unix();
92
92
  const date = d instanceof Date ? d : new Date(d);
93
- if (isNaN(date.getDate())) {
94
- throw new TypeError(`Cannot parse "${d}" to UnixTimestamp`);
95
- }
93
+ _assert(!isNaN(date.getDate()), `Cannot parse "${d}" to UnixTimestamp`, {
94
+ input: d,
95
+ });
96
96
  return date.valueOf() / 1000;
97
97
  }
98
98
  static isValid(d) {
@@ -168,8 +168,7 @@ export class LocalTime {
168
168
  if (v === undefined) {
169
169
  return dow;
170
170
  }
171
- if (!VALID_DAYS_OF_WEEK.has(v))
172
- throw new Error(`Invalid dayOfWeek: ${v}`);
171
+ _assert(VALID_DAYS_OF_WEEK.has(v), `Invalid dayOfWeek: ${v}`);
173
172
  return this.add(v - dow, 'day');
174
173
  }
175
174
  hour(v) {
package/dist-esm/index.js CHANGED
@@ -81,6 +81,7 @@ export * from './http/fetcher.model';
81
81
  export * from './string/hash.util';
82
82
  export * from './env/buildInfo';
83
83
  export * from './form.util';
84
+ export * from './semver';
84
85
  export * from './zod/zod.util';
85
86
  export * from './zod/zod.shared.schemas';
86
87
  import { z, ZodSchema, ZodError } from 'zod';
@@ -0,0 +1,78 @@
1
+ import { _range } from './array/range';
2
+ import { _assert } from './error/assert';
3
+ /**
4
+ * Simple Semver implementation.
5
+ *
6
+ * Suitable for Browser usage, unlike npm `semver` which is Node-targeted and simply too big for the Browser.
7
+ *
8
+ * Parsing algorithm is simple:
9
+ * 1. Split by `.`
10
+ * 2. parseInt each of 3 tokens, set to 0 if falsy
11
+ *
12
+ * toString returns `major.minor.patch`
13
+ * Missing tokens are replaced with 0.
14
+ *
15
+ * _semver('1').toString() === '1.0.0'
16
+ *
17
+ * @experimental
18
+ */
19
+ export class Semver {
20
+ constructor(tokens) {
21
+ this.tokens = tokens;
22
+ this.isAfter = (other) => this.cmp(other) > 0;
23
+ this.isSameOrAfter = (other) => this.cmp(other) >= 0;
24
+ this.isBefore = (other) => this.cmp(other) < 0;
25
+ this.isSameOrBefore = (other) => this.cmp(other) <= 0;
26
+ this.isSame = (other) => this.cmp(other) === 0;
27
+ this.toJSON = () => this.toString();
28
+ }
29
+ get major() {
30
+ return this.tokens[0];
31
+ }
32
+ get minor() {
33
+ return this.tokens[1];
34
+ }
35
+ get patch() {
36
+ return this.tokens[2];
37
+ }
38
+ static of(input) {
39
+ const s = this.parseOrNull(input);
40
+ _assert(s !== null, `Cannot parse "${input}" into Semver`, {
41
+ userFriendly: true,
42
+ input,
43
+ });
44
+ return s;
45
+ }
46
+ static parseOrNull(input) {
47
+ if (!input)
48
+ return null;
49
+ if (input instanceof Semver)
50
+ return input;
51
+ const t = input.split('.');
52
+ return new Semver(_range(3).map(i => parseInt(t[i]) || 0));
53
+ }
54
+ /**
55
+ * Returns 1 if this > other
56
+ * returns 0 if they are equal
57
+ * returns -1 if this < other
58
+ */
59
+ cmp(other) {
60
+ const { tokens } = Semver.of(other);
61
+ for (let i = 0; i < 3; i++) {
62
+ if (this.tokens[i] < tokens[i])
63
+ return -1;
64
+ if (this.tokens[i] > tokens[i])
65
+ return 1;
66
+ }
67
+ return 0;
68
+ }
69
+ toString() {
70
+ return this.tokens.join('.');
71
+ }
72
+ }
73
+ /**
74
+ * Shortcut for Semver.of(input)
75
+ */
76
+ export function _semver(input) {
77
+ return Semver.of(input);
78
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.162.0",
3
+ "version": "14.164.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -26,6 +26,42 @@ export function _uniq<T>(a: readonly T[]): T[] {
26
26
  return [...new Set(a)]
27
27
  }
28
28
 
29
+ /**
30
+ * Pushes an item to an array if it's not already there.
31
+ * Mutates the array (same as normal `push`) and also returns it for chaining convenience.
32
+ *
33
+ * _pushUniq([1, 2, 3], 2) // => [1, 2, 3]
34
+ *
35
+ * Shortcut for:
36
+ * if (!a.includes(item)) a.push(item)
37
+ * // or
38
+ * a = [...new Set(a).add(item)]
39
+ * // or
40
+ * a = _uniq([...a, item])
41
+ */
42
+ export function _pushUniq<T>(a: T[], ...items: T[]): T[] {
43
+ items.forEach(item => {
44
+ if (!a.includes(item)) a.push(item)
45
+ })
46
+ return a
47
+ }
48
+
49
+ /**
50
+ * Like _pushUniq but uses a mapper to determine uniqueness (like _uniqBy).
51
+ * Mutates the array (same as normal `push`).
52
+ */
53
+ export function _pushUniqBy<T>(a: T[], mapper: Mapper<T, any>, ...items: T[]): T[] {
54
+ const mappedSet = new Set(a.map((item, i) => mapper(item, i)))
55
+ items.forEach((item, i) => {
56
+ const mapped = mapper(item, i)
57
+ if (!mappedSet.has(mapped)) {
58
+ a.push(item)
59
+ mappedSet.add(mapped)
60
+ }
61
+ })
62
+ return a
63
+ }
64
+
29
65
  /**
30
66
  * This method is like `_.uniq` except that it accepts `iteratee` which is
31
67
  * invoked for each element in `array` to generate the criterion by which
@@ -38,9 +38,9 @@ export class LocalDate {
38
38
  static of(d: LocalDateInput): LocalDate {
39
39
  const t = this.parseOrNull(d)
40
40
 
41
- if (t === null) {
42
- throw new Error(`Cannot parse "${d}" into LocalDate`)
43
- }
41
+ _assert(t !== null, `Cannot parse "${d}" into LocalDate`, {
42
+ input: d,
43
+ })
44
44
 
45
45
  return t
46
46
  }
@@ -48,9 +48,7 @@ export class LocalDate {
48
48
  static parseCompact(d: string): LocalDate {
49
49
  const [year, month, day] = [d.slice(0, 4), d.slice(4, 2), d.slice(6, 2)].map(Number)
50
50
 
51
- if (!day || !month || !year) {
52
- throw new Error(`Cannot parse "${d}" into LocalDate`)
53
- }
51
+ _assert(day && month && year, `Cannot parse "${d}" into LocalDate`)
54
52
 
55
53
  return new LocalDate(year, month, day)
56
54
  }
@@ -53,9 +53,9 @@ export class LocalTime {
53
53
  static of(d: LocalTimeInput): LocalTime {
54
54
  const t = this.parseOrNull(d)
55
55
 
56
- if (t === null) {
57
- throw new TypeError(`Cannot parse "${d}" into LocalTime`)
58
- }
56
+ _assert(t !== null, `Cannot parse "${d}" into LocalTime`, {
57
+ input: d,
58
+ })
59
59
 
60
60
  return t
61
61
  }
@@ -106,9 +106,9 @@ export class LocalTime {
106
106
 
107
107
  const date = typeof d === 'number' ? new Date(d * 1000) : new Date(d)
108
108
 
109
- if (isNaN(date.getDate())) {
110
- throw new TypeError(`Cannot parse "${d}" to Date`)
111
- }
109
+ _assert(!isNaN(date.getDate()), `Cannot parse "${d}" to Date`, {
110
+ input: d,
111
+ })
112
112
 
113
113
  return date
114
114
  }
@@ -119,9 +119,9 @@ export class LocalTime {
119
119
 
120
120
  const date = d instanceof Date ? d : new Date(d)
121
121
 
122
- if (isNaN(date.getDate())) {
123
- throw new TypeError(`Cannot parse "${d}" to UnixTimestamp`)
124
- }
122
+ _assert(!isNaN(date.getDate()), `Cannot parse "${d}" to UnixTimestamp`, {
123
+ input: d,
124
+ })
125
125
 
126
126
  return date.valueOf() / 1000
127
127
  }
@@ -220,7 +220,7 @@ export class LocalTime {
220
220
  return dow
221
221
  }
222
222
 
223
- if (!VALID_DAYS_OF_WEEK.has(v)) throw new Error(`Invalid dayOfWeek: ${v}`)
223
+ _assert(VALID_DAYS_OF_WEEK.has(v), `Invalid dayOfWeek: ${v}`)
224
224
 
225
225
  return this.add(v - dow, 'day')
226
226
  }
@@ -54,13 +54,17 @@ export interface ErrorData {
54
54
  * Can be used by error-reporting tools (e.g Sentry).
55
55
  * If fingerprint is defined - it'll be used INSTEAD of default fingerprint of a tool.
56
56
  * Can be used to force-group errors that are NOT needed to be split by endpoint or calling function.
57
+ *
58
+ * Sentry takes string[], but for convenience we allow to pass a singe string.
57
59
  */
58
- fingerprint?: string[]
60
+ fingerprint?: string | string[]
59
61
 
60
62
  /**
61
63
  * If set to true - it'll use error.message as fingerprint,
62
64
  * so, all errors with the same message will be grouped together, even if they occurred in different places.
63
65
  * Defaults to false.
66
+ *
67
+ * @experimental
64
68
  */
65
69
  fingerprintByMessage?: boolean
66
70
 
package/src/index.ts CHANGED
@@ -81,6 +81,7 @@ export * from './http/fetcher.model'
81
81
  export * from './string/hash.util'
82
82
  export * from './env/buildInfo'
83
83
  export * from './form.util'
84
+ export * from './semver'
84
85
  export * from './zod/zod.util'
85
86
  export * from './zod/zod.shared.schemas'
86
87
  import { z, ZodSchema, ZodError, ZodIssue } from 'zod'
package/src/semver.ts ADDED
@@ -0,0 +1,87 @@
1
+ import { _range } from './array/range'
2
+ import { _assert } from './error/assert'
3
+
4
+ export type SemverInput = string | Semver
5
+ export type SemverTokens = [major: number, minor: number, patch: number]
6
+
7
+ /**
8
+ * Simple Semver implementation.
9
+ *
10
+ * Suitable for Browser usage, unlike npm `semver` which is Node-targeted and simply too big for the Browser.
11
+ *
12
+ * Parsing algorithm is simple:
13
+ * 1. Split by `.`
14
+ * 2. parseInt each of 3 tokens, set to 0 if falsy
15
+ *
16
+ * toString returns `major.minor.patch`
17
+ * Missing tokens are replaced with 0.
18
+ *
19
+ * _semver('1').toString() === '1.0.0'
20
+ *
21
+ * @experimental
22
+ */
23
+ export class Semver {
24
+ private constructor(public tokens: SemverTokens) {}
25
+
26
+ get major(): number {
27
+ return this.tokens[0]
28
+ }
29
+ get minor(): number {
30
+ return this.tokens[1]
31
+ }
32
+ get patch(): number {
33
+ return this.tokens[2]
34
+ }
35
+
36
+ static of(input: SemverInput): Semver {
37
+ const s = this.parseOrNull(input)
38
+
39
+ _assert(s !== null, `Cannot parse "${input}" into Semver`, {
40
+ userFriendly: true,
41
+ input,
42
+ })
43
+
44
+ return s
45
+ }
46
+
47
+ static parseOrNull(input: SemverInput | undefined | null): Semver | null {
48
+ if (!input) return null
49
+ if (input instanceof Semver) return input
50
+
51
+ const t = input.split('.')
52
+ return new Semver(_range(3).map(i => parseInt(t[i]!) || 0) as SemverTokens)
53
+ }
54
+
55
+ isAfter = (other: SemverInput): boolean => this.cmp(other) > 0
56
+ isSameOrAfter = (other: SemverInput): boolean => this.cmp(other) >= 0
57
+ isBefore = (other: SemverInput): boolean => this.cmp(other) < 0
58
+ isSameOrBefore = (other: SemverInput): boolean => this.cmp(other) <= 0
59
+ isSame = (other: SemverInput): boolean => this.cmp(other) === 0
60
+
61
+ /**
62
+ * Returns 1 if this > other
63
+ * returns 0 if they are equal
64
+ * returns -1 if this < other
65
+ */
66
+ cmp(other: SemverInput): -1 | 0 | 1 {
67
+ const { tokens } = Semver.of(other)
68
+ for (let i = 0; i < 3; i++) {
69
+ if (this.tokens[i]! < tokens[i]!) return -1
70
+ if (this.tokens[i]! > tokens[i]!) return 1
71
+ }
72
+ return 0
73
+ }
74
+
75
+ toJSON = (): string => this.toString()
76
+
77
+ toString(): string {
78
+ return this.tokens.join('.')
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Shortcut for Semver.of(input)
84
+ */
85
+ export function _semver(input: SemverInput): Semver {
86
+ return Semver.of(input)
87
+ }