@bgord/tools 0.14.2 → 0.15.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.
- package/dist/age.vo.d.ts +10 -6
- package/dist/age.vo.js +23 -17
- package/dist/api-key.vo.d.ts +3 -0
- package/dist/api-key.vo.js +7 -1
- package/dist/clock-format.service.d.ts +9 -0
- package/dist/clock-format.service.js +10 -0
- package/dist/clock.vo.d.ts +7 -14
- package/dist/clock.vo.js +25 -34
- package/dist/height.vo.d.ts +7 -7
- package/dist/height.vo.js +6 -6
- package/dist/hour-format.service.d.ts +10 -0
- package/dist/hour-format.service.js +19 -0
- package/dist/hour.vo.d.ts +8 -17
- package/dist/hour.vo.js +22 -39
- package/dist/iban-mask.service.js +6 -3
- package/dist/iban.vo.d.ts +6 -4
- package/dist/iban.vo.js +7 -5
- package/dist/image.vo.d.ts +8 -2
- package/dist/image.vo.js +14 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/language.vo.d.ts +4 -1
- package/dist/language.vo.js +5 -3
- package/dist/mean.service.d.ts +4 -2
- package/dist/mean.service.js +9 -5
- package/dist/min-max-scaler.service.d.ts +22 -14
- package/dist/min-max-scaler.service.js +16 -15
- package/dist/minute.vo.d.ts +5 -6
- package/dist/minute.vo.js +14 -14
- package/dist/money.vo.d.ts +13 -2
- package/dist/money.vo.js +18 -13
- package/dist/outlier-detector.service.d.ts +2 -1
- package/dist/outlier-detector.service.js +5 -3
- package/dist/percentage.service.d.ts +3 -2
- package/dist/percentage.service.js +3 -2
- package/dist/population-standard-deviation.service.d.ts +3 -2
- package/dist/population-standard-deviation.service.js +5 -4
- package/dist/random.service.d.ts +6 -0
- package/dist/random.service.js +12 -6
- package/dist/rounding.adapter.d.ts +16 -0
- package/dist/{rounding.service.js → rounding.adapter.js} +8 -7
- package/dist/rounding.port.d.ts +3 -0
- package/dist/rounding.port.js +1 -0
- package/dist/simple-linear-regression.service.d.ts +11 -4
- package/dist/simple-linear-regression.service.js +39 -30
- package/dist/size.vo.js +1 -1
- package/dist/stopwatch.service.d.ts +1 -1
- package/dist/stopwatch.service.js +3 -4
- package/dist/sum.service.d.ts +2 -1
- package/dist/sum.service.js +11 -0
- package/dist/time.service.js +1 -1
- package/dist/timestamp.vo.d.ts +3 -0
- package/dist/timestamp.vo.js +7 -1
- package/dist/timezone.vo.d.ts +3 -0
- package/dist/timezone.vo.js +4 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/visually-unambiguous-characters-generator.service.js +0 -1
- package/dist/weight.vo.d.ts +4 -4
- package/dist/weight.vo.js +1 -1
- package/dist/z-score.service.d.ts +3 -2
- package/dist/z-score.service.js +3 -2
- package/package.json +3 -3
- package/readme.md +4 -2
- package/src/age.vo.ts +23 -19
- package/src/api-key.vo.ts +9 -1
- package/src/clock-format.service.ts +15 -0
- package/src/clock.vo.ts +24 -43
- package/src/height.vo.ts +12 -11
- package/src/hour-format.service.ts +21 -0
- package/src/hour.vo.ts +24 -47
- package/src/iban-mask.service.ts +8 -3
- package/src/iban.vo.ts +10 -8
- package/src/image.vo.ts +19 -4
- package/src/index.ts +2 -2
- package/src/language.vo.ts +7 -3
- package/src/mean.service.ts +13 -5
- package/src/min-max-scaler.service.ts +39 -24
- package/src/minute.vo.ts +18 -15
- package/src/money.vo.ts +26 -23
- package/src/outlier-detector.service.ts +6 -4
- package/src/percentage.service.ts +6 -7
- package/src/population-standard-deviation.service.ts +8 -5
- package/src/random.service.ts +16 -8
- package/src/relative-date.vo.ts +0 -1
- package/src/rounding.adapter.ts +33 -0
- package/src/rounding.port.ts +3 -0
- package/src/simple-linear-regression.service.ts +41 -31
- package/src/size.vo.ts +1 -1
- package/src/stopwatch.service.ts +4 -6
- package/src/sum.service.ts +15 -1
- package/src/time.service.ts +1 -1
- package/src/timestamp.vo.ts +9 -1
- package/src/timezone.vo.ts +15 -15
- package/src/visually-unambiguous-characters-generator.service.ts +0 -1
- package/src/weight.vo.ts +5 -4
- package/src/z-score.service.ts +6 -3
- package/dist/dates-of-the-week.vo.d.ts +0 -9
- package/dist/dates-of-the-week.vo.js +0 -10
- package/dist/rounding.service.d.ts +0 -17
- package/src/dates-of-the-week.vo.ts +0 -9
- package/src/rounding.service.ts +0 -31
package/dist/sum.service.js
CHANGED
|
@@ -2,4 +2,15 @@ export class Sum {
|
|
|
2
2
|
static of(values) {
|
|
3
3
|
return values.reduce((sum, x) => sum + x, 0);
|
|
4
4
|
}
|
|
5
|
+
static precise(values) {
|
|
6
|
+
let runningTotal = 0;
|
|
7
|
+
let roundingCompensation = 0;
|
|
8
|
+
for (const currentValue of values) {
|
|
9
|
+
const correctedAddend = currentValue - roundingCompensation;
|
|
10
|
+
const tentativeTotal = runningTotal + correctedAddend;
|
|
11
|
+
roundingCompensation = tentativeTotal - runningTotal - correctedAddend;
|
|
12
|
+
runningTotal = tentativeTotal;
|
|
13
|
+
}
|
|
14
|
+
return runningTotal;
|
|
15
|
+
}
|
|
5
16
|
}
|
package/dist/time.service.js
CHANGED
package/dist/timestamp.vo.d.ts
CHANGED
package/dist/timestamp.vo.js
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
|
-
export const
|
|
2
|
+
export const TimestampError = { error: "invalid.timestamp" };
|
|
3
|
+
export const Timestamp = z
|
|
4
|
+
.number(TimestampError)
|
|
5
|
+
.int(TimestampError)
|
|
6
|
+
.gte(0, TimestampError)
|
|
7
|
+
.lte(Number.MAX_SAFE_INTEGER, TimestampError)
|
|
8
|
+
.brand("Timestamp");
|
package/dist/timezone.vo.d.ts
CHANGED
package/dist/timezone.vo.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
|
+
export const TimezoneError = { error: "timezone.invalid" };
|
|
2
3
|
export const Timezone = z
|
|
3
|
-
.string()
|
|
4
|
-
.min(1)
|
|
4
|
+
.string(TimezoneError)
|
|
5
|
+
.min(1, TimezoneError)
|
|
5
6
|
.refine((value) => {
|
|
6
7
|
try {
|
|
7
8
|
const date = new Date();
|
|
@@ -12,5 +13,5 @@ export const Timezone = z
|
|
|
12
13
|
catch (_error) {
|
|
13
14
|
return false;
|
|
14
15
|
}
|
|
15
|
-
},
|
|
16
|
+
}, TimezoneError)
|
|
16
17
|
.brand("Timezone");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/age.vo.ts","../src/api-key.vo.ts","../src/basename.vo.ts","../src/clock.
|
|
1
|
+
{"root":["../src/age.vo.ts","../src/api-key.vo.ts","../src/basename.vo.ts","../src/clock-format.service.ts","../src/clock.vo.ts","../src/date-calculator.service.ts","../src/date-formatter.service.ts","../src/date-range.vo.ts","../src/day-iso-id.vo.ts","../src/day.vo.ts","../src/directory-path-absolute.vo.ts","../src/directory-path-relative.vo.ts","../src/dll.service.ts","../src/email-mask.service.ts","../src/etags.vo.ts","../src/extension.vo.ts","../src/feature-flag.vo.ts","../src/file-path-absolute-schema.vo.ts","../src/file-path-relative-schema.vo.ts","../src/file-path.vo.ts","../src/filename-from-string.vo.ts","../src/filename-suffix.vo.ts","../src/filename.vo.ts","../src/height.vo.ts","../src/hour-format.service.ts","../src/hour.vo.ts","../src/iban-mask.service.ts","../src/iban.vo.ts","../src/image.vo.ts","../src/index.ts","../src/language.vo.ts","../src/mean.service.ts","../src/mime-types.vo.ts","../src/mime.vo.ts","../src/min-max-scaler.service.ts","../src/minute.vo.ts","../src/money.vo.ts","../src/month-iso-id.vo.ts","../src/month.vo.ts","../src/noop.service.ts","../src/notification-template.vo.ts","../src/object-key.vo.ts","../src/outlier-detector.service.ts","../src/package-version.vo.ts","../src/pagination.service.ts","../src/percentage.service.ts","../src/population-standard-deviation.service.ts","../src/quarter-iso-id.vo.ts","../src/quarter.vo.ts","../src/random.service.ts","../src/rate-limiter.service.ts","../src/relative-date.vo.ts","../src/reordering.service.ts","../src/revision.vo.ts","../src/rounding.adapter.ts","../src/rounding.port.ts","../src/simple-linear-regression.service.ts","../src/size.vo.ts","../src/stepper.service.ts","../src/stopwatch.service.ts","../src/streak-calculator.service.ts","../src/sum.service.ts","../src/thousands-separator.service.ts","../src/time-zone-offset-value.vo.ts","../src/time.service.ts","../src/timestamp.vo.ts","../src/timezone.vo.ts","../src/ts-utils.ts","../src/visually-unambiguous-characters-generator.service.ts","../src/week-iso-id.vo.ts","../src/week.vo.ts","../src/weekday.vo.ts","../src/weight.vo.ts","../src/year-iso-id.vo.ts","../src/year.vo.ts","../src/z-score.service.ts"],"version":"5.9.3"}
|
package/dist/weight.vo.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { RoundingPort } from "./rounding.port";
|
|
2
2
|
export declare enum WeightUnit {
|
|
3
3
|
kg = "kg",
|
|
4
4
|
lb = "lb"
|
|
@@ -14,9 +14,9 @@ export declare class Weight {
|
|
|
14
14
|
static fromGrams(grams: number): Weight;
|
|
15
15
|
static zero(): Weight;
|
|
16
16
|
toGrams(): number;
|
|
17
|
-
toKilograms(rounding?:
|
|
18
|
-
toPounds(rounding?:
|
|
19
|
-
format(unit: WeightUnit, rounding?:
|
|
17
|
+
toKilograms(rounding?: RoundingPort): number;
|
|
18
|
+
toPounds(rounding?: RoundingPort): number;
|
|
19
|
+
format(unit: WeightUnit, rounding?: RoundingPort): string;
|
|
20
20
|
add(other: Weight): Weight;
|
|
21
21
|
subtract(other: Weight): Weight;
|
|
22
22
|
multiply(factor: number): Weight;
|
package/dist/weight.vo.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
|
-
import { RoundToDecimal } from "./rounding.
|
|
2
|
+
import { RoundToDecimal } from "./rounding.adapter";
|
|
3
3
|
const FiniteNumericValue = z.number().refine(Number.isFinite, { message: "Expected a finite number" });
|
|
4
4
|
const NonNegativeNumericValue = FiniteNumericValue.min(0, { message: "Must be greater than or equal to 0" });
|
|
5
5
|
const PositiveNumericValue = FiniteNumericValue.gt(0, { message: "Must be greater than 0" });
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { RoundingPort } from "./rounding.port";
|
|
2
|
+
export declare const ZScoreMinValuesError: "zscore.min.values";
|
|
2
3
|
export declare class ZScore {
|
|
3
4
|
private readonly rounding;
|
|
4
5
|
private readonly mean;
|
|
5
6
|
private readonly standardDeviation;
|
|
6
|
-
constructor(values: number[], rounding?:
|
|
7
|
+
constructor(values: number[], rounding?: RoundingPort);
|
|
7
8
|
calculate(value: number): number;
|
|
8
9
|
}
|
package/dist/z-score.service.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Mean } from "./mean.service";
|
|
2
2
|
import { PopulationStandardDeviation } from "./population-standard-deviation.service";
|
|
3
|
-
import { RoundToDecimal } from "./rounding.
|
|
3
|
+
import { RoundToDecimal } from "./rounding.adapter";
|
|
4
|
+
export const ZScoreMinValuesError = "zscore.min.values";
|
|
4
5
|
export class ZScore {
|
|
5
6
|
rounding;
|
|
6
7
|
mean;
|
|
@@ -8,7 +9,7 @@ export class ZScore {
|
|
|
8
9
|
constructor(values, rounding = new RoundToDecimal(2)) {
|
|
9
10
|
this.rounding = rounding;
|
|
10
11
|
if (values.length < 2)
|
|
11
|
-
throw new Error(
|
|
12
|
+
throw new Error(ZScoreMinValuesError);
|
|
12
13
|
this.mean = Mean.calculate(values);
|
|
13
14
|
this.standardDeviation = PopulationStandardDeviation.calculate(values);
|
|
14
15
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bgord/tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bartosz Gordon",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"preinstall": "bunx only-allow bun"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@biomejs/biome": "2.2.
|
|
24
|
+
"@biomejs/biome": "2.2.5",
|
|
25
25
|
"@commitlint/cli": "20.1.0",
|
|
26
26
|
"@commitlint/config-conventional": "20.0.0",
|
|
27
27
|
"@types/bun": "1.2.23",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"lefthook": "1.13.6",
|
|
32
32
|
"only-allow": "1.2.1",
|
|
33
33
|
"shellcheck": "4.1.0",
|
|
34
|
-
"typescript": "5.9.
|
|
34
|
+
"typescript": "5.9.3",
|
|
35
35
|
"zod": "4.1.11"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
package/readme.md
CHANGED
|
@@ -27,11 +27,11 @@ src/
|
|
|
27
27
|
├── age.vo.ts
|
|
28
28
|
├── api-key.vo.ts
|
|
29
29
|
├── basename.vo.ts
|
|
30
|
+
├── clock-format.service.ts
|
|
30
31
|
├── clock.vo.ts
|
|
31
32
|
├── date-calculator.service.ts
|
|
32
33
|
├── date-formatter.service.ts
|
|
33
34
|
├── date-range.vo.ts
|
|
34
|
-
├── dates-of-the-week.vo.ts
|
|
35
35
|
├── day-iso-id.vo.ts
|
|
36
36
|
├── day.vo.ts
|
|
37
37
|
├── directory-path-absolute.vo.ts
|
|
@@ -48,6 +48,7 @@ src/
|
|
|
48
48
|
├── filename-suffix.vo.ts
|
|
49
49
|
├── filename.vo.ts
|
|
50
50
|
├── height.vo.ts
|
|
51
|
+
├── hour-format.service.ts
|
|
51
52
|
├── hour.vo.ts
|
|
52
53
|
├── iban-mask.service.ts
|
|
53
54
|
├── iban.vo.ts
|
|
@@ -76,7 +77,8 @@ src/
|
|
|
76
77
|
├── relative-date.vo.ts
|
|
77
78
|
├── reordering.service.ts
|
|
78
79
|
├── revision.vo.ts
|
|
79
|
-
├── rounding.
|
|
80
|
+
├── rounding.adapter.ts
|
|
81
|
+
├── rounding.port.ts
|
|
80
82
|
├── simple-linear-regression.service.ts
|
|
81
83
|
├── size.vo.ts
|
|
82
84
|
├── stepper.service.ts
|
package/src/age.vo.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { differenceInYears } from "date-fns";
|
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
3
|
import type { TimestampType } from "./timestamp.vo";
|
|
4
4
|
|
|
5
|
-
export const AgeValueError = { error: "invalid.age" };
|
|
5
|
+
export const AgeValueError = { error: "invalid.age" } as const;
|
|
6
|
+
export const InvalidBirthdateInFutureError = "invalid.birthdate_in_future" as const;
|
|
7
|
+
export const InvalidBirthdateError = "invalid.birthdate" as const;
|
|
6
8
|
|
|
7
9
|
export class Age {
|
|
8
10
|
static readonly MIN = 1;
|
|
@@ -25,41 +27,43 @@ export class Age {
|
|
|
25
27
|
return this.value === other.value ? 0 : this.value < other.value ? -1 : 1;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
equals(
|
|
29
|
-
return this.
|
|
30
|
+
equals(other: Age): boolean {
|
|
31
|
+
return this.value === other.value;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
isOlderThan(
|
|
33
|
-
return this.
|
|
34
|
+
isOlderThan(other: Age): boolean {
|
|
35
|
+
return this.value > other.value;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
isYoungerThan(
|
|
37
|
-
return this.
|
|
38
|
+
isYoungerThan(other: Age): boolean {
|
|
39
|
+
return this.value < other.value;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
isAdult(
|
|
41
|
-
return this.
|
|
42
|
+
isAdult(minimumAge: Age): boolean {
|
|
43
|
+
return this.value >= minimumAge.value;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
static fromValue(candidate: number): Age {
|
|
45
47
|
return new Age(Age.AgeValue.parse(candidate));
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
static
|
|
49
|
-
if (params.birthdate > params.now) throw new Error(
|
|
50
|
-
|
|
51
|
-
const years = differenceInYears(new Date(params.now), new Date(params.birthdate));
|
|
52
|
-
|
|
53
|
-
return Age.fromValue(years);
|
|
50
|
+
static fromBirthdateEpochMs(params: { birthdate: TimestampType; now: TimestampType }): Age {
|
|
51
|
+
if (params.birthdate > params.now) throw new Error(InvalidBirthdateInFutureError);
|
|
52
|
+
return Age.fromValue(differenceInYears(params.now, params.birthdate));
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
static fromBirthdate(params: { birthdate: string; now: TimestampType }): Age {
|
|
57
|
-
const
|
|
56
|
+
const birthdateMs = new Date(params.birthdate).getTime();
|
|
58
57
|
|
|
59
|
-
if (
|
|
58
|
+
if (birthdateMs > params.now) throw new Error(InvalidBirthdateInFutureError);
|
|
59
|
+
return Age.fromValue(differenceInYears(params.now, birthdateMs));
|
|
60
|
+
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
toJSON(): number {
|
|
63
|
+
return this.get();
|
|
64
|
+
}
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
toString(): string {
|
|
67
|
+
return String(this.value);
|
|
64
68
|
}
|
|
65
69
|
}
|
package/src/api-key.vo.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const ApiKeyError = { error: "invalid.api.key" };
|
|
4
|
+
|
|
5
|
+
export const ApiKey = z
|
|
6
|
+
.string(ApiKeyError)
|
|
7
|
+
.trim()
|
|
8
|
+
.length(64, ApiKeyError)
|
|
9
|
+
.regex(/^[0-9a-f]{64}$/i, ApiKeyError)
|
|
10
|
+
.brand("ApiKey");
|
|
11
|
+
|
|
4
12
|
export type ApiKeyType = z.infer<typeof ApiKey>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Hour } from "./hour.vo";
|
|
2
|
+
import { HourFormatters } from "./hour-format.service";
|
|
3
|
+
import type { Minute } from "./minute.vo";
|
|
4
|
+
|
|
5
|
+
export type ClockFormatter = (hour: Hour, minute: Minute) => string;
|
|
6
|
+
|
|
7
|
+
enum ClockFormatterEnum {
|
|
8
|
+
TWENTY_FOUR_HOURS = "TWENTY_FOUR_HOURS",
|
|
9
|
+
TWELVE_HOURS = "TWELVE_HOURS",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ClockFormatters: Record<ClockFormatterEnum, ClockFormatter> = {
|
|
13
|
+
TWENTY_FOUR_HOURS: (hour, minute) => `${hour.toString()}:${minute.toString()}`,
|
|
14
|
+
TWELVE_HOURS: (hour, minute) => `${hour.format(HourFormatters.TWELVE_HOURS)}:${minute.toString()}`,
|
|
15
|
+
} as const;
|
package/src/clock.vo.ts
CHANGED
|
@@ -1,21 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ClockFormatter, ClockFormatters } from "./clock-format.service";
|
|
2
|
+
import { Hour } from "./hour.vo";
|
|
2
3
|
import { Minute } from "./minute.vo";
|
|
3
4
|
import type { TimestampType } from "./timestamp.vo";
|
|
4
5
|
|
|
5
|
-
export type ClockFormatter = (hour: Hour, minute: Minute) => string;
|
|
6
|
-
|
|
7
|
-
enum ClockFormatterEnum {
|
|
8
|
-
TWENTY_FOUR_HOURS = "TWENTY_FOUR_HOURS",
|
|
9
|
-
TWELVE_HOURS = "TWELVE_HOURS",
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const ClockFormatters: Record<ClockFormatterEnum, ClockFormatter> = {
|
|
13
|
-
TWENTY_FOUR_HOURS: (hour, minute) => `${hour.get().formatted}:${minute.get().formatted}`,
|
|
14
|
-
|
|
15
|
-
TWELVE_HOURS: (hour, minute) =>
|
|
16
|
-
`${hour.get(HourFormatters.TWELVE_HOURS).formatted}:${minute.get().formatted}`,
|
|
17
|
-
} as const;
|
|
18
|
-
|
|
19
6
|
export class Clock {
|
|
20
7
|
private readonly formatter: ClockFormatter;
|
|
21
8
|
|
|
@@ -27,48 +14,42 @@ export class Clock {
|
|
|
27
14
|
this.formatter = (formatter as ClockFormatter) ?? ClockFormatters.TWENTY_FOUR_HOURS;
|
|
28
15
|
}
|
|
29
16
|
|
|
30
|
-
static
|
|
31
|
-
const hour = Hour.
|
|
32
|
-
const minute = Minute.
|
|
17
|
+
static fromEpochMs(timestamp: TimestampType, formatter?: ClockFormatter): Clock {
|
|
18
|
+
const hour = Hour.fromEpochMs(timestamp);
|
|
19
|
+
const minute = Minute.fromEpochMs(timestamp);
|
|
33
20
|
return new Clock(hour, minute, formatter);
|
|
34
21
|
}
|
|
35
22
|
|
|
36
|
-
get(
|
|
37
|
-
|
|
23
|
+
get(): { hour: number; minute: number } {
|
|
24
|
+
return { hour: this.hour.get(), minute: this.minute.get() };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
format(formatter?: ClockFormatter): string {
|
|
28
|
+
const chosen = formatter ?? this.formatter;
|
|
29
|
+
return chosen(this.hour, this.minute);
|
|
30
|
+
}
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
formatted: format(this.hour, this.minute),
|
|
42
|
-
};
|
|
32
|
+
toString(): string {
|
|
33
|
+
return this.format();
|
|
43
34
|
}
|
|
44
35
|
|
|
45
36
|
equals(another: Clock): boolean {
|
|
46
|
-
return (
|
|
47
|
-
this.hour.get().raw === another.get().raw.hour && this.minute.get().raw === another.get().raw.minute
|
|
48
|
-
);
|
|
37
|
+
return this.hour.get() === another.hour.get() && this.minute.get() === another.minute.get();
|
|
49
38
|
}
|
|
50
39
|
|
|
51
40
|
isAfter(another: Clock): boolean {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
41
|
+
const thisHour = this.hour.get();
|
|
42
|
+
const otherHour = another.hour.get();
|
|
55
43
|
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return false;
|
|
44
|
+
if (thisHour !== otherHour) return thisHour > otherHour;
|
|
45
|
+
return this.minute.get() > another.minute.get();
|
|
61
46
|
}
|
|
62
47
|
|
|
63
48
|
isBefore(another: Clock): boolean {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (this.hour.get().raw === another.hour.get().raw && this.minute.get().raw < another.minute.get().raw) {
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
49
|
+
const thisHour = this.hour.get();
|
|
50
|
+
const otherHour = another.hour.get();
|
|
71
51
|
|
|
72
|
-
return
|
|
52
|
+
if (thisHour !== otherHour) return thisHour < otherHour;
|
|
53
|
+
return this.minute.get() < another.minute.get();
|
|
73
54
|
}
|
|
74
55
|
}
|
package/src/height.vo.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
|
-
import {
|
|
2
|
+
import { RoundToDecimal, RoundToNearest } from "./rounding.adapter";
|
|
3
|
+
import type { RoundingPort } from "./rounding.port";
|
|
3
4
|
|
|
4
5
|
const FiniteNumericValue = z.number().refine(Number.isFinite, { message: "Expected a finite number" });
|
|
5
6
|
const NonNegativeNumericValue = FiniteNumericValue.min(0, { message: "Must be greater than or equal to 0" });
|
|
@@ -22,7 +23,7 @@ export class Height {
|
|
|
22
23
|
|
|
23
24
|
private constructor(private readonly millimeters: number) {}
|
|
24
25
|
|
|
25
|
-
static fromCentimeters(centimeters: number, rounding:
|
|
26
|
+
static fromCentimeters(centimeters: number, rounding: RoundingPort = new RoundToNearest()): Height {
|
|
26
27
|
NonNegativeNumericValue.parse(centimeters);
|
|
27
28
|
const mmFloat = centimeters * Height.MILLIMETERS_PER_CENTIMETER;
|
|
28
29
|
const mmRounded = rounding.round(mmFloat);
|
|
@@ -30,7 +31,7 @@ export class Height {
|
|
|
30
31
|
return new Height(mmRounded);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
static fromFeetInches(feet: number, inches = 0, rounding:
|
|
34
|
+
static fromFeetInches(feet: number, inches = 0, rounding: RoundingPort = new RoundToNearest()): Height {
|
|
34
35
|
NonNegativeNumericValue.parse(feet);
|
|
35
36
|
NonNegativeNumericValue.parse(inches);
|
|
36
37
|
const totalInches = feet * Height.INCHES_PER_FOOT + inches;
|
|
@@ -40,7 +41,7 @@ export class Height {
|
|
|
40
41
|
return new Height(mmRounded);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
static fromMillimeters(millimeters: number, rounding:
|
|
44
|
+
static fromMillimeters(millimeters: number, rounding: RoundingPort = new RoundToNearest()): Height {
|
|
44
45
|
NonNegativeNumericValue.parse(millimeters);
|
|
45
46
|
const mmRounded = rounding.round(millimeters);
|
|
46
47
|
NonNegativeIntegerMillimeters.parse(mmRounded);
|
|
@@ -55,12 +56,12 @@ export class Height {
|
|
|
55
56
|
return this.millimeters;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
toCentimeters(rounding?:
|
|
59
|
+
toCentimeters(rounding?: RoundingPort): number {
|
|
59
60
|
const cm = this.millimeters / Height.MILLIMETERS_PER_CENTIMETER;
|
|
60
61
|
return rounding ? rounding.round(cm) : cm;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
toFeetInches(rounding:
|
|
64
|
+
toFeetInches(rounding: RoundingPort = new RoundToNearest()): {
|
|
64
65
|
feet: number;
|
|
65
66
|
inches: number;
|
|
66
67
|
} {
|
|
@@ -74,16 +75,16 @@ export class Height {
|
|
|
74
75
|
return { feet, inches };
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
format(unit: HeightUnit,
|
|
78
|
+
format(unit: HeightUnit, rounding?: RoundingPort): string {
|
|
78
79
|
return {
|
|
79
80
|
[HeightUnit.cm]: () => {
|
|
80
|
-
const
|
|
81
|
+
const chosen = rounding ?? new RoundToDecimal(1);
|
|
81
82
|
|
|
82
|
-
return `${this.toCentimeters(
|
|
83
|
+
return `${this.toCentimeters(chosen)} cm`;
|
|
83
84
|
},
|
|
84
85
|
[HeightUnit.ft_in]: () => {
|
|
85
|
-
const
|
|
86
|
-
const { feet, inches } = this.toFeetInches(
|
|
86
|
+
const chosen = rounding ?? new RoundToNearest();
|
|
87
|
+
const { feet, inches } = this.toFeetInches(chosen);
|
|
87
88
|
|
|
88
89
|
return `${feet}′${inches}″`;
|
|
89
90
|
},
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type HourFormatter = (value: number) => string;
|
|
2
|
+
|
|
3
|
+
enum HourFormatterEnum {
|
|
4
|
+
TWENTY_FOUR_HOURS = "TWENTY_FOUR_HOURS",
|
|
5
|
+
TWENTY_FOUR_HOURS_WO_PADDING = "TWENTY_FOUR_HOURS_WO_PADDING",
|
|
6
|
+
AM_PM = "AM_PM",
|
|
7
|
+
TWELVE_HOURS = "TWELVE_HOURS",
|
|
8
|
+
TWELVE_HOURS_WO_PADDING = "TWELVE_HOURS_WO_PADDING",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const HourFormatters: Record<HourFormatterEnum, HourFormatter> = {
|
|
12
|
+
TWENTY_FOUR_HOURS: (value) => value.toString().padStart(2, "0"),
|
|
13
|
+
TWENTY_FOUR_HOURS_WO_PADDING: (value) => value.toString(),
|
|
14
|
+
AM_PM: (value) => {
|
|
15
|
+
const twelveHourValue = value % 12 || 12;
|
|
16
|
+
const suffix = value < 12 ? "a.m." : "p.m.";
|
|
17
|
+
return `${twelveHourValue} ${suffix}`;
|
|
18
|
+
},
|
|
19
|
+
TWELVE_HOURS: (value) => (value % 12 || 12).toString().padStart(2, "0"),
|
|
20
|
+
TWELVE_HOURS_WO_PADDING: (value) => (value % 12 || 12).toString(),
|
|
21
|
+
} as const;
|
package/src/hour.vo.ts
CHANGED
|
@@ -1,73 +1,50 @@
|
|
|
1
|
+
import { type HourFormatter, HourFormatters } from "./hour-format.service";
|
|
1
2
|
import type { TimestampType } from "./timestamp.vo";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
export enum HourFormatterEnum {
|
|
6
|
-
TWENTY_FOUR_HOURS = "TWENTY_FOUR_HOURS",
|
|
7
|
-
TWENTY_FOUR_HOURS_WO_PADDING = "TWENTY_FOUR_HOURS_WO_PADDING",
|
|
8
|
-
AM_PM = "AM_PM",
|
|
9
|
-
TWELVE_HOURS = "TWELVE_HOURS",
|
|
10
|
-
TWELVE_HOURS_WO_PADDING = "TWELVE_HOURS_WO_PADDING",
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const HourFormatters: Record<HourFormatterEnum, HourFormatter> = {
|
|
14
|
-
TWENTY_FOUR_HOURS: (value) => value.toString().padStart(2, "0"),
|
|
15
|
-
|
|
16
|
-
TWENTY_FOUR_HOURS_WO_PADDING: (value) => value.toString(),
|
|
17
|
-
|
|
18
|
-
AM_PM: (value) => {
|
|
19
|
-
const twelveHour = value % 12 || 12;
|
|
20
|
-
return `${twelveHour.toString()} ${value < 12 ? "a.m." : "p.m."}`;
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
TWELVE_HOURS: (value) => (value % 12 || 12).toString().padStart(2, "0"),
|
|
24
|
-
|
|
25
|
-
TWELVE_HOURS_WO_PADDING: (value) => (value % 12 || 12).toString(),
|
|
26
|
-
} as const;
|
|
4
|
+
export const HourValueError = "invalid.hour" as const;
|
|
27
5
|
|
|
28
6
|
export class Hour {
|
|
29
7
|
private readonly value: number;
|
|
30
8
|
|
|
31
|
-
private readonly formatter: HourFormatter;
|
|
32
|
-
|
|
33
9
|
static readonly ZERO = new Hour(0);
|
|
34
|
-
|
|
35
10
|
static readonly MAX = new Hour(23);
|
|
36
11
|
|
|
37
|
-
constructor(candidate: number
|
|
38
|
-
if (!Number.isInteger(candidate)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
12
|
+
constructor(candidate: number) {
|
|
13
|
+
if (!Number.isInteger(candidate) || candidate < 0 || candidate >= 24) {
|
|
14
|
+
throw new Error(HourValueError);
|
|
15
|
+
}
|
|
42
16
|
this.value = candidate;
|
|
43
|
-
this.formatter = (formatter as HourFormatter) ?? HourFormatters.TWENTY_FOUR_HOURS;
|
|
44
17
|
}
|
|
45
18
|
|
|
46
|
-
|
|
47
|
-
|
|
19
|
+
static fromEpochMs(timestamp: TimestampType): Hour {
|
|
20
|
+
return new Hour(new Date(timestamp).getUTCHours());
|
|
21
|
+
}
|
|
48
22
|
|
|
49
|
-
|
|
23
|
+
get(): number {
|
|
24
|
+
return this.value;
|
|
50
25
|
}
|
|
51
26
|
|
|
52
|
-
|
|
53
|
-
return this.value
|
|
27
|
+
toString(): string {
|
|
28
|
+
return HourFormatters.TWENTY_FOUR_HOURS(this.value);
|
|
54
29
|
}
|
|
55
30
|
|
|
56
|
-
|
|
57
|
-
return this.value
|
|
31
|
+
format(formatter: HourFormatter): string {
|
|
32
|
+
return formatter(this.value);
|
|
58
33
|
}
|
|
59
34
|
|
|
60
|
-
|
|
61
|
-
return this.value
|
|
35
|
+
equals(another: Hour): boolean {
|
|
36
|
+
return this.value === another.value;
|
|
62
37
|
}
|
|
63
38
|
|
|
64
|
-
|
|
65
|
-
|
|
39
|
+
isAfter(another: Hour): boolean {
|
|
40
|
+
return this.value > another.value;
|
|
41
|
+
}
|
|
66
42
|
|
|
67
|
-
|
|
43
|
+
isBefore(another: Hour): boolean {
|
|
44
|
+
return this.value < another.value;
|
|
68
45
|
}
|
|
69
46
|
|
|
70
|
-
static list(
|
|
71
|
-
return Array.from({ length: 24 }
|
|
47
|
+
static list(): readonly Hour[] {
|
|
48
|
+
return Array.from({ length: 24 }, (_, index) => new Hour(index));
|
|
72
49
|
}
|
|
73
50
|
}
|
package/src/iban-mask.service.ts
CHANGED
|
@@ -6,10 +6,15 @@ export class IbanMask {
|
|
|
6
6
|
static censor(iban: IBAN): IbanMaskedType {
|
|
7
7
|
const value = iban.format();
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
const
|
|
9
|
+
const FIRST_SPACE_INDEX = 4;
|
|
10
|
+
const LAST_SPACE_START_INDEX = value.length - 5;
|
|
11
|
+
|
|
12
|
+
const start = value.slice(0, FIRST_SPACE_INDEX);
|
|
13
|
+
const middle = value.slice(FIRST_SPACE_INDEX + 1, LAST_SPACE_START_INDEX);
|
|
11
14
|
const end = value.slice(-4);
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
const maskedMiddle = middle.replace(/[A-Z0-9]/g, "*");
|
|
17
|
+
|
|
18
|
+
return `${start} ${maskedMiddle} ${end}`;
|
|
14
19
|
}
|
|
15
20
|
}
|