@bgord/tools 0.17.2 → 1.0.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-years.vo.d.ts +11 -0
- package/dist/age-years.vo.js +9 -0
- package/dist/age.vo.d.ts +11 -16
- package/dist/age.vo.js +20 -31
- package/dist/api-key.vo.d.ts +3 -1
- package/dist/api-key.vo.js +10 -5
- package/dist/basename.vo.d.ts +9 -9
- package/dist/basename.vo.js +22 -22
- package/dist/clock.vo.d.ts +10 -4
- package/dist/clock.vo.js +12 -14
- package/dist/date-calculator.service.d.ts +2 -1
- package/dist/date-formatter.service.d.ts +3 -4
- package/dist/date-range.vo.d.ts +7 -1
- package/dist/date-range.vo.js +5 -2
- package/dist/day-iso-id.vo.d.ts +5 -2
- package/dist/day-iso-id.vo.js +11 -7
- package/dist/day.vo.d.ts +4 -3
- package/dist/day.vo.js +18 -16
- package/dist/directory-path-absolute.vo.d.ts +10 -6
- package/dist/directory-path-absolute.vo.js +19 -17
- package/dist/directory-path-relative.vo.d.ts +10 -7
- package/dist/directory-path-relative.vo.js +18 -17
- package/dist/division-factor.vo.d.ts +7 -0
- package/dist/division-factor.vo.js +9 -0
- package/dist/duration-ms.vo.d.ts +6 -0
- package/dist/duration-ms.vo.js +3 -0
- package/dist/duration.service.d.ts +2 -14
- package/dist/duration.service.js +16 -35
- package/dist/email-mask.service.d.ts +1 -6
- package/dist/email-mask.service.js +6 -8
- package/dist/etags.vo.d.ts +4 -3
- package/dist/etags.vo.js +3 -3
- package/dist/extension.vo.d.ts +6 -4
- package/dist/extension.vo.js +15 -10
- package/dist/feature-flag-value.vo.d.ts +10 -0
- package/dist/feature-flag-value.vo.js +8 -0
- package/dist/feature-flag.vo.d.ts +1 -7
- package/dist/feature-flag.vo.js +1 -7
- package/dist/file-path-absolute-schema.vo.d.ts +10 -7
- package/dist/file-path-absolute-schema.vo.js +17 -17
- package/dist/file-path-relative-schema.vo.d.ts +10 -7
- package/dist/file-path-relative-schema.vo.js +14 -12
- package/dist/file-path.vo.d.ts +4 -4
- package/dist/file-path.vo.js +8 -8
- package/dist/filename-from-string.vo.d.ts +4 -2
- package/dist/filename-from-string.vo.js +10 -8
- package/dist/filename-suffix.vo.d.ts +7 -3
- package/dist/filename-suffix.vo.js +13 -7
- package/dist/filename.vo.d.ts +2 -0
- package/dist/filename.vo.js +8 -2
- package/dist/height-milimiters.vo.d.ts +6 -0
- package/dist/height-milimiters.vo.js +10 -0
- package/dist/height.vo.d.ts +3 -20
- package/dist/height.vo.js +11 -62
- package/dist/hour-format.service.js +1 -1
- package/dist/hour-schema.vo.d.ts +7 -0
- package/dist/hour-schema.vo.js +8 -0
- package/dist/hour.vo.d.ts +4 -3
- package/dist/hour.vo.js +8 -8
- package/dist/iban-mask.service.d.ts +1 -3
- package/dist/iban-mask.service.js +2 -2
- package/dist/iban-schema.vo.d.ts +7 -0
- package/dist/iban-schema.vo.js +10 -0
- package/dist/iban.vo.d.ts +4 -10
- package/dist/iban.vo.js +6 -13
- package/dist/image.vo.d.ts +6 -4
- package/dist/image.vo.js +13 -12
- package/dist/index.d.ts +24 -2
- package/dist/index.js +24 -2
- package/dist/language.vo.d.ts +2 -1
- package/dist/language.vo.js +6 -4
- package/dist/linear-regression.service.d.ts +27 -0
- package/dist/{simple-linear-regression.service.js → linear-regression.service.js} +17 -15
- package/dist/mean.service.d.ts +3 -1
- package/dist/mean.service.js +3 -4
- package/dist/mime-types.vo.d.ts +1 -2
- package/dist/mime-value.vo.d.ts +9 -0
- package/dist/mime-value.vo.js +9 -0
- package/dist/mime.vo.d.ts +11 -17
- package/dist/mime.vo.js +10 -27
- package/dist/min-max-scaler.service.d.ts +7 -5
- package/dist/min-max-scaler.service.js +12 -10
- package/dist/minute-schema.vo.d.ts +7 -0
- package/dist/minute-schema.vo.js +8 -0
- package/dist/minute.vo.d.ts +4 -3
- package/dist/minute.vo.js +8 -8
- package/dist/money-amount.vo.d.ts +7 -0
- package/dist/money-amount.vo.js +7 -0
- package/dist/money.vo.d.ts +9 -18
- package/dist/money.vo.js +14 -27
- package/dist/month-iso-id.vo.d.ts +4 -2
- package/dist/month-iso-id.vo.js +13 -7
- package/dist/month.vo.d.ts +4 -3
- package/dist/month.vo.js +21 -21
- package/dist/multiplication-factor.vo.d.ts +7 -0
- package/dist/multiplication-factor.vo.js +9 -0
- package/dist/object-key.vo.d.ts +9 -6
- package/dist/object-key.vo.js +20 -19
- package/dist/outlier-detector.service.d.ts +3 -1
- package/dist/outlier-detector.service.js +2 -2
- package/dist/package-version-schema.vo.d.ts +11 -0
- package/dist/package-version-schema.vo.js +15 -0
- package/dist/package-version.vo.d.ts +11 -20
- package/dist/package-version.vo.js +11 -20
- package/dist/pagination-page.vo.d.ts +6 -0
- package/dist/pagination-page.vo.js +7 -0
- package/dist/pagination-skip.vo.d.ts +7 -0
- package/dist/pagination-skip.vo.js +9 -0
- package/dist/pagination-take.vo.d.ts +7 -0
- package/dist/pagination-take.vo.js +9 -0
- package/dist/pagination.service.d.ts +3 -8
- package/dist/pagination.service.js +5 -12
- package/dist/percentage.service.d.ts +3 -1
- package/dist/percentage.service.js +2 -2
- package/dist/population-standard-deviation.service.d.ts +3 -1
- package/dist/population-standard-deviation.service.js +5 -4
- package/dist/quarter-iso-id.vo.d.ts +3 -2
- package/dist/quarter-iso-id.vo.js +7 -9
- package/dist/quarter.vo.d.ts +2 -1
- package/dist/quarter.vo.js +10 -7
- package/dist/random.service.d.ts +3 -4
- package/dist/random.service.js +5 -11
- package/dist/rate-limiter.service.d.ts +2 -2
- package/dist/rate-limiter.service.js +8 -8
- package/dist/reordering-item-position-value.vo.d.ts +6 -0
- package/dist/reordering-item-position-value.vo.js +6 -0
- package/dist/reordering.service.d.ts +7 -23
- package/dist/reordering.service.js +15 -24
- package/dist/revision-value.vo.d.ts +7 -0
- package/dist/revision-value.vo.js +6 -0
- package/dist/revision.vo.d.ts +6 -13
- package/dist/revision.vo.js +10 -22
- package/dist/rounding.adapter.d.ts +7 -2
- package/dist/rounding.adapter.js +13 -5
- package/dist/size-bytes.vo.d.ts +6 -0
- package/dist/size-bytes.vo.js +7 -0
- package/dist/size.vo.d.ts +15 -15
- package/dist/size.vo.js +41 -51
- package/dist/stopwatch.service.d.ts +3 -1
- package/dist/stopwatch.service.js +2 -2
- package/dist/sum.service.js +8 -8
- package/dist/thousands-separator.service.js +4 -1
- package/dist/time.service.d.ts +8 -0
- package/dist/time.service.js +13 -0
- package/dist/timestamp.vo.d.ts +1 -1
- package/dist/timestamp.vo.js +4 -5
- package/dist/timezone.vo.d.ts +4 -1
- package/dist/timezone.vo.js +12 -6
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/week-iso-id.vo.d.ts +4 -2
- package/dist/week-iso-id.vo.js +15 -9
- package/dist/week.vo.d.ts +4 -3
- package/dist/week.vo.js +21 -22
- package/dist/weekday.vo.d.ts +1 -1
- package/dist/weekday.vo.js +6 -8
- package/dist/weight-grams.vo.d.ts +7 -0
- package/dist/weight-grams.vo.js +7 -0
- package/dist/weight.vo.d.ts +12 -35
- package/dist/weight.vo.js +23 -72
- package/dist/year-iso-id.vo.d.ts +3 -2
- package/dist/year-iso-id.vo.js +6 -4
- package/dist/year.vo.d.ts +5 -6
- package/dist/year.vo.js +21 -26
- package/dist/z-score.service.d.ts +3 -1
- package/dist/z-score.service.js +2 -2
- package/package.json +4 -4
- package/readme.md +21 -2
- package/src/age-years.vo.ts +14 -0
- package/src/age.vo.ts +22 -35
- package/src/api-key.vo.ts +11 -5
- package/src/basename.vo.ts +24 -22
- package/src/clock.vo.ts +16 -17
- package/src/date-calculator.service.ts +2 -1
- package/src/date-formatter.service.ts +4 -5
- package/src/date-range.vo.ts +6 -2
- package/src/day-iso-id.vo.ts +12 -8
- package/src/day.vo.ts +27 -24
- package/src/directory-path-absolute.vo.ts +23 -18
- package/src/directory-path-relative.vo.ts +21 -18
- package/src/division-factor.vo.ts +13 -0
- package/src/duration-ms.vo.ts +7 -0
- package/src/duration.service.ts +16 -40
- package/src/email-mask.service.ts +7 -15
- package/src/etags.vo.ts +4 -5
- package/src/extension.vo.ts +17 -10
- package/src/feature-flag-value.vo.ts +12 -0
- package/src/feature-flag.vo.ts +1 -9
- package/src/file-path-absolute-schema.vo.ts +18 -17
- package/src/file-path-relative-schema.vo.ts +15 -12
- package/src/file-path.vo.ts +8 -8
- package/src/filename-from-string.vo.ts +12 -9
- package/src/filename-suffix.vo.ts +14 -7
- package/src/filename.vo.ts +11 -2
- package/src/height-milimiters.vo.ts +12 -0
- package/src/height.vo.ts +12 -83
- package/src/hour-format.service.ts +2 -1
- package/src/hour-schema.vo.ts +12 -0
- package/src/hour.vo.ts +12 -12
- package/src/iban-mask.service.ts +3 -5
- package/src/iban-schema.vo.ts +15 -0
- package/src/iban.vo.ts +9 -22
- package/src/image.vo.ts +14 -12
- package/src/index.ts +24 -2
- package/src/language.vo.ts +7 -4
- package/src/linear-regression.service.ts +71 -0
- package/src/mean.service.ts +3 -5
- package/src/mime-types.vo.ts +1 -3
- package/src/mime-value.vo.ts +12 -0
- package/src/mime.vo.ts +12 -33
- package/src/min-max-scaler.service.ts +13 -11
- package/src/minute-schema.vo.ts +12 -0
- package/src/minute.vo.ts +12 -12
- package/src/money-amount.vo.ts +11 -0
- package/src/money.vo.ts +20 -38
- package/src/month-iso-id.vo.ts +14 -7
- package/src/month.vo.ts +25 -24
- package/src/multiplication-factor.vo.ts +13 -0
- package/src/object-key.vo.ts +25 -21
- package/src/outlier-detector.service.ts +2 -2
- package/src/package-version-schema.vo.ts +21 -0
- package/src/package-version.vo.ts +17 -33
- package/src/pagination-page.vo.ts +11 -0
- package/src/pagination-skip.vo.ts +13 -0
- package/src/pagination-take.vo.ts +13 -0
- package/src/pagination.service.ts +5 -22
- package/src/percentage.service.ts +2 -2
- package/src/population-standard-deviation.service.ts +5 -4
- package/src/quarter-iso-id.vo.ts +7 -10
- package/src/quarter.vo.ts +14 -9
- package/src/random.service.ts +6 -9
- package/src/rate-limiter.service.ts +9 -8
- package/src/reordering-item-position-value.vo.ts +10 -0
- package/src/reordering.service.ts +19 -28
- package/src/revision-value.vo.ts +10 -0
- package/src/revision.vo.ts +10 -25
- package/src/rounding.adapter.ts +16 -3
- package/src/size-bytes.vo.ts +11 -0
- package/src/size.vo.ts +43 -54
- package/src/stopwatch.service.ts +3 -3
- package/src/sum.service.ts +8 -8
- package/src/thousands-separator.service.ts +4 -1
- package/src/time.service.ts +15 -0
- package/src/timestamp.vo.ts +4 -5
- package/src/timezone.vo.ts +12 -6
- package/src/week-iso-id.vo.ts +16 -12
- package/src/week.vo.ts +26 -28
- package/src/weekday.vo.ts +6 -9
- package/src/weight-grams.vo.ts +11 -0
- package/src/weight.vo.ts +28 -85
- package/src/year-iso-id.vo.ts +7 -4
- package/src/year.vo.ts +27 -33
- package/src/z-score.service.ts +2 -2
- package/dist/simple-linear-regression.service.d.ts +0 -25
- package/dist/streak-calculator.service.d.ts +0 -13
- package/dist/streak-calculator.service.js +0 -22
- package/src/simple-linear-regression.service.ts +0 -69
- package/src/streak-calculator.service.ts +0 -32
package/src/etags.vo.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const RevisionValue = z.number().int().min(0);
|
|
4
|
-
type RevisionValueType = z.infer<typeof RevisionValue>;
|
|
1
|
+
import { RevisionValue, type RevisionValueType } from "./revision-value.vo";
|
|
5
2
|
|
|
6
3
|
type ETagValueType = string;
|
|
7
4
|
|
|
@@ -28,6 +25,8 @@ export class ETag {
|
|
|
28
25
|
|
|
29
26
|
export type WeakETagValueType = string;
|
|
30
27
|
|
|
28
|
+
export const WeakETagError = { Invalid: "weak.etag.invalid" } as const;
|
|
29
|
+
|
|
31
30
|
export class WeakETag {
|
|
32
31
|
static HEADER_NAME = "ETag";
|
|
33
32
|
|
|
@@ -40,7 +39,7 @@ export class WeakETag {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
static fromHeader(value?: WeakETagValueType): WeakETag | null {
|
|
43
|
-
if (!value?.startsWith("W/")) throw Error(
|
|
42
|
+
if (!value?.startsWith("W/")) throw new Error(WeakETagError.Invalid);
|
|
44
43
|
|
|
45
44
|
const candidate = Number(value.split("W/")[1]);
|
|
46
45
|
|
package/src/extension.vo.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export const ExtensionError = {
|
|
4
|
+
Type: "extension.type",
|
|
5
|
+
Empty: "extension.empty",
|
|
6
|
+
TooLong: "extension.too.long",
|
|
7
|
+
BadChars: "extension.bad.chars",
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
// Lowercase letters and digits allowed
|
|
11
|
+
const EXTENSION_WHITELIST = /^[a-z0-9]+$/;
|
|
12
|
+
|
|
13
|
+
const LEADING_DOT_FILE = /^\./;
|
|
7
14
|
|
|
8
15
|
export const Extension = z
|
|
9
|
-
.string(
|
|
10
|
-
.trim()
|
|
16
|
+
.string(ExtensionError.Type)
|
|
11
17
|
.toLowerCase()
|
|
12
|
-
.
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
.
|
|
18
|
+
.min(2, ExtensionError.Empty)
|
|
19
|
+
.max(16, ExtensionError.TooLong)
|
|
20
|
+
// Transform ".png" -> "png"
|
|
21
|
+
.transform((value) => value.replace(LEADING_DOT_FILE, ""))
|
|
22
|
+
.refine((value) => EXTENSION_WHITELIST.test(value), ExtensionError.BadChars)
|
|
16
23
|
.brand("Extension");
|
|
17
24
|
|
|
18
25
|
export type ExtensionType = z.infer<typeof Extension>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
export const FeatureFlagValueError = { Invalid: "feature.flag.value.invalid" } as const;
|
|
4
|
+
|
|
5
|
+
export enum FeatureFlagEnum {
|
|
6
|
+
yes = "yes",
|
|
7
|
+
no = "no",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const FeatureFlagValue = z.enum(FeatureFlagEnum, FeatureFlagValueError.Invalid);
|
|
11
|
+
|
|
12
|
+
export type FeatureFlagValueType = z.infer<typeof FeatureFlagValue>;
|
package/src/feature-flag.vo.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export enum FeatureFlagEnum {
|
|
4
|
-
yes = "yes",
|
|
5
|
-
no = "no",
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const FeatureFlagValue = z.enum(FeatureFlagEnum);
|
|
9
|
-
export type FeatureFlagValueType = z.infer<typeof FeatureFlagValue>;
|
|
1
|
+
import { FeatureFlagEnum, type FeatureFlagValueType } from "./feature-flag-value.vo";
|
|
10
2
|
|
|
11
3
|
export class FeatureFlag {
|
|
12
4
|
static isEnabled(flag: FeatureFlagValueType): boolean {
|
|
@@ -2,30 +2,31 @@ import { z } from "zod/v4";
|
|
|
2
2
|
import { DirectoryPathAbsoluteSchema } from "./directory-path-absolute.vo";
|
|
3
3
|
import { Filename } from "./filename.vo";
|
|
4
4
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
export const FilePathAbsoluteSchemaError = {
|
|
6
|
+
Type: "file.path.absolute.type",
|
|
7
|
+
LeadingSlash: "file.path.absolute.leading.slash",
|
|
8
|
+
TrailingSlash: "file.path.absolute.trailing.slash",
|
|
9
|
+
BackslashForbidden: "file.path.absolute.backslash.forbidden",
|
|
10
|
+
Empty: "file.path.absolute.empty",
|
|
11
|
+
} as const;
|
|
9
12
|
|
|
10
13
|
export const FilePathAbsoluteSchema = z
|
|
11
|
-
.string(
|
|
12
|
-
.
|
|
13
|
-
.refine((value) => value.startsWith("/"),
|
|
14
|
-
.refine((value) => !value.
|
|
15
|
-
|
|
16
|
-
.transform((value) => value.replace(/\/{2,}/g, "/"))
|
|
17
|
-
// keep "/" as-is; otherwise remove a trailing slash
|
|
18
|
-
.transform((value) => (value !== "/" && value.endsWith("/") ? value.slice(0, -1) : value))
|
|
19
|
-
.refine((value) => value !== "/", AbsFilePathMissingFilenameError)
|
|
14
|
+
.string(FilePathAbsoluteSchemaError.Type)
|
|
15
|
+
.min(1, FilePathAbsoluteSchemaError.Empty)
|
|
16
|
+
.refine((value) => value.startsWith("/"), FilePathAbsoluteSchemaError.LeadingSlash)
|
|
17
|
+
.refine((value) => !value.endsWith("/"), FilePathAbsoluteSchemaError.TrailingSlash)
|
|
18
|
+
.refine((value) => !value.includes("\\"), FilePathAbsoluteSchemaError.BackslashForbidden)
|
|
20
19
|
.transform((normalized) => {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const
|
|
20
|
+
const index = normalized.lastIndexOf("/");
|
|
21
|
+
|
|
22
|
+
const directoryCandidate = index === 0 ? "/" : normalized.slice(0, index);
|
|
23
|
+
const filenameCandidate = normalized.slice(index + 1);
|
|
24
24
|
|
|
25
25
|
const directory = DirectoryPathAbsoluteSchema.parse(directoryCandidate);
|
|
26
26
|
const filename = Filename.fromString(filenameCandidate);
|
|
27
27
|
|
|
28
28
|
return { directory, filename };
|
|
29
|
-
})
|
|
29
|
+
})
|
|
30
|
+
.brand("FilePathAbsoluteSchema");
|
|
30
31
|
|
|
31
32
|
export type FilePathAbsoluteType = z.infer<typeof FilePathAbsoluteSchema>;
|
|
@@ -2,21 +2,23 @@ import { z } from "zod/v4";
|
|
|
2
2
|
import { DirectoryPathRelativeSchema } from "./directory-path-relative.vo";
|
|
3
3
|
import { Filename } from "./filename.vo";
|
|
4
4
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
export const FilePathRelativeSchemaError = {
|
|
6
|
+
Type: "file.path.relative.type",
|
|
7
|
+
LeadingSlash: "file.path.relative.leading.slash",
|
|
8
|
+
BackslashForbidden: "file.path.relative.backslash.forbidden",
|
|
9
|
+
RequiresDirectory: "file.path.relative.requires.directory",
|
|
10
|
+
Empty: "file.path.relative.empty",
|
|
11
|
+
} as const;
|
|
9
12
|
|
|
10
13
|
export const FilePathRelativeSchema = z
|
|
11
|
-
.string(
|
|
12
|
-
.
|
|
13
|
-
.refine((value) => !value.startsWith("/"),
|
|
14
|
-
.refine((value) => !value.includes("\\"),
|
|
15
|
-
|
|
16
|
-
.transform((value) => value.replace(/\/{2,}/g, "/").replace(/^\/+|\/+$/g, ""))
|
|
17
|
-
.refine((value) => value.includes("/"), RelFilePathRequiresDirectoryError)
|
|
14
|
+
.string(FilePathRelativeSchemaError.Type)
|
|
15
|
+
.min(1, FilePathRelativeSchemaError.Empty)
|
|
16
|
+
.refine((value) => !value.startsWith("/"), FilePathRelativeSchemaError.LeadingSlash)
|
|
17
|
+
.refine((value) => !value.includes("\\"), FilePathRelativeSchemaError.BackslashForbidden)
|
|
18
|
+
.refine((value) => value.includes("/"), FilePathRelativeSchemaError.RequiresDirectory)
|
|
18
19
|
.transform((normalized) => {
|
|
19
20
|
const lastSlashIndex = normalized.lastIndexOf("/");
|
|
21
|
+
|
|
20
22
|
const directoryCandidate = normalized.slice(0, lastSlashIndex);
|
|
21
23
|
const filenameCandidate = normalized.slice(lastSlashIndex + 1);
|
|
22
24
|
|
|
@@ -24,6 +26,7 @@ export const FilePathRelativeSchema = z
|
|
|
24
26
|
const filename = Filename.fromString(filenameCandidate);
|
|
25
27
|
|
|
26
28
|
return { directory, filename };
|
|
27
|
-
})
|
|
29
|
+
})
|
|
30
|
+
.brand("FilePathRelativeSchema");
|
|
28
31
|
|
|
29
32
|
export type FilePathRelativeType = z.infer<typeof FilePathRelativeSchema>;
|
package/src/file-path.vo.ts
CHANGED
|
@@ -20,10 +20,10 @@ export class FilePathRelative {
|
|
|
20
20
|
return new FilePathRelative(directory, filename);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
static fromString(
|
|
24
|
-
const
|
|
23
|
+
static fromString(candidate: string): FilePathRelative {
|
|
24
|
+
const schema = FilePathRelativeSchema.parse(candidate);
|
|
25
25
|
|
|
26
|
-
return new FilePathRelative(
|
|
26
|
+
return new FilePathRelative(schema.directory, schema.filename);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
get(): string {
|
|
@@ -38,7 +38,7 @@ export class FilePathRelative {
|
|
|
38
38
|
return this.filename;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
withDirectory(newDirectory: DirectoryPathRelativeType): FilePathRelative {
|
|
42
42
|
return new FilePathRelative(newDirectory, this.filename);
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -67,10 +67,10 @@ export class FilePathAbsolute {
|
|
|
67
67
|
return new FilePathAbsolute(directory, filename);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
static fromString(
|
|
71
|
-
const
|
|
70
|
+
static fromString(candidate: string): FilePathAbsolute {
|
|
71
|
+
const schema = FilePathAbsoluteSchema.parse(candidate);
|
|
72
72
|
|
|
73
|
-
return new FilePathAbsolute(
|
|
73
|
+
return new FilePathAbsolute(schema.directory, schema.filename);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
get(): string {
|
|
@@ -86,7 +86,7 @@ export class FilePathAbsolute {
|
|
|
86
86
|
return this.filename;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
withDirectory(newDirectory: DirectoryPathAbsoluteType): FilePathAbsolute {
|
|
90
90
|
return new FilePathAbsolute(newDirectory, this.filename);
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -2,19 +2,22 @@ import { z } from "zod/v4";
|
|
|
2
2
|
import { Basename } from "./basename.vo";
|
|
3
3
|
import { Extension } from "./extension.vo";
|
|
4
4
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
5
|
+
export const FilenameFromStringError = {
|
|
6
|
+
Type: "filename.from.string.type",
|
|
7
|
+
Invalid: "filename.from.string.Invalid",
|
|
8
|
+
} as const;
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const index = value.lastIndexOf(".");
|
|
10
|
+
// .+ at least one character, advances to the last dot
|
|
11
|
+
// .
|
|
12
|
+
// .+ at least one character
|
|
13
|
+
const DOT_WITH_SIDES = /^.+\..+$/;
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
export const FilenameFromString = z
|
|
16
|
+
.string(FilenameFromStringError.Type)
|
|
17
|
+
.regex(DOT_WITH_SIDES, FilenameFromStringError.Invalid)
|
|
16
18
|
.transform((value) => {
|
|
17
19
|
const index = value.lastIndexOf(".");
|
|
20
|
+
|
|
18
21
|
const basename = Basename.parse(value.slice(0, index));
|
|
19
22
|
const extension = Extension.parse(value.slice(index + 1));
|
|
20
23
|
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
|
|
3
|
+
export const FilenameSuffixError = {
|
|
4
|
+
Type: "suffix.type",
|
|
5
|
+
Empty: "suffix.empty",
|
|
6
|
+
TooLong: "suffix.too.long",
|
|
7
|
+
BadChars: "suffix.bad.chars",
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
// Letters, digits, underscores, and hyphens allowed
|
|
11
|
+
const FILENAME_SUFFIX_WHITELIST = /^[a-zA-Z0-9_-]+$/;
|
|
5
12
|
|
|
6
13
|
export const FilenameSuffix = z
|
|
7
|
-
.string(
|
|
8
|
-
.
|
|
9
|
-
.
|
|
10
|
-
.
|
|
11
|
-
.brand("
|
|
14
|
+
.string(FilenameSuffixError.Type)
|
|
15
|
+
.min(1, FilenameSuffixError.Empty)
|
|
16
|
+
.max(32, FilenameSuffixError.TooLong)
|
|
17
|
+
.regex(FILENAME_SUFFIX_WHITELIST, FilenameSuffixError.BadChars)
|
|
18
|
+
.brand("FilenameSuffix");
|
|
12
19
|
|
|
13
20
|
export type FilenameSuffixType = z.infer<typeof FilenameSuffix>;
|
package/src/filename.vo.ts
CHANGED
|
@@ -18,8 +18,9 @@ export class Filename {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
static fromString(candidate: string): Filename {
|
|
21
|
-
const
|
|
22
|
-
|
|
21
|
+
const filename = FilenameFromString.parse(candidate);
|
|
22
|
+
|
|
23
|
+
return new Filename(filename.basename, filename.extension);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
get(): string {
|
|
@@ -54,4 +55,12 @@ export class Filename {
|
|
|
54
55
|
|
|
55
56
|
return new Filename(basename, this.extension);
|
|
56
57
|
}
|
|
58
|
+
|
|
59
|
+
toString(): string {
|
|
60
|
+
return this.get();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
toJSON(): string {
|
|
64
|
+
return this.get();
|
|
65
|
+
}
|
|
57
66
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
export const HeightMillimetersError = {
|
|
4
|
+
Type: "height.millimeters.type",
|
|
5
|
+
Invalid: "height.millimeters.invalid",
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
8
|
+
export const HeightMillimeters = z
|
|
9
|
+
.number(HeightMillimetersError.Type)
|
|
10
|
+
.int(HeightMillimetersError.Type)
|
|
11
|
+
.min(0, HeightMillimetersError.Invalid)
|
|
12
|
+
.brand("HeightMillimeters");
|
package/src/height.vo.ts
CHANGED
|
@@ -1,64 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HeightMillimeters } from "./height-milimiters.vo";
|
|
2
2
|
import { RoundToDecimal, RoundToNearest } from "./rounding.adapter";
|
|
3
3
|
import type { RoundingPort } from "./rounding.port";
|
|
4
4
|
|
|
5
|
-
const NonFiniteNumberError = { error: "number.non_finite" } as const;
|
|
6
|
-
const NumberNegativeError = { error: "number.negative" } as const;
|
|
7
|
-
const MillimetersIntegerNonNegativeError = { error: "millimeters.integer_non_negative" } as const;
|
|
8
|
-
const IntegerNonNegativeError = { error: "integer.non_negative" } as const;
|
|
9
|
-
|
|
10
|
-
const HeightFiniteNumber = z.number(NonFiniteNumberError).refine(Number.isFinite, NonFiniteNumberError);
|
|
11
|
-
|
|
12
|
-
const HeightNonNegativeQuantity = HeightFiniteNumber.min(0, NumberNegativeError);
|
|
13
|
-
|
|
14
|
-
const HeightCanonicalMillimeters = HeightFiniteNumber.int(MillimetersIntegerNonNegativeError).min(
|
|
15
|
-
0,
|
|
16
|
-
MillimetersIntegerNonNegativeError,
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
const HeightRoundedWholeInches = HeightFiniteNumber.int(IntegerNonNegativeError).min(
|
|
20
|
-
0,
|
|
21
|
-
IntegerNonNegativeError,
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
export enum HeightUnit {
|
|
25
|
-
cm = "cm",
|
|
26
|
-
ft_in = "ft_in",
|
|
27
|
-
}
|
|
28
|
-
|
|
29
5
|
export class Height {
|
|
30
6
|
private static readonly MILLIMETERS_PER_CENTIMETER = 10;
|
|
31
|
-
private static readonly MILLIMETERS_PER_INCH = 25.4;
|
|
32
|
-
private static readonly INCHES_PER_FOOT = 12;
|
|
33
7
|
|
|
34
8
|
private constructor(private readonly millimeters: number) {}
|
|
35
9
|
|
|
36
10
|
static fromCentimeters(centimeters: number, rounding: RoundingPort = new RoundToNearest()): Height {
|
|
37
|
-
const
|
|
38
|
-
const millimetersFloat = validatedCentimeters * Height.MILLIMETERS_PER_CENTIMETER;
|
|
39
|
-
const millimetersRounded = rounding.round(millimetersFloat);
|
|
40
|
-
const validatedMillimeters = HeightCanonicalMillimeters.parse(millimetersRounded);
|
|
11
|
+
const millimeters = rounding.round(centimeters * Height.MILLIMETERS_PER_CENTIMETER);
|
|
41
12
|
|
|
42
|
-
return new Height(
|
|
13
|
+
return new Height(HeightMillimeters.parse(millimeters));
|
|
43
14
|
}
|
|
44
15
|
|
|
45
|
-
static
|
|
46
|
-
|
|
47
|
-
const validatedInches = HeightNonNegativeQuantity.parse(inches);
|
|
48
|
-
const totalInches = validatedFeet * Height.INCHES_PER_FOOT + validatedInches;
|
|
49
|
-
const millimetersFloat = totalInches * Height.MILLIMETERS_PER_INCH;
|
|
50
|
-
const millimetersRounded = rounding.round(millimetersFloat);
|
|
51
|
-
const validatedMillimeters = HeightCanonicalMillimeters.parse(millimetersRounded);
|
|
52
|
-
|
|
53
|
-
return new Height(validatedMillimeters);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
static fromMillimeters(millimeters: number, rounding: RoundingPort = new RoundToNearest()): Height {
|
|
57
|
-
const validatedMillimetersInput = HeightNonNegativeQuantity.parse(millimeters);
|
|
58
|
-
const millimetersRounded = rounding.round(validatedMillimetersInput);
|
|
59
|
-
const validatedMillimeters = HeightCanonicalMillimeters.parse(millimetersRounded);
|
|
60
|
-
|
|
61
|
-
return new Height(validatedMillimeters);
|
|
16
|
+
static fromMillimeters(millimeters: number): Height {
|
|
17
|
+
return new Height(HeightMillimeters.parse(millimeters));
|
|
62
18
|
}
|
|
63
19
|
|
|
64
20
|
static zero(): Height {
|
|
@@ -80,44 +36,21 @@ export class Height {
|
|
|
80
36
|
return centimeters;
|
|
81
37
|
}
|
|
82
38
|
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const totalWholeInches = HeightRoundedWholeInches.parse(totalInchesRounded);
|
|
39
|
+
format(rounding?: RoundingPort): string {
|
|
40
|
+
const chosen = rounding ?? new RoundToDecimal(1);
|
|
41
|
+
const value = this.toCentimeters(chosen);
|
|
87
42
|
|
|
88
|
-
|
|
89
|
-
const inches = totalWholeInches % Height.INCHES_PER_FOOT;
|
|
90
|
-
|
|
91
|
-
return { feet, inches };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
format(unit: HeightUnit, rounding?: RoundingPort): string {
|
|
95
|
-
if (unit === HeightUnit.cm) {
|
|
96
|
-
const chosen = rounding ?? new RoundToDecimal(1);
|
|
97
|
-
const value = this.toCentimeters(chosen);
|
|
98
|
-
|
|
99
|
-
return `${value} cm`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const chosen = rounding ?? new RoundToNearest();
|
|
103
|
-
const parts = this.toFeetInches(chosen);
|
|
104
|
-
|
|
105
|
-
return `${parts.feet}′${parts.inches}″`;
|
|
43
|
+
return `${value} cm`;
|
|
106
44
|
}
|
|
107
45
|
|
|
108
46
|
toString(): string {
|
|
109
|
-
return this.format(
|
|
47
|
+
return this.format(new RoundToDecimal(1));
|
|
110
48
|
}
|
|
111
49
|
|
|
112
50
|
equals(another: Height): boolean {
|
|
113
51
|
return this.millimeters === another.millimeters;
|
|
114
52
|
}
|
|
115
53
|
|
|
116
|
-
compare(another: Height): -1 | 0 | 1 {
|
|
117
|
-
if (this.equals(another)) return 0;
|
|
118
|
-
return this.millimeters < another.millimeters ? -1 : 1;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
54
|
greaterThan(another: Height): boolean {
|
|
122
55
|
return this.millimeters > another.millimeters;
|
|
123
56
|
}
|
|
@@ -130,11 +63,7 @@ export class Height {
|
|
|
130
63
|
return this.millimeters === 0;
|
|
131
64
|
}
|
|
132
65
|
|
|
133
|
-
toJSON():
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
static fromJSON(input: { mm: number }): Height {
|
|
138
|
-
return Height.fromMillimeters(input.mm);
|
|
66
|
+
toJSON(): number {
|
|
67
|
+
return this.millimeters;
|
|
139
68
|
}
|
|
140
69
|
}
|
|
@@ -12,8 +12,9 @@ export const HourFormatters: Record<HourFormatterEnum, HourFormatter> = {
|
|
|
12
12
|
TWENTY_FOUR_HOURS: (value) => value.toString().padStart(2, "0"),
|
|
13
13
|
TWENTY_FOUR_HOURS_WO_PADDING: (value) => value.toString(),
|
|
14
14
|
AM_PM: (value) => {
|
|
15
|
-
const twelveHourValue = value % 12 || 12;
|
|
16
15
|
const suffix = value < 12 ? "a.m." : "p.m.";
|
|
16
|
+
const twelveHourValue = value % 12 || 12;
|
|
17
|
+
|
|
17
18
|
return `${twelveHourValue} ${suffix}`;
|
|
18
19
|
},
|
|
19
20
|
TWELVE_HOURS: (value) => (value % 12 || 12).toString().padStart(2, "0"),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
export const HourSchemaError = { Type: "hour.schema.error", Invalid: "hour.schema.invalid" };
|
|
4
|
+
|
|
5
|
+
export const HourSchema = z
|
|
6
|
+
.number(HourSchemaError.Type)
|
|
7
|
+
.int(HourSchemaError.Type)
|
|
8
|
+
.gte(0, HourSchemaError.Invalid)
|
|
9
|
+
.lte(23, HourSchemaError.Invalid)
|
|
10
|
+
.brand("HourSchema");
|
|
11
|
+
|
|
12
|
+
export type HourSchemaType = z.infer<typeof HourSchema>;
|
package/src/hour.vo.ts
CHANGED
|
@@ -1,33 +1,25 @@
|
|
|
1
1
|
import { type HourFormatter, HourFormatters } from "./hour-format.service";
|
|
2
|
+
import { HourSchema, type HourSchemaType } from "./hour-schema.vo";
|
|
2
3
|
import type { TimestampType } from "./timestamp.vo";
|
|
3
4
|
|
|
4
|
-
export const HourValueError = "invalid.hour" as const;
|
|
5
|
-
|
|
6
5
|
export class Hour {
|
|
7
|
-
private readonly value:
|
|
6
|
+
private readonly value: HourSchemaType;
|
|
8
7
|
|
|
9
8
|
static readonly ZERO = new Hour(0);
|
|
10
9
|
static readonly MAX = new Hour(23);
|
|
11
10
|
|
|
12
11
|
constructor(candidate: number) {
|
|
13
|
-
|
|
14
|
-
throw new Error(HourValueError);
|
|
15
|
-
}
|
|
16
|
-
this.value = candidate;
|
|
12
|
+
this.value = HourSchema.parse(candidate);
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
static fromEpochMs(timestamp: TimestampType): Hour {
|
|
20
16
|
return new Hour(new Date(timestamp).getUTCHours());
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
get():
|
|
19
|
+
get(): HourSchemaType {
|
|
24
20
|
return this.value;
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
toString(): string {
|
|
28
|
-
return HourFormatters.TWENTY_FOUR_HOURS(this.value);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
format(formatter: HourFormatter): string {
|
|
32
24
|
return formatter(this.value);
|
|
33
25
|
}
|
|
@@ -47,4 +39,12 @@ export class Hour {
|
|
|
47
39
|
static list(): readonly Hour[] {
|
|
48
40
|
return Array.from({ length: 24 }, (_, index) => new Hour(index));
|
|
49
41
|
}
|
|
42
|
+
|
|
43
|
+
toString(): string {
|
|
44
|
+
return HourFormatters.TWENTY_FOUR_HOURS(this.value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
toJSON(): number {
|
|
48
|
+
return this.value;
|
|
49
|
+
}
|
|
50
50
|
}
|
package/src/iban-mask.service.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import type { IBAN } from "./iban.vo";
|
|
2
2
|
|
|
3
|
-
type IbanMaskedType = string;
|
|
4
|
-
|
|
5
3
|
export class IbanMask {
|
|
6
|
-
static censor(iban: IBAN):
|
|
4
|
+
static censor(iban: IBAN): string {
|
|
7
5
|
const value = iban.format();
|
|
8
6
|
|
|
9
7
|
const FIRST_SPACE_INDEX = 4;
|
|
10
|
-
const
|
|
8
|
+
const LAST_SPACE_INDEX = value.length - 5;
|
|
11
9
|
|
|
12
10
|
const start = value.slice(0, FIRST_SPACE_INDEX);
|
|
13
|
-
const middle = value.slice(FIRST_SPACE_INDEX + 1,
|
|
11
|
+
const middle = value.slice(FIRST_SPACE_INDEX + 1, LAST_SPACE_INDEX);
|
|
14
12
|
const end = value.slice(-4);
|
|
15
13
|
|
|
16
14
|
const maskedMiddle = middle.replace(/[A-Z0-9]/g, "*");
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
export const IbanSchemaError = { Type: "iban.schema.type", Invalid: "iban.schema.invalid" } as const;
|
|
4
|
+
|
|
5
|
+
// Two letters for country code, two digits, 11–30 uppercase letters or digits
|
|
6
|
+
const IBAN_CHARS_WHITELIST = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/;
|
|
7
|
+
|
|
8
|
+
export const IbanSchema = z
|
|
9
|
+
.string(IbanSchemaError.Type)
|
|
10
|
+
.toUpperCase()
|
|
11
|
+
.transform((value) => value.replaceAll(" ", ""))
|
|
12
|
+
.refine((value) => IBAN_CHARS_WHITELIST.test(value), IbanSchemaError.Invalid)
|
|
13
|
+
.brand("IbanSchema");
|
|
14
|
+
|
|
15
|
+
export type IbanSchemaType = z.infer<typeof IbanSchema>;
|
package/src/iban.vo.ts
CHANGED
|
@@ -1,37 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export const IBANError = { error: "invalid.iban.format" } as const;
|
|
4
|
-
|
|
5
|
-
// 2-letter country code + 2 digits + 11–30 alphanumerics (overall 15–34 chars)
|
|
6
|
-
const IBAN_REGEX = /^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/;
|
|
7
|
-
|
|
8
|
-
export const IBANValue = z
|
|
9
|
-
.string(IBANError)
|
|
10
|
-
.trim()
|
|
11
|
-
.toUpperCase()
|
|
12
|
-
.transform((val) => val.replace(/\s+/g, ""))
|
|
13
|
-
.refine((iban) => IBAN_REGEX.test(iban), IBANError)
|
|
14
|
-
.brand("IBAN");
|
|
15
|
-
|
|
16
|
-
export type IBANValueType = z.infer<typeof IBANValue>;
|
|
17
|
-
export type IBANCountryCode = string;
|
|
1
|
+
import { IbanSchema, type IbanSchemaType } from "./iban-schema.vo";
|
|
18
2
|
|
|
19
3
|
export class IBAN {
|
|
20
|
-
private readonly value:
|
|
4
|
+
private readonly value: IbanSchemaType;
|
|
21
5
|
|
|
22
|
-
constructor(
|
|
23
|
-
this.value =
|
|
6
|
+
constructor(candidate: string) {
|
|
7
|
+
this.value = IbanSchema.parse(candidate);
|
|
24
8
|
}
|
|
25
9
|
|
|
26
|
-
toString():
|
|
10
|
+
toString(): IbanSchemaType {
|
|
27
11
|
return this.value;
|
|
28
12
|
}
|
|
29
13
|
|
|
30
14
|
format(): string {
|
|
15
|
+
// (.{4}) - capture any four characters
|
|
16
|
+
// (?=.) - positive lookahead, at least one more character after the match
|
|
17
|
+
// "$1 " - replace each match with the group and a space
|
|
31
18
|
return this.value.replace(/(.{4})(?=.)/g, "$1 ");
|
|
32
19
|
}
|
|
33
20
|
|
|
34
|
-
get countryCode():
|
|
21
|
+
get countryCode(): string {
|
|
35
22
|
return this.value.slice(0, 2);
|
|
36
23
|
}
|
|
37
24
|
|