@bgord/tools 0.17.2 → 1.0.1

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 (257) hide show
  1. package/dist/age-years.vo.d.ts +11 -0
  2. package/dist/age-years.vo.js +9 -0
  3. package/dist/age.vo.d.ts +11 -16
  4. package/dist/age.vo.js +20 -31
  5. package/dist/api-key.vo.d.ts +3 -1
  6. package/dist/api-key.vo.js +10 -5
  7. package/dist/basename.vo.d.ts +9 -9
  8. package/dist/basename.vo.js +22 -22
  9. package/dist/clock.vo.d.ts +10 -4
  10. package/dist/clock.vo.js +12 -14
  11. package/dist/date-calculator.service.d.ts +2 -1
  12. package/dist/date-formatter.service.d.ts +3 -4
  13. package/dist/date-range.vo.d.ts +7 -1
  14. package/dist/date-range.vo.js +5 -2
  15. package/dist/day-iso-id.vo.d.ts +5 -2
  16. package/dist/day-iso-id.vo.js +11 -7
  17. package/dist/day.vo.d.ts +4 -3
  18. package/dist/day.vo.js +18 -16
  19. package/dist/directory-path-absolute.vo.d.ts +10 -6
  20. package/dist/directory-path-absolute.vo.js +19 -17
  21. package/dist/directory-path-relative.vo.d.ts +10 -7
  22. package/dist/directory-path-relative.vo.js +18 -17
  23. package/dist/division-factor.vo.d.ts +7 -0
  24. package/dist/division-factor.vo.js +9 -0
  25. package/dist/duration-ms.vo.d.ts +6 -0
  26. package/dist/duration-ms.vo.js +3 -0
  27. package/dist/duration.service.d.ts +2 -14
  28. package/dist/duration.service.js +16 -35
  29. package/dist/email-mask.service.d.ts +1 -6
  30. package/dist/email-mask.service.js +6 -8
  31. package/dist/etags.vo.d.ts +4 -3
  32. package/dist/etags.vo.js +3 -3
  33. package/dist/extension.vo.d.ts +6 -4
  34. package/dist/extension.vo.js +15 -10
  35. package/dist/feature-flag-value.vo.d.ts +10 -0
  36. package/dist/feature-flag-value.vo.js +8 -0
  37. package/dist/feature-flag.vo.d.ts +1 -7
  38. package/dist/feature-flag.vo.js +1 -7
  39. package/dist/file-path-absolute-schema.vo.d.ts +10 -7
  40. package/dist/file-path-absolute-schema.vo.js +17 -17
  41. package/dist/file-path-relative-schema.vo.d.ts +10 -7
  42. package/dist/file-path-relative-schema.vo.js +14 -12
  43. package/dist/file-path.vo.d.ts +4 -4
  44. package/dist/file-path.vo.js +8 -8
  45. package/dist/filename-from-string.vo.d.ts +4 -2
  46. package/dist/filename-from-string.vo.js +10 -8
  47. package/dist/filename-suffix.vo.d.ts +7 -3
  48. package/dist/filename-suffix.vo.js +13 -7
  49. package/dist/filename.vo.d.ts +2 -0
  50. package/dist/filename.vo.js +8 -2
  51. package/dist/height-milimiters.vo.d.ts +6 -0
  52. package/dist/height-milimiters.vo.js +10 -0
  53. package/dist/height.vo.d.ts +3 -20
  54. package/dist/height.vo.js +11 -62
  55. package/dist/hour-format.service.js +1 -1
  56. package/dist/hour-schema.vo.d.ts +7 -0
  57. package/dist/hour-schema.vo.js +8 -0
  58. package/dist/hour.vo.d.ts +4 -3
  59. package/dist/hour.vo.js +8 -8
  60. package/dist/iban-mask.service.d.ts +1 -3
  61. package/dist/iban-mask.service.js +2 -2
  62. package/dist/iban-schema.vo.d.ts +7 -0
  63. package/dist/iban-schema.vo.js +10 -0
  64. package/dist/iban.vo.d.ts +4 -10
  65. package/dist/iban.vo.js +6 -13
  66. package/dist/image.vo.d.ts +6 -4
  67. package/dist/image.vo.js +13 -12
  68. package/dist/index.d.ts +24 -2
  69. package/dist/index.js +24 -2
  70. package/dist/language.vo.d.ts +2 -1
  71. package/dist/language.vo.js +6 -4
  72. package/dist/linear-regression.service.d.ts +27 -0
  73. package/dist/{simple-linear-regression.service.js → linear-regression.service.js} +17 -15
  74. package/dist/mean.service.d.ts +3 -1
  75. package/dist/mean.service.js +3 -4
  76. package/dist/mime-types.vo.d.ts +1 -2
  77. package/dist/mime-value.vo.d.ts +9 -0
  78. package/dist/mime-value.vo.js +9 -0
  79. package/dist/mime.vo.d.ts +11 -17
  80. package/dist/mime.vo.js +10 -27
  81. package/dist/min-max-scaler.service.d.ts +7 -5
  82. package/dist/min-max-scaler.service.js +12 -10
  83. package/dist/minute-schema.vo.d.ts +7 -0
  84. package/dist/minute-schema.vo.js +8 -0
  85. package/dist/minute.vo.d.ts +4 -3
  86. package/dist/minute.vo.js +8 -8
  87. package/dist/money-amount.vo.d.ts +7 -0
  88. package/dist/money-amount.vo.js +7 -0
  89. package/dist/money.vo.d.ts +9 -18
  90. package/dist/money.vo.js +14 -27
  91. package/dist/month-iso-id.vo.d.ts +4 -2
  92. package/dist/month-iso-id.vo.js +13 -7
  93. package/dist/month.vo.d.ts +4 -3
  94. package/dist/month.vo.js +21 -21
  95. package/dist/multiplication-factor.vo.d.ts +7 -0
  96. package/dist/multiplication-factor.vo.js +9 -0
  97. package/dist/object-key.vo.d.ts +9 -6
  98. package/dist/object-key.vo.js +20 -19
  99. package/dist/outlier-detector.service.d.ts +3 -1
  100. package/dist/outlier-detector.service.js +2 -2
  101. package/dist/package-version-schema.vo.d.ts +11 -0
  102. package/dist/package-version-schema.vo.js +15 -0
  103. package/dist/package-version.vo.d.ts +11 -20
  104. package/dist/package-version.vo.js +11 -20
  105. package/dist/pagination-page.vo.d.ts +6 -0
  106. package/dist/pagination-page.vo.js +7 -0
  107. package/dist/pagination-skip.vo.d.ts +7 -0
  108. package/dist/pagination-skip.vo.js +9 -0
  109. package/dist/pagination-take.vo.d.ts +7 -0
  110. package/dist/pagination-take.vo.js +9 -0
  111. package/dist/pagination.service.d.ts +3 -8
  112. package/dist/pagination.service.js +5 -12
  113. package/dist/percentage.service.d.ts +3 -1
  114. package/dist/percentage.service.js +2 -2
  115. package/dist/population-standard-deviation.service.d.ts +3 -1
  116. package/dist/population-standard-deviation.service.js +5 -4
  117. package/dist/quarter-iso-id.vo.d.ts +3 -2
  118. package/dist/quarter-iso-id.vo.js +7 -9
  119. package/dist/quarter.vo.d.ts +2 -1
  120. package/dist/quarter.vo.js +10 -7
  121. package/dist/random.service.d.ts +3 -4
  122. package/dist/random.service.js +5 -11
  123. package/dist/rate-limiter.service.d.ts +2 -2
  124. package/dist/rate-limiter.service.js +8 -8
  125. package/dist/reordering-item-position-value.vo.d.ts +6 -0
  126. package/dist/reordering-item-position-value.vo.js +6 -0
  127. package/dist/reordering.service.d.ts +7 -23
  128. package/dist/reordering.service.js +15 -24
  129. package/dist/revision-value.vo.d.ts +7 -0
  130. package/dist/revision-value.vo.js +6 -0
  131. package/dist/revision.vo.d.ts +6 -13
  132. package/dist/revision.vo.js +10 -22
  133. package/dist/rounding.adapter.d.ts +7 -2
  134. package/dist/rounding.adapter.js +13 -5
  135. package/dist/size-bytes.vo.d.ts +6 -0
  136. package/dist/size-bytes.vo.js +7 -0
  137. package/dist/size.vo.d.ts +15 -15
  138. package/dist/size.vo.js +41 -51
  139. package/dist/stopwatch.service.d.ts +3 -1
  140. package/dist/stopwatch.service.js +2 -2
  141. package/dist/sum.service.js +8 -8
  142. package/dist/thousands-separator.service.js +4 -1
  143. package/dist/time.service.d.ts +8 -0
  144. package/dist/time.service.js +13 -0
  145. package/dist/timestamp.vo.d.ts +1 -1
  146. package/dist/timestamp.vo.js +4 -5
  147. package/dist/timezone.vo.d.ts +4 -1
  148. package/dist/timezone.vo.js +12 -6
  149. package/dist/tsconfig.tsbuildinfo +1 -1
  150. package/dist/week-iso-id.vo.d.ts +4 -2
  151. package/dist/week-iso-id.vo.js +15 -9
  152. package/dist/week.vo.d.ts +4 -3
  153. package/dist/week.vo.js +21 -22
  154. package/dist/weekday.vo.d.ts +1 -1
  155. package/dist/weekday.vo.js +6 -8
  156. package/dist/weight-grams.vo.d.ts +7 -0
  157. package/dist/weight-grams.vo.js +7 -0
  158. package/dist/weight.vo.d.ts +12 -35
  159. package/dist/weight.vo.js +23 -72
  160. package/dist/year-iso-id.vo.d.ts +3 -2
  161. package/dist/year-iso-id.vo.js +6 -4
  162. package/dist/year.vo.d.ts +5 -6
  163. package/dist/year.vo.js +21 -26
  164. package/dist/z-score.service.d.ts +3 -1
  165. package/dist/z-score.service.js +2 -2
  166. package/package.json +4 -4
  167. package/readme.md +21 -2
  168. package/src/age-years.vo.ts +14 -0
  169. package/src/age.vo.ts +22 -35
  170. package/src/api-key.vo.ts +11 -5
  171. package/src/basename.vo.ts +24 -22
  172. package/src/clock.vo.ts +16 -17
  173. package/src/date-calculator.service.ts +2 -1
  174. package/src/date-formatter.service.ts +4 -5
  175. package/src/date-range.vo.ts +6 -2
  176. package/src/day-iso-id.vo.ts +12 -8
  177. package/src/day.vo.ts +27 -24
  178. package/src/directory-path-absolute.vo.ts +23 -18
  179. package/src/directory-path-relative.vo.ts +21 -18
  180. package/src/division-factor.vo.ts +13 -0
  181. package/src/duration-ms.vo.ts +7 -0
  182. package/src/duration.service.ts +16 -40
  183. package/src/email-mask.service.ts +7 -15
  184. package/src/etags.vo.ts +4 -5
  185. package/src/extension.vo.ts +17 -10
  186. package/src/feature-flag-value.vo.ts +12 -0
  187. package/src/feature-flag.vo.ts +1 -9
  188. package/src/file-path-absolute-schema.vo.ts +18 -17
  189. package/src/file-path-relative-schema.vo.ts +15 -12
  190. package/src/file-path.vo.ts +8 -8
  191. package/src/filename-from-string.vo.ts +12 -9
  192. package/src/filename-suffix.vo.ts +14 -7
  193. package/src/filename.vo.ts +11 -2
  194. package/src/height-milimiters.vo.ts +12 -0
  195. package/src/height.vo.ts +12 -83
  196. package/src/hour-format.service.ts +2 -1
  197. package/src/hour-schema.vo.ts +12 -0
  198. package/src/hour.vo.ts +12 -12
  199. package/src/iban-mask.service.ts +3 -5
  200. package/src/iban-schema.vo.ts +15 -0
  201. package/src/iban.vo.ts +9 -22
  202. package/src/image.vo.ts +14 -12
  203. package/src/index.ts +24 -2
  204. package/src/language.vo.ts +7 -4
  205. package/src/linear-regression.service.ts +71 -0
  206. package/src/mean.service.ts +3 -5
  207. package/src/mime-types.vo.ts +1 -3
  208. package/src/mime-value.vo.ts +12 -0
  209. package/src/mime.vo.ts +12 -33
  210. package/src/min-max-scaler.service.ts +13 -11
  211. package/src/minute-schema.vo.ts +12 -0
  212. package/src/minute.vo.ts +12 -12
  213. package/src/money-amount.vo.ts +11 -0
  214. package/src/money.vo.ts +20 -38
  215. package/src/month-iso-id.vo.ts +14 -7
  216. package/src/month.vo.ts +25 -24
  217. package/src/multiplication-factor.vo.ts +13 -0
  218. package/src/object-key.vo.ts +25 -21
  219. package/src/outlier-detector.service.ts +2 -2
  220. package/src/package-version-schema.vo.ts +21 -0
  221. package/src/package-version.vo.ts +17 -33
  222. package/src/pagination-page.vo.ts +11 -0
  223. package/src/pagination-skip.vo.ts +13 -0
  224. package/src/pagination-take.vo.ts +13 -0
  225. package/src/pagination.service.ts +5 -22
  226. package/src/percentage.service.ts +2 -2
  227. package/src/population-standard-deviation.service.ts +5 -4
  228. package/src/quarter-iso-id.vo.ts +7 -10
  229. package/src/quarter.vo.ts +14 -9
  230. package/src/random.service.ts +6 -9
  231. package/src/rate-limiter.service.ts +9 -8
  232. package/src/reordering-item-position-value.vo.ts +10 -0
  233. package/src/reordering.service.ts +19 -28
  234. package/src/revision-value.vo.ts +10 -0
  235. package/src/revision.vo.ts +10 -25
  236. package/src/rounding.adapter.ts +16 -3
  237. package/src/size-bytes.vo.ts +11 -0
  238. package/src/size.vo.ts +43 -54
  239. package/src/stopwatch.service.ts +3 -3
  240. package/src/sum.service.ts +8 -8
  241. package/src/thousands-separator.service.ts +4 -1
  242. package/src/time.service.ts +15 -0
  243. package/src/timestamp.vo.ts +4 -5
  244. package/src/timezone.vo.ts +12 -6
  245. package/src/week-iso-id.vo.ts +16 -12
  246. package/src/week.vo.ts +26 -28
  247. package/src/weekday.vo.ts +6 -9
  248. package/src/weight-grams.vo.ts +11 -0
  249. package/src/weight.vo.ts +28 -85
  250. package/src/year-iso-id.vo.ts +7 -4
  251. package/src/year.vo.ts +27 -33
  252. package/src/z-score.service.ts +2 -2
  253. package/dist/simple-linear-regression.service.d.ts +0 -25
  254. package/dist/streak-calculator.service.d.ts +0 -13
  255. package/dist/streak-calculator.service.js +0 -22
  256. package/src/simple-linear-regression.service.ts +0 -69
  257. package/src/streak-calculator.service.ts +0 -32
