@bgord/tools 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/basename.vo.d.ts +11 -2
  2. package/dist/basename.vo.js +22 -13
  3. package/dist/date-calculator.service.d.ts +2 -2
  4. package/dist/date-calculator.service.js +10 -11
  5. package/dist/date-range.vo.d.ts +1 -0
  6. package/dist/date-range.vo.js +2 -1
  7. package/dist/day-iso-id.vo.d.ts +3 -0
  8. package/dist/day-iso-id.vo.js +4 -4
  9. package/dist/day.vo.js +12 -10
  10. package/dist/directory-path-absolute.vo.d.ts +5 -0
  11. package/dist/directory-path-absolute.vo.js +11 -5
  12. package/dist/directory-path-relative.vo.d.ts +6 -0
  13. package/dist/directory-path-relative.vo.js +12 -6
  14. package/dist/dll.service.js +37 -31
  15. package/dist/extension.vo.d.ts +6 -2
  16. package/dist/extension.vo.js +11 -9
  17. package/dist/file-path-absolute-schema.vo.d.ts +5 -0
  18. package/dist/file-path-absolute-schema.vo.js +12 -6
  19. package/dist/file-path-relative-schema.vo.d.ts +6 -1
  20. package/dist/file-path-relative-schema.vo.js +10 -6
  21. package/dist/file-path.vo.js +4 -4
  22. package/dist/filename-from-string.vo.d.ts +6 -4
  23. package/dist/filename-from-string.vo.js +15 -14
  24. package/dist/filename-suffix.vo.d.ts +4 -2
  25. package/dist/filename-suffix.vo.js +5 -3
  26. package/dist/filename.vo.d.ts +2 -2
  27. package/dist/filename.vo.js +9 -9
  28. package/dist/height.vo.d.ts +6 -4
  29. package/dist/height.vo.js +56 -51
  30. package/dist/index.d.ts +0 -1
  31. package/dist/index.js +0 -1
  32. package/dist/language.vo.d.ts +1 -1
  33. package/dist/language.vo.js +1 -2
  34. package/dist/mime.vo.d.ts +3 -1
  35. package/dist/mime.vo.js +8 -6
  36. package/dist/month-iso-id.vo.d.ts +3 -0
  37. package/dist/month-iso-id.vo.js +7 -12
  38. package/dist/month.vo.js +15 -13
  39. package/dist/object-key.vo.d.ts +5 -0
  40. package/dist/object-key.vo.js +16 -6
  41. package/dist/package-version.vo.d.ts +3 -0
  42. package/dist/package-version.vo.js +12 -34
  43. package/dist/pagination.service.d.ts +1 -1
  44. package/dist/pagination.service.js +11 -11
  45. package/dist/quarter-iso-id.vo.d.ts +3 -0
  46. package/dist/quarter-iso-id.vo.js +8 -7
  47. package/dist/rate-limiter.service.d.ts +3 -2
  48. package/dist/rate-limiter.service.js +4 -2
  49. package/dist/reordering.service.d.ts +20 -2
  50. package/dist/reordering.service.js +49 -29
  51. package/dist/revision.vo.d.ts +8 -3
  52. package/dist/revision.vo.js +13 -6
  53. package/dist/rounding.adapter.js +1 -2
  54. package/dist/size.vo.d.ts +1 -0
  55. package/dist/size.vo.js +4 -7
  56. package/dist/streak-calculator.service.d.ts +3 -4
  57. package/dist/streak-calculator.service.js +11 -17
  58. package/dist/time-zone-offset-value.vo.d.ts +1 -1
  59. package/dist/time-zone-offset-value.vo.js +1 -7
  60. package/dist/time.service.d.ts +11 -6
  61. package/dist/time.service.js +31 -18
  62. package/dist/timezone.vo.js +1 -3
  63. package/dist/tsconfig.tsbuildinfo +1 -1
  64. package/dist/week-iso-id.vo.d.ts +3 -0
  65. package/dist/week-iso-id.vo.js +4 -4
  66. package/dist/week.vo.js +1 -1
  67. package/dist/weekday.vo.d.ts +7 -6
  68. package/dist/weekday.vo.js +20 -13
  69. package/dist/weight.vo.d.ts +12 -0
  70. package/dist/weight.vo.js +37 -27
  71. package/dist/year-iso-id.vo.d.ts +3 -0
  72. package/dist/year-iso-id.vo.js +4 -6
  73. package/dist/year.vo.d.ts +2 -0
  74. package/dist/year.vo.js +4 -2
  75. package/package.json +1 -1
  76. package/readme.md +0 -1
  77. package/src/basename.vo.ts +25 -14
  78. package/src/clock.vo.ts +1 -0
  79. package/src/date-calculator.service.ts +10 -15
  80. package/src/date-range.vo.ts +3 -1
  81. package/src/day-iso-id.vo.ts +9 -10
  82. package/src/day.vo.ts +17 -10
  83. package/src/directory-path-absolute.vo.ts +12 -5
  84. package/src/directory-path-relative.vo.ts +13 -6
  85. package/src/dll.service.ts +45 -43
  86. package/src/extension.vo.ts +14 -12
  87. package/src/file-path-absolute-schema.vo.ts +15 -6
  88. package/src/file-path-relative-schema.vo.ts +13 -6
  89. package/src/file-path.vo.ts +15 -11
  90. package/src/filename-from-string.vo.ts +20 -15
  91. package/src/filename-suffix.vo.ts +8 -4
  92. package/src/filename.vo.ts +14 -15
  93. package/src/height.vo.ts +71 -53
  94. package/src/index.ts +0 -1
  95. package/src/language.vo.ts +1 -2
  96. package/src/mime.vo.ts +10 -7
  97. package/src/month-iso-id.vo.ts +10 -20
  98. package/src/month.vo.ts +19 -13
  99. package/src/object-key.vo.ts +21 -7
  100. package/src/outlier-detector.service.ts +1 -0
  101. package/src/package-version.vo.ts +18 -47
  102. package/src/pagination.service.ts +15 -13
  103. package/src/quarter-iso-id.vo.ts +11 -13
  104. package/src/quarter.vo.ts +3 -0
  105. package/src/rate-limiter.service.ts +7 -7
  106. package/src/reordering.service.ts +52 -38
  107. package/src/revision.vo.ts +17 -8
  108. package/src/rounding.adapter.ts +1 -3
  109. package/src/size.vo.ts +6 -16
  110. package/src/streak-calculator.service.ts +12 -17
  111. package/src/time-zone-offset-value.vo.ts +2 -7
  112. package/src/time.service.ts +43 -45
  113. package/src/timezone.vo.ts +1 -3
  114. package/src/week-iso-id.vo.ts +13 -14
  115. package/src/week.vo.ts +4 -2
  116. package/src/weekday.vo.ts +27 -13
  117. package/src/weight.vo.ts +49 -30
  118. package/src/year-iso-id.vo.ts +6 -9
  119. package/src/year.vo.ts +12 -2
  120. package/dist/stepper.service.d.ts +0 -23
  121. package/dist/stepper.service.js +0 -33
  122. package/src/stepper.service.ts +0 -43