@@ -1,14 +1,7 @@
1
- import { z } from "zod/v4";
2
1
  import type { ETag, WeakETag } from "./etags.vo";
2
+ import { RevisionValue, type RevisionValueType } from "./revision-value.vo";
3
3
 
4
- export const RevisionValueError = { error: "invalid.revision.value" } as const;
5
-
6
- export const RevisionValue = z.number(RevisionValueError).int(RevisionValueError).min(0, RevisionValueError);
7
-
8
- export type RevisionValueType = z.infer<typeof RevisionValue>;
9
-
10
- export const RevisionInvalidErrorMessage = "revision.invalid" as const;
11
- export const RevisionMismatchErrorMessage = "revision.mismatch" as const;
4
+ export const RevisionError = { Missing: "revision.missing", Mismatch: "revision.mismatch" } as const;
12
5
 
13
6
  export class Revision {
14
7
  static readonly INITIAL: RevisionValueType = RevisionValue.parse(0);
@@ -16,9 +9,7 @@ export class Revision {
16
9
  readonly value: RevisionValueType;
17
10
 
18
11
  constructor(value: unknown) {
19
- const result = RevisionValue.safeParse(value);
20
- if (!result.success) throw new InvalidRevisionError();
21
- this.value = result.data;
12
+ this.value = RevisionValue.parse(value);
22
13
  }
23
14
 
24
15
  equals(another: RevisionValueType): boolean {
@@ -26,7 +17,7 @@ export class Revision {
26
17
  }
27
18
 
28
19
  validate(another: RevisionValueType): void {
29
- if (!this.equals(another)) throw new RevisionMismatchError();
20
+ if (!this.equals(another)) throw new Error(RevisionError.Mismatch);
30
21
  }
31
22
 
32
23
  next(): Revision {
@@ -34,26 +25,20 @@ export class Revision {
34
25
  }
35
26
 
36
27
  static fromETag(etag: ETag | null): Revision {
37
- if (!etag) throw new InvalidRevisionError();
28
+ if (!etag) throw new Error(RevisionError.Missing);
38
29
  return new Revision(etag.revision);
39
30
  }
40
31
 
41
32
  static fromWeakETag(weakEtag: WeakETag | null): Revision {
42
- if (!weakEtag) throw new InvalidRevisionError();
33
+ if (!weakEtag) throw new Error(RevisionError.Missing);
43
34
  return new Revision(weakEtag.revision);
44
35
  }
45
- }
46
36
 
47
- export class RevisionMismatchError extends Error {
48
- constructor() {
49
- super(RevisionMismatchErrorMessage);
50
- Object.setPrototypeOf(this, RevisionMismatchError.prototype);
37
+ toString(): string {
38
+ return this.value.toString();
51
39
  }
52
- }
53
40
 
54
- export class InvalidRevisionError extends Error {
55
- constructor() {
56
- super(RevisionInvalidErrorMessage);
57
- Object.setPrototypeOf(this, InvalidRevisionError.prototype);
41
+ toJSON(): number {
42
+ return this.value;
58
43
  }
59
44
  }
@@ -1,3 +1,4 @@
1
+ import { z } from "zod/v4";
1
2
  import type { RoundingPort } from "./rounding.port";
2
3
 
3
4
  export class RoundToNearest implements RoundingPort {
@@ -18,11 +19,23 @@ export class RoundDown implements RoundingPort {
18
19
  }
19
20
  }
20
21
 
21
- export const RoundingDecimalsError = "invalid.rounding.decimals" as const;
22
+ export const RoundingDecimalError = {
23
+ Type: "rounding.decimal.type",
24
+ Invalid: "rounding.decimal.invalid",
25
+ } as const;
26
+
27
+ export const RoundingDecimal = z
28
+ .number(RoundingDecimalError.Type)
29
+ .int(RoundingDecimalError.Invalid)
30
+ .min(1, RoundingDecimalError.Invalid)
31
+ .max(100, RoundingDecimalError.Invalid)
32
+ .brand("RoundingDecimal");
22
33
 
23
34
  export class RoundToDecimal implements RoundingPort {
24
- constructor(private readonly decimals: number) {
25
- if (!Number.isInteger(decimals) || decimals < 0 || decimals > 100) throw new Error(RoundingDecimalsError);
35
+ private readonly decimals: number;
36
+
37
+ constructor(candidate: number) {
38
+ this.decimals = RoundingDecimal.parse(candidate);
26
39
  }
27
40
 
28
41
  round(value: number): number {
@@ -0,0 +1,11 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const SizeBytesError = { Invalid: "size.bytes.invalid" } as const;
4
+
5
+ export const SizeBytes = z
6
+ .number(SizeBytesError.Invalid)
7
+ .int(SizeBytesError.Invalid)
8
+ .gte(0, SizeBytesError.Invalid)
9
+ .brand("SizeBytes");
10
+
11
+ export type SizeBytesType = z.infer<typeof SizeBytes>;
package/src/size.vo.ts CHANGED
@@ -1,22 +1,18 @@
1
- import { z } from "zod/v4";
2
1
  import { RoundToDecimal } from "./rounding.adapter";
2
+ import { SizeBytes, type SizeBytesType } from "./size-bytes.vo";
3
3
 
4
- export enum SizeUnit {
4
+ enum SizeUnitEnum {
5
5
  b = "b",
6
6
  kB = "kB",
7
7
  MB = "MB",
8
8
  GB = "GB",
9
9
  }
10
10
 
11
- export const SizeValue = z.number().positive().brand("SizeValue");
12
- type SizeValueType = z.infer<typeof SizeValue>;
13
-
14
- type SizeConfigType = { unit: SizeUnit; value: number };
11
+ type SizeConfigType = { unit: SizeUnitEnum; value: number };
15
12
 
16
13
  export class Size {
17
- private readonly unit: SizeUnit;
18
- private readonly value: SizeValueType;
19
- private readonly bytes: SizeValueType;
14
+ private readonly unit: SizeUnitEnum;
15
+ private readonly bytes: SizeBytesType;
20
16
 
21
17
  private static readonly KB_MULTIPLIER = 1024;
22
18
  private static readonly MB_MULTIPLIER = 1024 * Size.KB_MULTIPLIER;
@@ -26,35 +22,26 @@ export class Size {
26
22
 
27
23
  constructor(config: SizeConfigType) {
28
24
  this.unit = config.unit;
29
- this.value = SizeValue.parse(config.value);
30
- this.bytes = this.calculateBytes();
31
- }
32
-
33
- static fromBytes(candidate: number): Size {
34
- const value = SizeValue.parse(candidate);
35
- return new Size({ value, unit: SizeUnit.b });
25
+ this.bytes = this.calculateBytes(config.value, config.unit);
36
26
  }
37
27
 
38
- static fromKb(candidate: number): Size {
39
- const value = SizeValue.parse(candidate);
40
- return new Size({ value, unit: SizeUnit.kB });
28
+ static fromBytes(value: SizeConfigType["value"]): Size {
29
+ return new Size({ value, unit: SizeUnitEnum.b });
41
30
  }
42
31
 
43
- static fromMB(candidate: number): Size {
44
- const value = SizeValue.parse(candidate);
45
- return new Size({ value, unit: SizeUnit.MB });
32
+ static fromKb(value: SizeConfigType["value"]): Size {
33
+ return new Size({ value, unit: SizeUnitEnum.kB });
46
34
  }
47
35
 
48
- static fromGB(candidate: number): Size {
49
- const value = SizeValue.parse(candidate);
50
- return new Size({ value, unit: SizeUnit.GB });
36
+ static fromMB(value: SizeConfigType["value"]): Size {
37
+ return new Size({ value, unit: SizeUnitEnum.MB });
51
38
  }
52
39
 
53
- toString(): string {
54
- return `${this.value} ${this.unit}`;
40
+ static fromGB(value: SizeConfigType["value"]): Size {
41
+ return new Size({ value, unit: SizeUnitEnum.GB });
55
42
  }
56
43
 
57
- toBytes(): SizeValueType {
44
+ toBytes(): SizeBytesType {
58
45
  return this.bytes;
59
46
  }
60
47
 
@@ -62,41 +49,43 @@ export class Size {
62
49
  return this.bytes > another.toBytes();
63
50
  }
64
51
 
65
- format(unit: SizeUnit): string {
52
+ format(unit: SizeUnitEnum): string {
66
53
  switch (unit) {
67
- case SizeUnit.kB: {
68
- return `${Size.ROUNDER.round(this.bytes / Size.KB_MULTIPLIER)} ${SizeUnit.kB}`;
69
- }
70
- case SizeUnit.MB: {
71
- return `${Size.ROUNDER.round(this.bytes / Size.MB_MULTIPLIER)} ${SizeUnit.MB}`;
72
- }
73
- case SizeUnit.GB: {
74
- return `${Size.ROUNDER.round(this.bytes / Size.GB_MULTIPLIER)} ${SizeUnit.GB}`;
75
- }
76
- default: {
77
- // SizeUnit.b
78
- return `${this.bytes} ${SizeUnit.b}`;
79
- }
54
+ case SizeUnitEnum.kB:
55
+ return `${Size.ROUNDER.round(this.bytes / Size.KB_MULTIPLIER)} ${SizeUnitEnum.kB}`;
56
+ case SizeUnitEnum.MB:
57
+ return `${Size.ROUNDER.round(this.bytes / Size.MB_MULTIPLIER)} ${SizeUnitEnum.MB}`;
58
+ case SizeUnitEnum.GB:
59
+ return `${Size.ROUNDER.round(this.bytes / Size.GB_MULTIPLIER)} ${SizeUnitEnum.GB}`;
60
+ default:
61
+ return `${this.bytes} ${SizeUnitEnum.b}`;
80
62
  }
81
63
  }
82
64
 
83
- static toBytes(config: SizeConfigType): SizeValueType {
65
+ static toBytes(config: SizeConfigType): SizeBytesType {
84
66
  return new Size(config).toBytes();
85
67
  }
86
68
 
87
- static unit = SizeUnit;
69
+ static unit = SizeUnitEnum;
88
70
 
89
- private calculateBytes(): SizeValueType {
90
- switch (this.unit) {
91
- case SizeUnit.kB:
92
- return SizeValue.parse(this.value * Size.KB_MULTIPLIER);
93
- case SizeUnit.MB:
94
- return SizeValue.parse(this.value * Size.MB_MULTIPLIER);
95
- case SizeUnit.GB:
96
- return SizeValue.parse(this.value * Size.GB_MULTIPLIER);
71
+ private calculateBytes(value: SizeConfigType["value"], unit: SizeUnitEnum): SizeBytesType {
72
+ switch (unit) {
73
+ case SizeUnitEnum.kB:
74
+ return SizeBytes.parse(value * Size.KB_MULTIPLIER);
75
+ case SizeUnitEnum.MB:
76
+ return SizeBytes.parse(value * Size.MB_MULTIPLIER);
77
+ case SizeUnitEnum.GB:
78
+ return SizeBytes.parse(value * Size.GB_MULTIPLIER);
97
79
  default:
98
- // SizeUnit.b
99
- return this.value;
80
+ return SizeBytes.parse(value);
100
81
  }
101
82
  }
83
+
84
+ toString(): string {
85
+ return this.format(this.unit);
86
+ }
87
+
88
+ toJSON(): { bytes: number } {
89
+ return { bytes: this.bytes };
90
+ }
102
91
  }
@@ -1,13 +1,13 @@
1
1
  import { Duration } from "./duration.service";
2
2
  import { Timestamp, type TimestampType } from "./timestamp.vo";
3
3
 
4
+ export const StopwatchError = { AlreadyStopped: "stopwatch.already.stopped" } as const;
5
+
4
6
  enum StopwatchState {
5
7
  started = "started",
6
8
  stopped = "stopped",
7
9
  }
8
10
 
9
- export const StopwatchStateError = "stopwatch.already.stopped" as const;
10
-
11
11
  export type StopwatchResultType = Duration;
12
12
 
13
13
  export class Stopwatch {
@@ -16,7 +16,7 @@ export class Stopwatch {
16
16
  constructor(private readonly startMs: TimestampType) {}
17
17
 
18
18
  stop(): StopwatchResultType {
19
- if (this.state === StopwatchState.stopped) throw new Error(StopwatchStateError);
19
+ if (this.state === StopwatchState.stopped) throw new Error(StopwatchError.AlreadyStopped);
20
20
 
21
21
  this.state = StopwatchState.stopped;
22
22
 
@@ -4,16 +4,16 @@ export class Sum {
4
4
  }
5
5
 
6
6
  static precise(values: readonly number[]): number {
7
- let runningTotal = 0;
8
- let roundingCompensation = 0;
7
+ let sum = 0;
8
+ let compensation = 0;
9
9
 
10
- for (const currentValue of values) {
11
- const correctedAddend = currentValue - roundingCompensation;
12
- const tentativeTotal = runningTotal + correctedAddend;
13
- roundingCompensation = tentativeTotal - runningTotal - correctedAddend;
14
- runningTotal = tentativeTotal;
10
+ for (const value of values) {
11
+ const adjusted = value - compensation;
12
+ const temporary = sum + adjusted;
13
+ compensation = temporary - sum - adjusted;
14
+ sum = temporary;
15
15
  }
16
16
 
17
- return runningTotal;
17
+ return sum;
18
18
  }
19
19
  }
@@ -2,6 +2,9 @@ export class ThousandsSeparator {
2
2
  private static DEFAULT_SEPARATOR = " ";
3
3
 
4
4
  static format(value: number, separator = ThousandsSeparator.DEFAULT_SEPARATOR): string {
5
- return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator);
5
+ // B - not a word boundary, prevents inserting at the very start
6
+ // (?=([0-9]{3})+(?![0-9])) - positive lookahead, find three digits
7
+ // (?![0-9]) - negative lookahead, the next character is not a digit
8
+ return value.toString().replace(/\B(?=([0-9]{3})+(?![0-9]))/g, separator);
6
9
  }
7
10
  }
@@ -0,0 +1,15 @@
1
+ import type { Duration } from "./duration.service";
2
+ import { Timestamp, type TimestampType } from "./timestamp.vo";
3
+
4
+ export const Time = {
5
+ Now(now: TimestampType) {
6
+ return {
7
+ Add(duration: Duration): TimestampType {
8
+ return Timestamp.parse(now + duration.ms);
9
+ },
10
+ Minus(duration: Duration): TimestampType {
11
+ return Timestamp.parse(now - duration.ms);
12
+ },
13
+ };
14
+ },
15
+ };
@@ -1,12 +1,11 @@
1
1
  import { z } from "zod/v4";
2
2
 
3
- export const TimestampError = { error: "invalid.timestamp" } as const;
3
+ export const TimestampError = { Invalid: "timestamp.invalid" } as const;
4
4
 
5
5
  export const Timestamp = z
6
- .number(TimestampError)
7
- .int(TimestampError)
8
- .gte(0, TimestampError)
9
- .lte(Number.MAX_SAFE_INTEGER, TimestampError)
6
+ .number(TimestampError.Invalid)
7
+ .int(TimestampError.Invalid)
8
+ .gte(0, TimestampError.Invalid)
10
9
  .brand("Timestamp");
11
10
 
12
11
  export type TimestampType = z.infer<typeof Timestamp>;
@@ -1,18 +1,24 @@
1
1
  import { z } from "zod/v4";
2
2
 
3
- export const TimezoneError = { error: "timezone.invalid" } as const;
3
+ export const TimezoneError = {
4
+ Type: "timezone.type",
5
+ Empty: "timezone.empty",
6
+ TooLong: "timezone.too.long",
7
+ Invalid: "timezone.invalid",
8
+ } as const;
4
9
 
5
10
  export const Timezone = z
6
- .string(TimezoneError)
7
- .min(1, TimezoneError)
11
+ .string(TimezoneError.Type)
12
+ .min(1, TimezoneError.Empty)
13
+ .max(128, TimezoneError.TooLong)
8
14
  .refine((value) => {
9
15
  try {
10
- new Intl.DateTimeFormat("en-US", { timeZone: value }).format(new Date());
16
+ new Intl.DateTimeFormat("en-US", { timeZone: value }).format(Date.now());
11
17
  return true;
12
- } catch (_error) {
18
+ } catch {
13
19
  return false;
14
20
  }
15
- }, TimezoneError)
21
+ }, TimezoneError.Invalid)
16
22
  .brand("Timezone");
17
23
 
18
24
  export type TimezoneType = z.infer<typeof Timezone>;
@@ -1,22 +1,26 @@
1
1
  import { getISOWeeksInYear } from "date-fns";
2
2
  import { z } from "zod/v4";
3
3
 
4
- export const WeekIsoIdError = { error: "week-iso-id.invalid" } as const;
4
+ export const WeekIsoIdError = {
5
+ Type: "week.iso.id.type",
6
+ BadChars: "week.iso.id.bad.chars",
7
+ Invalid: "week.iso.id.invalid",
8
+ } as const;
9
+
10
+ // Four digits, hypen, W, followed by two digits
11
+ const WEEK_ISO_ID_CHARS_WHITELIST = /^[0-9]{4}-W[0-9]{2}$/;
5
12
 
6
13
  export const WeekIsoId = z
7
- .string(WeekIsoIdError)
8
- .regex(/^\d{4}-W\d{2}$/, WeekIsoIdError)
14
+ .string(WeekIsoIdError.Type)
15
+ .regex(WEEK_ISO_ID_CHARS_WHITELIST, WeekIsoIdError.BadChars)
9
16
  .refine((value) => {
10
- const [yearPart, weekPart] = value.split("-W");
11
-
12
- const year = Number(yearPart);
13
- const week = Number(weekPart);
14
-
15
- if (!(Number.isInteger(year) && Number.isInteger(week)) || week < 1) return false;
16
-
17
- const weeksInYear = getISOWeeksInYear(new Date(Date.UTC(year, 0, 4)));
17
+ const [year, week] = value.split("-W").map(Number);
18
+ // ISO-8601 rule: Jan 4 is always in week 01 of the ISO week-year.
19
+ const weeksInYear = getISOWeeksInYear(Date.UTC(year, 0, 4));
18
20
 
21
+ if (week < 1) return false;
19
22
  return week <= weeksInYear;
20
- }, WeekIsoIdError);
23
+ }, WeekIsoIdError.Invalid)
24
+ .brand("WeekIsoId");
21
25
 
22
26
  export type WeekIsoIdType = z.infer<typeof WeekIsoId>;
package/src/week.vo.ts CHANGED
@@ -4,50 +4,48 @@ import { Timestamp, type TimestampType } from "./timestamp.vo";
4
4
  import { WeekIsoId, type WeekIsoIdType } from "./week-iso-id.vo";
5
5
 
6
6
  export class Week extends DateRange {
7
- toIsoId(): WeekIsoIdType {
8
- const year = getISOWeekYear(this.getStart());
9
- const week = getISOWeek(this.getStart()).toString().padStart(2, "0");
7
+ static fromTimestamp(timestamp: TimestampType): Week {
8
+ const start = Timestamp.parse(startOfISOWeek(timestamp).getTime());
9
+ const end = Timestamp.parse(endOfISOWeek(timestamp).getTime());
10
10
 
11
- return WeekIsoId.parse(`${year}-W${week}`);
11
+ return new Week(start, end);
12
12
  }
13
13
 
14
- previous(): Week {
15
- const shifted = addWeeks(new Date(this.getStart()), -1).getTime();
16
-
17
- return Week.fromTimestamp(Timestamp.parse(shifted));
14
+ static fromNow(now: TimestampType): Week {
15
+ return Week.fromTimestamp(now);
18
16
  }
19
17
 
20
- next(): Week {
21
- const shifted = addWeeks(new Date(this.getStart()), 1).getTime();
18
+ static fromIsoId(isoId: WeekIsoIdType): Week {
19
+ const [year, week] = WeekIsoId.parse(isoId).split("-W").map(Number);
22
20
 
23
- return Week.fromTimestamp(Timestamp.parse(shifted));
21
+ // ISO-8601 rule: Jan 4 is always in week 01 of the ISO week-year.
22
+ const reference = setISOWeek(Date.UTC(year, 0, 4), week).getTime();
23
+
24
+ return Week.fromTimestamp(Timestamp.parse(reference));
24
25
  }
25
26
 
26
- shift(count: number): Week {
27
- const shifted = addWeeks(new Date(this.getStart()), count).getTime();
27
+ toIsoId(): WeekIsoIdType {
28
+ const year = getISOWeekYear(this.getStart());
29
+ const week = getISOWeek(this.getStart());
28
30
 
29
- return Week.fromTimestamp(Timestamp.parse(shifted));
31
+ return WeekIsoId.parse(`${year}-W${String(week).padStart(2, "0")}`);
30
32
  }
31
33
 
32
- static fromTimestamp(timestamp: TimestampType): Week {
33
- const start = Timestamp.parse(startOfISOWeek(timestamp).getTime());
34
- const end = Timestamp.parse(endOfISOWeek(timestamp).getTime());
35
-
36
- return new Week(start, end);
34
+ previous(): Week {
35
+ return this.shift(-1);
37
36
  }
38
37
 
39
- static fromNow(now: TimestampType): Week {
40
- return Week.fromTimestamp(now);
38
+ next(): Week {
39
+ return this.shift(1);
41
40
  }
42
41
 
43
- static fromIsoId(isoId: WeekIsoIdType): Week {
44
- const [yearPart, weekPart] = WeekIsoId.parse(isoId).split("-W");
45
- const year = Number(yearPart);
46
- const week = Number(weekPart);
42
+ shift(count: number): Week {
43
+ const shifted = addWeeks(this.getStart(), count).getTime();
47
44
 
48
- // ISO-8601 rule: Jan 4 is always in week 01 of the ISO week-year.
49
- const reference = setISOWeek(new Date(Date.UTC(year, 0, 4)), week);
45
+ return Week.fromTimestamp(Timestamp.parse(shifted));
46
+ }
50
47
 
51
- return Week.fromTimestamp(Timestamp.parse(reference.getTime()));
48
+ toString(): string {
49
+ return this.toIsoId();
52
50
  }
53
51
  }
package/src/weekday.vo.ts CHANGED
@@ -61,13 +61,12 @@ export class Weekday {
61
61
  return this.value;
62
62
  }
63
63
 
64
- format(formatter?: WeekdayFormatter): string {
65
- const chosen = formatter ?? this.formatter;
66
- return chosen(this.value);
64
+ format(): string {
65
+ return this.formatter(this.value);
67
66
  }
68
67
 
69
68
  toString(): string {
70
- return this.format(WeekdayFormatters.FULL);
69
+ return this.format();
71
70
  }
72
71
 
73
72
  equals(another: Weekday): boolean {
@@ -102,14 +101,12 @@ export class Weekday {
102
101
  }
103
102
 
104
103
  static list(formatter?: WeekdayFormatter): readonly Weekday[] {
105
- const chosen = formatter ?? undefined;
106
-
107
- return Array.from({ length: 7 }, (_, index) => new Weekday(index, chosen));
104
+ return Array.from({ length: 7 }, (_, index) => new Weekday(index, formatter));
108
105
  }
109
106
 
110
107
  static listMondayFirst(formatter?: WeekdayFormatter): readonly Weekday[] {
111
- const days = Weekday.list(formatter);
108
+ const [Sunday, ...rest] = Weekday.list(formatter);
112
109
 
113
- return [...days.slice(1), days[0]];
110
+ return [...rest, Sunday];
114
111
  }
115
112
  }
@@ -0,0 +1,11 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const WeightGramsError = { Type: "weight.grams.type", Invalid: "weight.grams.invalid" } as const;
4
+
5
+ export const WeightGrams = z
6
+ .number(WeightGramsError.Type)
7
+ .int(WeightGramsError.Type)
8
+ .gte(0, WeightGramsError.Invalid)
9
+ .brand("WeightGrams");
10
+
11
+ export type WeightGramsType = z.infer<typeof WeightGrams>;