@@ -1,6 +1,6 @@
1
1
  import { type BasenameType } from "./basename.vo";
2
2
  import { type ExtensionType } from "./extension.vo";
3
- import { type FilenameSuffixSchemaType } from "./filename-suffix.vo";
3
+ import { type FilenameSuffixType } from "./filename-suffix.vo";
4
4
  export declare class Filename {
5
5
  private readonly basename;
6
6
  private readonly extension;
@@ -14,5 +14,5 @@ export declare class Filename {
14
14
  withExtension(extension: ExtensionType): Filename;
15
15
  withBasename(basename: BasenameType): Filename;
16
16
  withSuffix(candidate: string): Filename;
17
- withSuffixSafe(suffix: FilenameSuffixSchemaType): Filename;
17
+ withSuffixSafe(suffix: FilenameSuffixType): Filename;
18
18
  }
@@ -1,7 +1,7 @@
1
- import { BasenameSchema } from "./basename.vo";
2
- import { ExtensionSchema } from "./extension.vo";
3
- import { FilenameFromStringSchema } from "./filename-from-string.vo";
4
- import { FilenameSuffixSchema } from "./filename-suffix.vo";
1
+ import { Basename } from "./basename.vo";
2
+ import { Extension } from "./extension.vo";
3
+ import { FilenameFromString } from "./filename-from-string.vo";
4
+ import { FilenameSuffix } from "./filename-suffix.vo";
5
5
  export class Filename {
6
6
  basename;
7
7
  extension;
@@ -10,13 +10,13 @@ export class Filename {
10
10
  this.extension = extension;
11
11
  }
12
12
  static fromParts(basename, extension) {
13
- return new Filename(BasenameSchema.parse(basename), ExtensionSchema.parse(extension));
13
+ return new Filename(Basename.parse(basename), Extension.parse(extension));
14
14
  }
15
15
  static fromPartsSafe(basename, extension) {
16
16
  return new Filename(basename, extension);
17
17
  }
18
18
  static fromString(candidate) {
19
- const { basename, extension } = FilenameFromStringSchema.parse(candidate);
19
+ const { basename, extension } = FilenameFromString.parse(candidate);
20
20
  return new Filename(basename, extension);
21
21
  }
22
22
  get() {
@@ -35,12 +35,12 @@ export class Filename {
35
35
  return new Filename(basename, this.extension);
36
36
  }
37
37
  withSuffix(candidate) {
38
- const suffix = FilenameSuffixSchema.parse(candidate);
39
- const basename = BasenameSchema.parse(`${this.basename}${suffix}`);
38
+ const suffix = FilenameSuffix.parse(candidate);
39
+ const basename = Basename.parse(`${this.basename}${suffix}`);
40
40
  return new Filename(basename, this.extension);
41
41
  }
42
42
  withSuffixSafe(suffix) {
43
- const basename = BasenameSchema.parse(`${this.basename}${suffix}`);
43
+ const basename = Basename.parse(`${this.basename}${suffix}`);
44
44
  return new Filename(basename, this.extension);
45
45
  }
46
46
  }
@@ -13,6 +13,7 @@ export declare class Height {
13
13
  static fromFeetInches(feet: number, inches?: number, rounding?: RoundingPort): Height;
14
14
  static fromMillimeters(millimeters: number, rounding?: RoundingPort): Height;
15
15
  static zero(): Height;
16
+ get(): number;
16
17
  toMillimeters(): number;
17
18
  toCentimeters(rounding?: RoundingPort): number;
18
19
  toFeetInches(rounding?: RoundingPort): {
@@ -20,10 +21,11 @@ export declare class Height {
20
21
  inches: number;
21
22
  };
22
23
  format(unit: HeightUnit, rounding?: RoundingPort): string;
23
- equals(other: Height): boolean;
24
- compare(other: Height): -1 | 0 | 1;
25
- greaterThan(other: Height): boolean;
26
- lessThan(other: Height): boolean;
24
+ toString(): string;
25
+ equals(another: Height): boolean;
26
+ compare(another: Height): -1 | 0 | 1;
27
+ greaterThan(another: Height): boolean;
28
+ lessThan(another: Height): boolean;
27
29
  isZero(): boolean;
28
30
  toJSON(): {
29
31
  mm: number;
package/dist/height.vo.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { z } from "zod/v4";
2
2
  import { RoundToDecimal, RoundToNearest } from "./rounding.adapter";
3
- const FiniteNumericValue = z.number().refine(Number.isFinite, { message: "Expected a finite number" });
4
- const NonNegativeNumericValue = FiniteNumericValue.min(0, { message: "Must be greater than or equal to 0" });
5
- const NonNegativeIntegerMillimeters = FiniteNumericValue.int().min(0, {
6
- message: "Millimeters must be an integer greater than or equal to 0",
7
- });
8
- const NonNegativeIntegerValue = FiniteNumericValue.int().min(0, {
9
- message: "Value must be an integer greater than or equal to 0",
10
- });
3
+ const NonFiniteNumberError = { error: "number.non_finite" };
4
+ const NumberNegativeError = { error: "number.negative" };
5
+ const MillimetersIntegerNonNegativeError = { error: "millimeters.integer_non_negative" };
6
+ const IntegerNonNegativeError = { error: "integer.non_negative" };
7
+ const HeightFiniteNumber = z.number(NonFiniteNumberError).refine(Number.isFinite, NonFiniteNumberError);
8
+ const HeightNonNegativeQuantity = HeightFiniteNumber.min(0, NumberNegativeError);
9
+ const HeightCanonicalMillimeters = HeightFiniteNumber.int(MillimetersIntegerNonNegativeError).min(0, MillimetersIntegerNonNegativeError);
10
+ const HeightRoundedWholeInches = HeightFiniteNumber.int(IntegerNonNegativeError).min(0, IntegerNonNegativeError);
11
11
  export var HeightUnit;
12
12
  (function (HeightUnit) {
13
13
  HeightUnit["cm"] = "cm";
@@ -22,71 +22,76 @@ export class Height {
22
22
  this.millimeters = millimeters;
23
23
  }
24
24
  static fromCentimeters(centimeters, rounding = new RoundToNearest()) {
25
- NonNegativeNumericValue.parse(centimeters);
26
- const mmFloat = centimeters * Height.MILLIMETERS_PER_CENTIMETER;
27
- const mmRounded = rounding.round(mmFloat);
28
- NonNegativeIntegerMillimeters.parse(mmRounded);
29
- return new Height(mmRounded);
25
+ const validatedCentimeters = HeightNonNegativeQuantity.parse(centimeters);
26
+ const millimetersFloat = validatedCentimeters * Height.MILLIMETERS_PER_CENTIMETER;
27
+ const millimetersRounded = rounding.round(millimetersFloat);
28
+ const validatedMillimeters = HeightCanonicalMillimeters.parse(millimetersRounded);
29
+ return new Height(validatedMillimeters);
30
30
  }
31
31
  static fromFeetInches(feet, inches = 0, rounding = new RoundToNearest()) {
32
- NonNegativeNumericValue.parse(feet);
33
- NonNegativeNumericValue.parse(inches);
34
- const totalInches = feet * Height.INCHES_PER_FOOT + inches;
35
- const mmFloat = totalInches * Height.MILLIMETERS_PER_INCH;
36
- const mmRounded = rounding.round(mmFloat);
37
- NonNegativeIntegerMillimeters.parse(mmRounded);
38
- return new Height(mmRounded);
32
+ const validatedFeet = HeightNonNegativeQuantity.parse(feet);
33
+ const validatedInches = HeightNonNegativeQuantity.parse(inches);
34
+ const totalInches = validatedFeet * Height.INCHES_PER_FOOT + validatedInches;
35
+ const millimetersFloat = totalInches * Height.MILLIMETERS_PER_INCH;
36
+ const millimetersRounded = rounding.round(millimetersFloat);
37
+ const validatedMillimeters = HeightCanonicalMillimeters.parse(millimetersRounded);
38
+ return new Height(validatedMillimeters);
39
39
  }
40
40
  static fromMillimeters(millimeters, rounding = new RoundToNearest()) {
41
- NonNegativeNumericValue.parse(millimeters);
42
- const mmRounded = rounding.round(millimeters);
43
- NonNegativeIntegerMillimeters.parse(mmRounded);
44
- return new Height(mmRounded);
41
+ const validatedMillimetersInput = HeightNonNegativeQuantity.parse(millimeters);
42
+ const millimetersRounded = rounding.round(validatedMillimetersInput);
43
+ const validatedMillimeters = HeightCanonicalMillimeters.parse(millimetersRounded);
44
+ return new Height(validatedMillimeters);
45
45
  }
46
46
  static zero() {
47
47
  return new Height(0);
48
48
  }
49
+ get() {
50
+ return this.millimeters;
51
+ }
49
52
  toMillimeters() {
50
53
  return this.millimeters;
51
54
  }
52
55
  toCentimeters(rounding) {
53
- const cm = this.millimeters / Height.MILLIMETERS_PER_CENTIMETER;
54
- return rounding ? rounding.round(cm) : cm;
56
+ const centimeters = this.millimeters / Height.MILLIMETERS_PER_CENTIMETER;
57
+ if (rounding)
58
+ return rounding.round(centimeters);
59
+ return centimeters;
55
60
  }
56
61
  toFeetInches(rounding = new RoundToNearest()) {
57
62
  const totalInchesFloat = this.millimeters / Height.MILLIMETERS_PER_INCH;
58
63
  const totalInchesRounded = rounding.round(totalInchesFloat);
59
- const integerInches = NonNegativeIntegerValue.parse(totalInchesRounded);
60
- const feet = (integerInches - (integerInches % Height.INCHES_PER_FOOT)) / Height.INCHES_PER_FOOT;
61
- const inches = integerInches % Height.INCHES_PER_FOOT;
64
+ const totalWholeInches = HeightRoundedWholeInches.parse(totalInchesRounded);
65
+ const feet = Math.floor(totalWholeInches / Height.INCHES_PER_FOOT);
66
+ const inches = totalWholeInches % Height.INCHES_PER_FOOT;
62
67
  return { feet, inches };
63
68
  }
64
69
  format(unit, rounding) {
65
- return {
66
- [HeightUnit.cm]: () => {
67
- const chosen = rounding ?? new RoundToDecimal(1);
68
- return `${this.toCentimeters(chosen)} cm`;
69
- },
70
- [HeightUnit.ft_in]: () => {
71
- const chosen = rounding ?? new RoundToNearest();
72
- const { feet, inches } = this.toFeetInches(chosen);
73
- return `${feet}′${inches}″`;
74
- },
75
- }[unit]();
76
- }
77
- equals(other) {
78
- return this.millimeters === other.millimeters;
79
- }
80
- compare(other) {
81
- if (this.equals(other))
70
+ if (unit === HeightUnit.cm) {
71
+ const chosen = rounding ?? new RoundToDecimal(1);
72
+ const value = this.toCentimeters(chosen);
73
+ return `${value} cm`;
74
+ }
75
+ const chosen = rounding ?? new RoundToNearest();
76
+ const parts = this.toFeetInches(chosen);
77
+ return `${parts.feet}′${parts.inches}″`;
78
+ }
79
+ toString() {
80
+ return this.format(HeightUnit.cm, new RoundToDecimal(1));
81
+ }
82
+ equals(another) {
83
+ return this.millimeters === another.millimeters;
84
+ }
85
+ compare(another) {
86
+ if (this.equals(another))
82
87
  return 0;
83
- return this.millimeters < other.millimeters ? -1 : 1;
88
+ return this.millimeters < another.millimeters ? -1 : 1;
84
89
  }
85
- greaterThan(other) {
86
- return this.millimeters > other.millimeters;
90
+ greaterThan(another) {
91
+ return this.millimeters > another.millimeters;
87
92
  }
88
- lessThan(other) {
89
- return this.millimeters < other.millimeters;
93
+ lessThan(another) {
94
+ return this.millimeters < another.millimeters;
90
95
  }
91
96
  isZero() {
92
97
  return this.millimeters === 0;
package/dist/index.d.ts CHANGED
@@ -53,7 +53,6 @@ export * from "./rounding.adapter";
53
53
  export * from "./rounding.port";
54
54
  export * from "./simple-linear-regression.service";
55
55
  export * from "./size.vo";
56
- export * from "./stepper.service";
57
56
  export * from "./stopwatch.service";
58
57
  export * from "./streak-calculator.service";
59
58
  export * from "./sum.service";
package/dist/index.js CHANGED
@@ -53,7 +53,6 @@ export * from "./rounding.adapter";
53
53
  export * from "./rounding.port";
54
54
  export * from "./simple-linear-regression.service";
55
55
  export * from "./size.vo";
56
- export * from "./stepper.service";
57
56
  export * from "./stopwatch.service";
58
57
  export * from "./streak-calculator.service";
59
58
  export * from "./sum.service";
@@ -2,5 +2,5 @@ import { z } from "zod/v4";
2
2
  export declare const LanguageError: {
3
3
  readonly error: "invalid.language";
4
4
  };
5
- export declare const Language: z.core.$ZodBranded<z.ZodString, "Language">;
5
+ export declare const Language: z.ZodString;
6
6
  export type LanguageType = z.infer<typeof Language>;
@@ -3,5 +3,4 @@ export const LanguageError = { error: "invalid.language" };
3
3
  export const Language = z
4
4
  .string(LanguageError)
5
5
  .length(2, LanguageError)
6
- .regex(/^[a-z]{2}$/, LanguageError)
7
- .brand("Language");
6
+ .regex(/^[a-z]{2}$/, LanguageError);
package/dist/mime.vo.d.ts CHANGED
@@ -2,6 +2,8 @@ import { type ExtensionType } from "./extension.vo";
2
2
  export type MimeRawType = string;
3
3
  type MimeTypeType = string;
4
4
  type MimeSubtypeType = string;
5
+ export declare const InvalidMimeErrorMessage: "invalid.mime";
6
+ export declare const NotAcceptedMimeErrorMessage: "mime.not.accepted";
5
7
  export declare class Mime {
6
8
  readonly raw: MimeRawType;
7
9
  readonly type: MimeTypeType;
@@ -16,7 +18,7 @@ export declare class InvalidMimeError extends Error {
16
18
  }
17
19
  export declare class NotAcceptedMimeError extends Error {
18
20
  mime: MimeRawType;
19
- constructor(mime: MimeRawType);
21
+ constructor(mimeValue: MimeRawType);
20
22
  }
21
23
  export declare const MIMES: {
22
24
  csv: Mime;
package/dist/mime.vo.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import * as mime from "mime-types";
2
- import { ExtensionSchema } from "./extension.vo";
2
+ import { Extension } from "./extension.vo";
3
+ export const InvalidMimeErrorMessage = "invalid.mime";
4
+ export const NotAcceptedMimeErrorMessage = "mime.not.accepted";
3
5
  export class Mime {
4
6
  raw;
5
7
  type;
@@ -26,21 +28,21 @@ export class Mime {
26
28
  return this.subtype === another.subtype || this.subtype === "*";
27
29
  }
28
30
  toExtension() {
29
- return ExtensionSchema.parse(mime.extension(this.raw));
31
+ return Extension.parse(mime.extension(this.raw));
30
32
  }
31
33
  }
32
34
  export class InvalidMimeError extends Error {
33
35
  constructor() {
34
- super();
36
+ super(InvalidMimeErrorMessage);
35
37
  Object.setPrototypeOf(this, InvalidMimeError.prototype);
36
38
  }
37
39
  }
38
40
  export class NotAcceptedMimeError extends Error {
39
41
  mime;
40
- constructor(mime) {
41
- super();
42
+ constructor(mimeValue) {
43
+ super(NotAcceptedMimeErrorMessage);
42
44
  Object.setPrototypeOf(this, NotAcceptedMimeError.prototype);
43
- this.mime = mime;
45
+ this.mime = mimeValue;
44
46
  }
45
47
  }
46
48
  export const MIMES = {
@@ -1,3 +1,6 @@
1
1
  import { z } from "zod/v4";
2
+ export declare const MonthIsoIdError: {
3
+ readonly error: "month-iso-id.invalid";
4
+ };
2
5
  export declare const MonthIsoId: z.ZodString;
3
6
  export type MonthIsoIdType = z.infer<typeof MonthIsoId>;
@@ -1,15 +1,10 @@
1
1
  import { z } from "zod/v4";
2
+ export const MonthIsoIdError = { error: "month-iso-id.invalid" };
2
3
  export const MonthIsoId = z
3
- .string()
4
- .regex(/^\d{4}-\d{2}$/)
4
+ .string(MonthIsoIdError)
5
+ .regex(/^\d{4}-\d{2}$/, MonthIsoIdError)
5
6
  .refine((value) => {
6
- const [y, m] = value.split("-");
7
- const year = Number(y);
8
- const month = Number(m);
9
- return (y.length === 4 &&
10
- m.length === 2 &&
11
- Number.isInteger(year) &&
12
- Number.isInteger(month) &&
13
- month >= 1 &&
14
- month <= 12);
15
- }, { message: "month-iso-id.invalid" });
7
+ const year = Number(value.slice(0, 4));
8
+ const month = Number(value.slice(5, 7));
9
+ return Number.isInteger(year) && Number.isInteger(month) && month >= 1 && month <= 12;
10
+ }, MonthIsoIdError);
package/dist/month.vo.js CHANGED
@@ -1,4 +1,4 @@
1
- import { addMonths, endOfMonth, startOfMonth } from "date-fns";
1
+ // src/month.vo.ts
2
2
  import { DateRange } from "./date-range.vo";
3
3
  import { MonthIsoId } from "./month-iso-id.vo";
4
4
  import { Timestamp } from "./timestamp.vo";
@@ -7,28 +7,30 @@ export class Month extends DateRange {
7
7
  return new Date(this.getStart()).toISOString().slice(0, 7);
8
8
  }
9
9
  previous() {
10
- const shifted = addMonths(new Date(this.getStart()), -1).getTime();
11
- return Month.fromTimestamp(Timestamp.parse(shifted));
10
+ return Month.fromTimestamp(Timestamp.parse(this.getStart() - 1));
12
11
  }
13
12
  next() {
14
- const shifted = addMonths(new Date(this.getStart()), 1).getTime();
15
- return Month.fromTimestamp(Timestamp.parse(shifted));
13
+ return Month.fromTimestamp(Timestamp.parse(this.getEnd() + 1));
16
14
  }
17
15
  shift(count) {
18
- const shifted = addMonths(new Date(this.getStart()), count).getTime();
19
- return Month.fromTimestamp(Timestamp.parse(shifted));
16
+ const date = new Date(this.getStart());
17
+ date.setUTCMonth(date.getUTCMonth() + count);
18
+ return Month.fromTimestamp(Timestamp.parse(date.getTime()));
20
19
  }
21
20
  static fromTimestamp(timestamp) {
22
- const start = Timestamp.parse(startOfMonth(timestamp).getTime());
23
- const end = Timestamp.parse(endOfMonth(timestamp).getTime());
24
- return new Month(start, end);
21
+ const isoMonth = new Date(timestamp).toISOString().slice(0, 7);
22
+ return Month.fromIsoId(isoMonth);
25
23
  }
26
24
  static fromNow(now) {
27
25
  return Month.fromTimestamp(now);
28
26
  }
29
27
  static fromIsoId(iso) {
30
- const [year, month] = MonthIsoId.parse(iso).split("-").map(Number);
31
- const reference = new Date(Date.UTC(year, month - 1, 1));
32
- return Month.fromTimestamp(Timestamp.parse(reference.getTime()));
28
+ const validated = MonthIsoId.parse(iso);
29
+ const year = Number(validated.slice(0, 4));
30
+ const monthIndex = Number(validated.slice(5, 7)) - 1;
31
+ const startUtc = Date.UTC(year, monthIndex, 1);
32
+ const nextStartUtc = Date.UTC(year, monthIndex + 1, 1);
33
+ const endUtc = nextStartUtc - 1;
34
+ return new Month(Timestamp.parse(startUtc), Timestamp.parse(endUtc));
33
35
  }
34
36
  }
@@ -1,3 +1,8 @@
1
1
  import { z } from "zod/v4";
2
+ export declare const ObjectKeyMustNotStartWithSlashError: "obj_key_must_not_start_with_slash";
3
+ export declare const ObjectKeyBackslashForbiddenError: "obj_key_backslash_forbidden";
4
+ export declare const ObjectKeyControlCharsForbiddenError: "obj_key_control_chars_forbidden";
5
+ export declare const ObjectKeyEmptyError: "obj_key_empty";
6
+ export declare const ObjectKeyBadSegmentsError: "obj_key_bad_segments";
2
7
  export declare const ObjectKey: z.core.$ZodBranded<z.ZodString, "object_key">;
3
8
  export type ObjectKeyType = z.infer<typeof ObjectKey>;
@@ -1,11 +1,21 @@
1
1
  import { z } from "zod/v4";
2
+ export const ObjectKeyMustNotStartWithSlashError = "obj_key_must_not_start_with_slash";
3
+ export const ObjectKeyBackslashForbiddenError = "obj_key_backslash_forbidden";
4
+ export const ObjectKeyControlCharsForbiddenError = "obj_key_control_chars_forbidden";
5
+ export const ObjectKeyEmptyError = "obj_key_empty";
6
+ export const ObjectKeyBadSegmentsError = "obj_key_bad_segments";
7
+ // biome-ignore lint: lint/suspicious/noControlCharactersInRegex
8
+ const CONTROL_CHARS_REGEX = /[\u0000-\u001F\u007F]/;
9
+ const SEGMENT_ALLOWED_REGEX = /^[a-z0-9._-]+$/;
2
10
  export const ObjectKey = z
3
11
  .string()
4
12
  .trim()
5
- .refine((v) => !v.startsWith("/"), "obj_key_must_not_start_with_slash")
6
- .refine((v) => !v.includes("\\"), "obj_key_backslash_forbidden")
7
- // biome-ignore lint: lint/suspicious/noControlCharactersInRegex
8
- .refine((v) => !/[\u0000-\u001F\u007F]/.test(v), "obj_key_control_chars_forbidden")
9
- .refine((v) => v.length > 0, "obj_key_empty")
10
- .refine((v) => v.split("/").every((s) => /^[a-z0-9._-]+$/.test(s) && s !== "." && s !== ".."), "obj_key_bad_segments")
13
+ // fastest early exits first:
14
+ .refine((value) => value.length > 0, ObjectKeyEmptyError)
15
+ .refine((value) => !value.startsWith("/"), ObjectKeyMustNotStartWithSlashError)
16
+ .refine((value) => !value.includes("\\"), ObjectKeyBackslashForbiddenError)
17
+ .refine((value) => !CONTROL_CHARS_REGEX.test(value), ObjectKeyControlCharsForbiddenError)
18
+ .refine((value) => value
19
+ .split("/")
20
+ .every((segment) => SEGMENT_ALLOWED_REGEX.test(segment) && segment !== "." && segment !== ".."), ObjectKeyBadSegmentsError)
11
21
  .brand("object_key");
@@ -2,6 +2,9 @@ import { z } from "zod/v4";
2
2
  type MajorType = number;
3
3
  type MinorType = number;
4
4
  type PatchType = number;
5
+ export declare const PackageVersionError: {
6
+ readonly error: "package.version.error";
7
+ };
5
8
  export declare const PackageVersionValue: z.core.$ZodBranded<z.ZodPipe<z.ZodString, z.ZodTransform<{
6
9
  major: number;
7
10
  minor: number;
@@ -1,36 +1,14 @@
1
1
  import { z } from "zod/v4";
2
+ export const PackageVersionError = { error: "package.version.error" };
2
3
  export const PackageVersionValue = z
3
- .string()
4
- .min(1)
5
- .refine((value) => {
6
- try {
7
- if (!value.startsWith("v"))
8
- return false;
9
- const [, version] = value.split("v");
10
- if (!version)
11
- return false;
12
- const [major, minor, patch] = version.split(".");
13
- if (!(major && minor && patch))
14
- return false;
15
- if (!(Number.isInteger(Number(major)) &&
16
- Number.isInteger(Number(minor)) &&
17
- Number.isInteger(Number(patch)))) {
18
- return false;
19
- }
20
- return true;
21
- }
22
- catch (_error) {
23
- return false;
24
- }
25
- }, { message: "package.version.error" })
4
+ .string(PackageVersionError)
5
+ .regex(/^v(\d+)\.(\d+)\.(\d+)$/, PackageVersionError)
26
6
  .transform((value) => {
27
- const [, version] = value.split("v");
28
- const [major, minor, patch] = version.split(".");
29
- return {
30
- major: Number(major),
31
- minor: Number(minor),
32
- patch: Number(patch),
33
- };
7
+ const match = /^v(\d+)\.(\d+)\.(\d+)$/.exec(value);
8
+ const major = Number(match[1]);
9
+ const minor = Number(match[2]);
10
+ const patch = Number(match[3]);
11
+ return { major, minor, patch };
34
12
  })
35
13
  .brand("PackageVersionValue");
36
14
  export class PackageVersion {
@@ -61,11 +39,11 @@ export class PackageVersion {
61
39
  return `${this.major}.${this.minor}.${this.patch}`;
62
40
  }
63
41
  static fromStringWithV(value) {
64
- const version = PackageVersionValue.parse(value);
65
- return new PackageVersion(version.major, version.minor, version.patch);
42
+ const parsed = PackageVersionValue.parse(value);
43
+ return new PackageVersion(parsed.major, parsed.minor, parsed.patch);
66
44
  }
67
45
  static fromString(value) {
68
- const version = PackageVersionValue.parse(`v${value}`);
69
- return new PackageVersion(version.major, version.minor, version.patch);
46
+ const parsed = PackageVersionValue.parse(`v${value}`);
47
+ return new PackageVersion(parsed.major, parsed.minor, parsed.patch);
70
48
  }
71
49
  }
@@ -40,7 +40,7 @@ export declare class Pagination {
40
40
  total: number;
41
41
  };
42
42
  };
43
- static getFirstPage({ take }: {
43
+ static getFirstPage(input: {
44
44
  take: TakeType;
45
45
  }): PaginationType;
46
46
  }
@@ -1,9 +1,12 @@
1
1
  import { z } from "zod/v4";
2
- const Take = z.number().int().gte(0);
3
- const Skip = z.number().int().gte(0);
2
+ const PaginationTakeError = { error: "pagination.take.invalid" };
3
+ const PaginationSkipError = { error: "pagination.skip.invalid" };
4
+ const PaginationPageError = { error: "pagination.page.invalid" };
5
+ const Take = z.number(PaginationTakeError).int(PaginationTakeError).gte(1, PaginationTakeError);
6
+ const Skip = z.number(PaginationSkipError).int(PaginationSkipError).gte(0, PaginationSkipError);
4
7
  const Page = z.coerce
5
- .number()
6
- .int()
8
+ .number(PaginationPageError)
9
+ .int(PaginationPageError)
7
10
  .transform((value) => (value <= 0 ? 1 : value))
8
11
  .default(1);
9
12
  export class Pagination {
@@ -25,13 +28,10 @@ export class Pagination {
25
28
  };
26
29
  }
27
30
  static isExhausted(config) {
28
- const lastPage = Pagination.getLastPage(config);
29
- const currentPage = config.pagination.page;
30
- return lastPage <= currentPage;
31
+ return Pagination.getLastPage(config) <= config.pagination.page;
31
32
  }
32
33
  static getLastPage(config) {
33
- const lastPage = Math.ceil(config.total / config.pagination.values.take);
34
- return Page.parse(lastPage);
34
+ return Page.parse(Math.ceil(config.total / config.pagination.values.take));
35
35
  }
36
36
  static empty = {
37
37
  result: [],
@@ -44,7 +44,7 @@ export class Pagination {
44
44
  total: 0,
45
45
  },
46
46
  };
47
- static getFirstPage({ take }) {
48
- return { values: { take, skip: Skip.parse(0) }, page: Page.parse(1) };
47
+ static getFirstPage(input) {
48
+ return { values: { take: Take.parse(input.take), skip: Skip.parse(0) }, page: Page.parse(1) };
49
49
  }
50
50
  }
@@ -1,3 +1,6 @@
1
1
  import { z } from "zod/v4";
2
+ export declare const QuarterIsoIdError: {
3
+ readonly error: "quarter-iso-id.invalid";
4
+ };
2
5
  export declare const QuarterIsoId: z.ZodString;
3
6
  export type QuarterIsoIdType = z.infer<typeof QuarterIsoId>;
@@ -1,10 +1,11 @@
1
1
  import { z } from "zod/v4";
2
+ export const QuarterIsoIdError = { error: "quarter-iso-id.invalid" };
2
3
  export const QuarterIsoId = z
3
- .string()
4
- .regex(/^\d{4}-Q[1-4]$/)
4
+ .string(QuarterIsoIdError)
5
+ .regex(/^\d{4}-Q[1-4]$/, QuarterIsoIdError)
5
6
  .refine((value) => {
6
- const [y, q] = value.split("-Q");
7
- const year = Number(y);
8
- const quarter = Number(q);
9
- return (y.length === 4 && Number.isInteger(year) && Number.isInteger(quarter) && quarter >= 1 && quarter <= 4);
10
- }, { message: "quarter-iso-id.invalid" });
7
+ const [yearPart, quarterPart] = value.split("-Q");
8
+ const year = Number(yearPart);
9
+ const quarter = Number(quarterPart);
10
+ return Number.isInteger(year) && Number.isInteger(quarter) && quarter >= 1 && quarter <= 4;
11
+ }, QuarterIsoIdError);
@@ -1,6 +1,7 @@
1
- import type { TimeResult } from "./time.service";
2
1
  import { type TimestampType } from "./timestamp.vo";
3
- type RateLimiterOptionsType = Pick<TimeResult, "ms">;
2
+ type RateLimiterOptionsType = {
3
+ ms: TimestampType;
4
+ };
4
5
  type RateLimiterResultSuccessType = {
5
6
  allowed: true;
6
7
  };
@@ -6,7 +6,7 @@ export class RateLimiter {
6
6
  this.options = options;
7
7
  }
8
8
  verify(currentTimestampMs) {
9
- if (this.lastInvocationTimestampMs === null || this.lastInvocationTimestampMs === undefined) {
9
+ if (this.lastInvocationTimestampMs == null) {
10
10
  this.lastInvocationTimestampMs = currentTimestampMs;
11
11
  return { allowed: true };
12
12
  }
@@ -15,6 +15,8 @@ export class RateLimiter {
15
15
  this.lastInvocationTimestampMs = currentTimestampMs;
16
16
  return { allowed: true };
17
17
  }
18
- return { allowed: false, remainingMs: Timestamp.parse(nextAllowedTimestampMs - currentTimestampMs) };
18
+ const remainingDelta = nextAllowedTimestampMs - currentTimestampMs;
19
+ const remainingMs = Timestamp.parse(remainingDelta);
20
+ return { allowed: false, remainingMs };
19
21
  }
20
22
  }