@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.
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
package/src/month.vo.ts CHANGED
@@ -1,46 +1,47 @@
1
+ import { endOfMonth, format, getMonth, setMonth, startOfMonth } from "date-fns";
1
2
  import { DateRange } from "./date-range.vo";
2
3
  import { MonthIsoId, type MonthIsoIdType } from "./month-iso-id.vo";
3
4
  import { Timestamp, type TimestampType } from "./timestamp.vo";
4
5
 
5
6
  export class Month extends DateRange {
6
- toIsoId(): MonthIsoIdType {
7
- return new Date(this.getStart()).toISOString().slice(0, 7) as MonthIsoIdType;
8
- }
7
+ static fromTimestamp(timestamp: TimestampType): Month {
8
+ const start = Timestamp.parse(startOfMonth(timestamp).getTime());
9
+ const end = Timestamp.parse(endOfMonth(timestamp).getTime());
9
10
 
10
- previous(): Month {
11
- return Month.fromTimestamp(Timestamp.parse(this.getStart() - 1));
11
+ return new Month(start, end);
12
12
  }
13
13
 
14
- next(): Month {
15
- return Month.fromTimestamp(Timestamp.parse(this.getEnd() + 1));
14
+ static fromNow(now: TimestampType): Month {
15
+ return Month.fromTimestamp(now);
16
16
  }
17
17
 
18
- shift(count: number): Month {
19
- const date = new Date(this.getStart());
20
- date.setUTCMonth(date.getUTCMonth() + count);
18
+ static fromIsoId(iso: MonthIsoIdType): Month {
19
+ const [year, month] = MonthIsoId.parse(iso).split("-").map(Number);
20
+
21
+ const reference = setMonth(Date.UTC(year), month - 1).getTime();
21
22
 
22
- return Month.fromTimestamp(Timestamp.parse(date.getTime()));
23
+ return Month.fromTimestamp(Timestamp.parse(reference));
23
24
  }
24
25
 
25
- static fromTimestamp(timestamp: TimestampType): Month {
26
- const isoMonth = new Date(timestamp).toISOString().slice(0, 7) as MonthIsoIdType;
26
+ toIsoId(): MonthIsoIdType {
27
+ return MonthIsoId.parse(format(this.getStart(), "yyyy-MM"));
28
+ }
27
29
 
28
- return Month.fromIsoId(isoMonth);
30
+ previous(): Month {
31
+ return this.shift(-1);
29
32
  }
30
33
 
31
- static fromNow(now: TimestampType): Month {
32
- return Month.fromTimestamp(now);
34
+ next(): Month {
35
+ return this.shift(1);
33
36
  }
34
37
 
35
- static fromIsoId(iso: MonthIsoIdType): Month {
36
- const validated = MonthIsoId.parse(iso);
37
- const year = Number(validated.slice(0, 4));
38
- const monthIndex = Number(validated.slice(5, 7)) - 1;
38
+ shift(count: number): Month {
39
+ const shifted = setMonth(this.getStart(), getMonth(this.getStart()) + count).getTime();
39
40
 
40
- const startUtc = Date.UTC(year, monthIndex, 1);
41
- const nextStartUtc = Date.UTC(year, monthIndex + 1, 1);
42
- const endUtc = nextStartUtc - 1;
41
+ return Month.fromTimestamp(Timestamp.parse(shifted));
42
+ }
43
43
 
44
- return new Month(Timestamp.parse(startUtc), Timestamp.parse(endUtc));
44
+ toString(): string {
45
+ return this.toIsoId();
45
46
  }
46
47
  }
@@ -0,0 +1,13 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const MultiplicationFactorError = {
4
+ Type: "multiplication.factor.type",
5
+ Invalid: "multiplication.factor.invalid",
6
+ } as const;
7
+
8
+ export const MultiplicationFactor = z
9
+ .number(MultiplicationFactorError.Type)
10
+ .min(0, MultiplicationFactorError.Invalid)
11
+ .brand("MultiplicationFactor");
12
+
13
+ export type MultiplicationFactorType = z.infer<typeof MultiplicationFactor>;
@@ -1,30 +1,34 @@
1
1
  import { z } from "zod/v4";
2
2
 
3
- export const ObjectKeyMustNotStartWithSlashError = "obj_key_must_not_start_with_slash" as const;
4
- export const ObjectKeyBackslashForbiddenError = "obj_key_backslash_forbidden" as const;
5
- export const ObjectKeyControlCharsForbiddenError = "obj_key_control_chars_forbidden" as const;
6
- export const ObjectKeyEmptyError = "obj_key_empty" as const;
7
- export const ObjectKeyBadSegmentsError = "obj_key_bad_segments" as const;
3
+ export const ObjectKeyError = {
4
+ Type: "object.key.type",
5
+ LeadingSlash: "object.key.leading.slash",
6
+ Empty: "object.key.empty",
7
+ TooLong: "object.key.too.long",
8
+ BadChars: "object.key.bad.chars",
9
+ DotSegments: "object.key.dot.segments",
10
+ } as const;
8
11
 
9
- // biome-ignore lint: lint/suspicious/noControlCharactersInRegex
10
- const CONTROL_CHARS_REGEX = /[\u0000-\u001F\u007F]/;
11
- const SEGMENT_ALLOWED_REGEX = /^[a-z0-9._-]+$/;
12
+ // Lowercase letters, digits, dots, underscores, and hyphens
13
+ const OBJECT_KEY_SEGMENT_CHARS_WHITELIST = /^[a-z0-9._-]+$/;
14
+
15
+ const DOT_SEGMENTS = [".", ".."];
12
16
 
13
17
  export const ObjectKey = z
14
- .string()
15
- .trim()
16
- // fastest early exits first:
17
- .refine((value) => value.length > 0, ObjectKeyEmptyError)
18
- .refine((value) => !value.startsWith("/"), ObjectKeyMustNotStartWithSlashError)
19
- .refine((value) => !value.includes("\\"), ObjectKeyBackslashForbiddenError)
20
- .refine((value) => !CONTROL_CHARS_REGEX.test(value), ObjectKeyControlCharsForbiddenError)
18
+ .string(ObjectKeyError.Type)
19
+ .min(1, ObjectKeyError.Empty)
20
+ .max(256, ObjectKeyError.TooLong)
21
+ .refine((value) => !value.startsWith("/"), ObjectKeyError.LeadingSlash)
22
+ // Allow only known characters for users/avatars/1234567890/avatar.png segments
23
+ .refine(
24
+ (value) => value.split("/").every((segment) => OBJECT_KEY_SEGMENT_CHARS_WHITELIST.test(segment)),
25
+ ObjectKeyError.BadChars,
26
+ )
27
+ // Reject object keys like users/./avatar.png or users/../avatar.png
21
28
  .refine(
22
- (value) =>
23
- value
24
- .split("/")
25
- .every((segment) => SEGMENT_ALLOWED_REGEX.test(segment) && segment !== "." && segment !== ".."),
26
- ObjectKeyBadSegmentsError,
29
+ (value) => value.split("/").every((segment) => !DOT_SEGMENTS.includes(segment)),
30
+ ObjectKeyError.DotSegments,
27
31
  )
28
- .brand("object_key");
32
+ .brand("ObjectKey");
29
33
 
30
34
  export type ObjectKeyType = z.infer<typeof ObjectKey>;
@@ -1,13 +1,13 @@
1
1
  import { ZScore } from "./z-score.service";
2
2
 
3
- export const OutlierDetectorMinValuesError = "outlier.detector.min.values" as const;
3
+ export const OutlierDetectorError = { NotEnoughValues: "outlier.detector.not.enough.values" } as const;
4
4
 
5
5
  export class OutlierDetector {
6
6
  private readonly zScore: ZScore;
7
7
  private readonly threshold: number;
8
8
 
9
9
  constructor(values: number[], threshold: number) {
10
- if (values.length < 2) throw new Error(OutlierDetectorMinValuesError);
10
+ if (values.length < 2) throw new Error(OutlierDetectorError.NotEnoughValues);
11
11
 
12
12
  this.zScore = new ZScore(values);
13
13
  this.threshold = Math.abs(threshold);
@@ -0,0 +1,21 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const PackageVersionSchemaError = {
4
+ Type: "package.version.schema.error",
5
+ BadChars: "package.version.schema.bad.chars",
6
+ } as const;
7
+
8
+ // v, 1-4 digits, dot, 1-4 digits, dot, 1-4 digits - () for capturing groups
9
+ const PACKAGE_VERSIONS_CHARS_WHITELIST = /^v([0-9]{1,4})\.([0-9]{1,4})\.([0-9]{1,4})$/;
10
+
11
+ export const PackageVersionSchema = z
12
+ .string(PackageVersionSchemaError.Type)
13
+ .regex(PACKAGE_VERSIONS_CHARS_WHITELIST, PackageVersionSchemaError.BadChars)
14
+ .transform((value) => {
15
+ const match = PACKAGE_VERSIONS_CHARS_WHITELIST.exec(value)!;
16
+
17
+ return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
18
+ })
19
+ .brand("PackageVersionSchema");
20
+
21
+ export type PackageVersionSchemaType = z.infer<typeof PackageVersionSchema>;
@@ -1,33 +1,23 @@
1
- import { z } from "zod/v4";
1
+ import { PackageVersionSchema } from "./package-version-schema.vo";
2
2
 
3
- type MajorType = number;
4
- type MinorType = number;
5
- type PatchType = number;
6
-
7
- export const PackageVersionError = { error: "package.version.error" } as const;
8
-
9
- export const PackageVersionValue = z
10
- .string(PackageVersionError)
11
- .regex(/^v(\d+)\.(\d+)\.(\d+)$/, PackageVersionError)
12
- .transform((value) => {
13
- const match = /^v(\d+)\.(\d+)\.(\d+)$/.exec(value)!;
3
+ export class PackageVersion {
4
+ constructor(
5
+ private readonly major: number,
6
+ private readonly minor: number,
7
+ private readonly patch: number,
8
+ ) {}
14
9
 
15
- const major = Number(match[1]);
16
- const minor = Number(match[2]);
17
- const patch = Number(match[3]);
10
+ static fromStringWithV(candidate: string): PackageVersion {
11
+ const version = PackageVersionSchema.parse(candidate);
18
12
 
19
- return { major, minor, patch };
20
- })
21
- .brand("PackageVersionValue");
13
+ return new PackageVersion(version.major, version.minor, version.patch);
14
+ }
22
15
 
23
- export type PackageVersionValueType = z.infer<typeof PackageVersionValue>;
16
+ static fromString(candidate: string): PackageVersion {
17
+ const version = PackageVersionSchema.parse(`v${candidate}`);
24
18
 
25
- export class PackageVersion {
26
- constructor(
27
- readonly major: MajorType,
28
- readonly minor: MinorType,
29
- readonly patch: PatchType,
30
- ) {}
19
+ return new PackageVersion(version.major, version.minor, version.patch);
20
+ }
31
21
 
32
22
  isGreaterThanOrEqual(another: PackageVersion): boolean {
33
23
  if (this.major > another.major) return true;
@@ -46,13 +36,7 @@ export class PackageVersion {
46
36
  return `${this.major}.${this.minor}.${this.patch}`;
47
37
  }
48
38
 
49
- static fromStringWithV(value: string): PackageVersion {
50
- const parsed = PackageVersionValue.parse(value);
51
- return new PackageVersion(parsed.major, parsed.minor, parsed.patch);
52
- }
53
-
54
- static fromString(value: string): PackageVersion {
55
- const parsed = PackageVersionValue.parse(`v${value}`);
56
- return new PackageVersion(parsed.major, parsed.minor, parsed.patch);
39
+ toJSON(): { major: number; minor: number; patch: number } {
40
+ return { major: this.major, minor: this.minor, patch: this.patch };
57
41
  }
58
42
  }
@@ -0,0 +1,11 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const PaginationPageError = { Type: "pagination.page.Type" } as const;
4
+
5
+ export const Page = z.coerce
6
+ .number(PaginationPageError.Type)
7
+ .int(PaginationPageError.Type)
8
+ .transform((value) => (value <= 0 ? 1 : value))
9
+ .default(1);
10
+
11
+ export type PageType = z.infer<typeof Page>;
@@ -0,0 +1,13 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const PaginationSkipError = {
4
+ Type: "pagination.skip.type",
5
+ Invalid: "pagination.skip.invalid",
6
+ } as const;
7
+
8
+ export const Skip = z
9
+ .number(PaginationSkipError.Type)
10
+ .int(PaginationSkipError.Type)
11
+ .gte(0, PaginationSkipError.Invalid);
12
+
13
+ export type SkipType = z.infer<typeof Skip>;
@@ -0,0 +1,13 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const PaginationTakeError = {
4
+ Type: "pagination.take.type",
5
+ Invalid: "pagination.take.invalid",
6
+ } as const;
7
+
8
+ export const Take = z
9
+ .number(PaginationTakeError.Type)
10
+ .int(PaginationTakeError.Type)
11
+ .gte(1, PaginationTakeError.Invalid);
12
+
13
+ export type TakeType = z.infer<typeof Take>;
@@ -1,24 +1,7 @@
1
- import { z } from "zod/v4";
2
-
3
- const PaginationTakeError = { error: "pagination.take.invalid" } as const;
4
- const PaginationSkipError = { error: "pagination.skip.invalid" } as const;
5
- const PaginationPageError = { error: "pagination.page.invalid" } as const;
6
-
7
- const Take = z.number(PaginationTakeError).int(PaginationTakeError).gte(1, PaginationTakeError);
8
-
9
- type TakeType = z.infer<typeof Take>;
10
-
11
- const Skip = z.number(PaginationSkipError).int(PaginationSkipError).gte(0, PaginationSkipError);
12
-
13
- type SkipType = z.infer<typeof Skip>;
14
-
15
- const Page = z.coerce
16
- .number(PaginationPageError)
17
- .int(PaginationPageError)
18
- .transform((value) => (value <= 0 ? 1 : value))
19
- .default(1);
20
-
21
- export type PageType = z.infer<typeof Page>;
1
+ import { Page, type PageType } from "./pagination-page.vo";
2
+ import { Skip, type SkipType } from "./pagination-skip.vo";
3
+ import { Take, type TakeType } from "./pagination-take.vo";
4
+ import { RoundUp } from "./rounding.adapter";
22
5
 
23
6
  export type PaginationType = { values: { take: TakeType; skip: SkipType }; page: PageType };
24
7
  export type PaginationValuesType = Record<string, unknown>;
@@ -57,7 +40,7 @@ export class Pagination {
57
40
  }
58
41
 
59
42
  private static getLastPage(config: PaginationExhaustedConfig): PageType {
60
- return Page.parse(Math.ceil(config.total / config.pagination.values.take));
43
+ return Page.parse(new RoundUp().round(config.total / config.pagination.values.take));
61
44
  }
62
45
 
63
46
  static empty = {
@@ -1,11 +1,11 @@
1
1
  import { RoundToNearest } from "./rounding.adapter";
2
2
  import type { RoundingPort } from "./rounding.port";
3
3
 
4
- export const PercentageInvalidDenominatorError = "percentage.invalid.denominator" as const;
4
+ export const PercentageError = { InvalidDenominator: "percentage.invalid.denominator" } as const;
5
5
 
6
6
  export class Percentage {
7
7
  static of(numerator: number, denominator: number, rounding: RoundingPort = new RoundToNearest()): number {
8
- if (denominator === 0) throw new Error(PercentageInvalidDenominatorError);
8
+ if (denominator === 0) throw new Error(PercentageError.InvalidDenominator);
9
9
  if (numerator === 0) return 0;
10
10
  return rounding.round((numerator / denominator) * 100);
11
11
  }
@@ -3,19 +3,20 @@ import { RoundToDecimal } from "./rounding.adapter";
3
3
  import type { RoundingPort } from "./rounding.port";
4
4
  import { Sum } from "./sum.service";
5
5
 
6
- export const PopulationStandardDeviationMinValuesError = "population.standard.deviation.min.values" as const;
6
+ export const PopulationStandardDeviationError = {
7
+ NotEnoughValues: "population.standard.deviation.not.enough.values",
8
+ } as const;
7
9
 
8
10
  export class PopulationStandardDeviation {
9
11
  static calculate(values: number[], rounding: RoundingPort = new RoundToDecimal(2)): number {
10
- if (values.length < 2) throw new Error(PopulationStandardDeviationMinValuesError);
12
+ if (values.length < 2) throw new Error(PopulationStandardDeviationError.NotEnoughValues);
11
13
 
12
14
  const mean = Mean.calculate(values);
13
15
  const count = values.length;
14
16
 
15
17
  const squaredDifferences = values.map((value) => (value - mean) ** 2);
16
- const sumOfSquaredDifferences = Sum.of(squaredDifferences);
17
18
 
18
- const variance = sumOfSquaredDifferences / count;
19
+ const variance = Sum.of(squaredDifferences) / count;
19
20
 
20
21
  return rounding.round(Math.sqrt(variance));
21
22
  }
@@ -1,16 +1,13 @@
1
1
  import { z } from "zod/v4";
2
2
 
3
- export const QuarterIsoIdError = { error: "quarter-iso-id.invalid" } as const;
3
+ export const QuarterIsoIdError = {
4
+ Type: "quarter.iso.id.type",
5
+ BadChars: "quarter.iso.id.bad.chars",
6
+ } as const;
4
7
 
5
8
  export const QuarterIsoId = z
6
- .string(QuarterIsoIdError)
7
- .regex(/^\d{4}-Q[1-4]$/, QuarterIsoIdError)
8
- .refine((value) => {
9
- const [yearPart, quarterPart] = value.split("-Q");
10
- const year = Number(yearPart);
11
- const quarter = Number(quarterPart);
12
-
13
- return Number.isInteger(year) && Number.isInteger(quarter) && quarter >= 1 && quarter <= 4;
14
- }, QuarterIsoIdError);
9
+ .string(QuarterIsoIdError.Type)
10
+ .regex(/^\d{4}-Q[1-4]$/, QuarterIsoIdError.BadChars)
11
+ .brand("QuarterIsoId");
15
12
 
16
13
  export type QuarterIsoIdType = z.infer<typeof QuarterIsoId>;
package/src/quarter.vo.ts CHANGED
@@ -4,13 +4,6 @@ import { QuarterIsoId, type QuarterIsoIdType } from "./quarter-iso-id.vo";
4
4
  import { Timestamp, type TimestampType } from "./timestamp.vo";
5
5
 
6
6
  export class Quarter extends DateRange {
7
- toIsoId(): QuarterIsoIdType {
8
- const year = getYear(this.getStart());
9
- const quarter = getQuarter(this.getStart());
10
-
11
- return `${year}-Q${quarter}` as QuarterIsoIdType;
12
- }
13
-
14
7
  static fromTimestamp(timestamp: TimestampType): Quarter {
15
8
  const start = Timestamp.parse(startOfQuarter(timestamp).getTime());
16
9
  const end = Timestamp.parse(endOfQuarter(timestamp).getTime());
@@ -24,8 +17,20 @@ export class Quarter extends DateRange {
24
17
 
25
18
  static fromIsoId(isoId: QuarterIsoIdType): Quarter {
26
19
  const [year, quarter] = QuarterIsoId.parse(isoId).split("-Q").map(Number);
27
- const reference = setQuarter(new Date(Date.UTC(year, 0, 1)), quarter);
28
20
 
29
- return Quarter.fromTimestamp(Timestamp.parse(reference.getTime()));
21
+ const reference = setQuarter(Date.UTC(year), quarter).getTime();
22
+
23
+ return Quarter.fromTimestamp(Timestamp.parse(reference));
24
+ }
25
+
26
+ toIsoId(): QuarterIsoIdType {
27
+ const year = getYear(this.getStart());
28
+ const quarter = getQuarter(this.getStart());
29
+
30
+ return QuarterIsoId.parse(`${year}-Q${quarter}`);
31
+ }
32
+
33
+ toString(): string {
34
+ return this.toIsoId();
30
35
  }
31
36
  }
@@ -1,9 +1,8 @@
1
+ import { RoundDown } from "./rounding.adapter";
2
+
1
3
  type RandomGenerateConfigType = { min: number; max: number };
2
4
 
3
- export const RandomMinNotIntegerError = "random.min.not.integer" as const;
4
- export const RandomMaxNotIntegerError = "random.max.not.integer" as const;
5
- export const RandomMinEqualsMaxError = "random.min.equals.max" as const;
6
- export const RandomMinGreaterThanMaxError = "random.min.greater.than.max" as const;
5
+ export const RandomError = { MinMax: "random.min.max" } as const;
7
6
 
8
7
  export class Random {
9
8
  private static readonly DEFAULT_MIN = 0;
@@ -13,11 +12,9 @@ export class Random {
13
12
  const min = config ? config.min : Random.DEFAULT_MIN;
14
13
  const max = config ? config.max : Random.DEFAULT_MAX;
15
14
 
16
- if (!Number.isInteger(min)) throw new Error(RandomMinNotIntegerError);
17
- if (!Number.isInteger(max)) throw new Error(RandomMaxNotIntegerError);
18
- if (min === max) throw new Error(RandomMinEqualsMaxError);
19
- if (min > max) throw new Error(RandomMinGreaterThanMaxError);
15
+ if (min === max) throw new Error(RandomError.MinMax);
16
+ if (min > max) throw new Error(RandomError.MinMax);
20
17
 
21
- return Math.floor(Math.random() * (max - min + 1)) + min;
18
+ return new RoundDown().round(Math.random() * (max - min + 1)) + min;
22
19
  }
23
20
  }
@@ -6,25 +6,26 @@ type RateLimiterResultErrorType = { allowed: false; remaining: Duration };
6
6
  type RateLimiterResultType = RateLimiterResultSuccessType | RateLimiterResultErrorType;
7
7
 
8
8
  export class RateLimiter {
9
- private lastInvocationTimestampMs: TimestampType | null = null;
9
+ private lastInvocation: TimestampType | null = null;
10
10
 
11
11
  constructor(private readonly duration: Duration) {}
12
12
 
13
- verify(currentTimestampMs: TimestampType): RateLimiterResultType {
14
- if (this.lastInvocationTimestampMs == null) {
15
- this.lastInvocationTimestampMs = currentTimestampMs;
13
+ verify(now: TimestampType): RateLimiterResultType {
14
+ if (this.lastInvocation == null) {
15
+ this.lastInvocation = now;
16
16
 
17
17
  return { allowed: true };
18
18
  }
19
19
 
20
- const nextAllowedTimestampMs = this.lastInvocationTimestampMs + this.duration.ms;
20
+ const nextAllowedTimestamp = this.lastInvocation + this.duration.ms;
21
+
22
+ if (nextAllowedTimestamp <= now) {
23
+ this.lastInvocation = now;
21
24
 
22
- if (nextAllowedTimestampMs <= currentTimestampMs) {
23
- this.lastInvocationTimestampMs = currentTimestampMs;
24
25
  return { allowed: true };
25
26
  }
26
27
 
27
- const remainingDelta = nextAllowedTimestampMs - currentTimestampMs;
28
+ const remainingDelta = nextAllowedTimestamp - now;
28
29
 
29
30
  return { allowed: false, remaining: Duration.Ms(remainingDelta) };
30
31
  }
@@ -0,0 +1,10 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const ReorderingItemPositionValueError = { Invalid: "reordering.position.type" } as const;
4
+
5
+ export const ReorderingItemPositionValue = z
6
+ .number(ReorderingItemPositionValueError.Invalid)
7
+ .int(ReorderingItemPositionValueError.Invalid)
8
+ .min(0, ReorderingItemPositionValueError.Invalid);
9
+
10
+ export type ReorderingItemPositionValueType = z.infer<typeof ReorderingItemPositionValue>;
@@ -1,30 +1,24 @@
1
1
  import { z } from "zod/v4";
2
2
  import { DoublyLinkedList, Node } from "./dll.service";
3
+ import {
4
+ ReorderingItemPositionValue,
5
+ type ReorderingItemPositionValueType,
6
+ } from "./reordering-item-position-value.vo";
3
7
 
4
- export const ReorderingPositionError = { error: "reordering.position.invalid" } as const;
5
- export const ReorderingCannotFindItemError = { error: "reordering.item.not_found" } as const;
6
- export const ReorderingCannotFindCurrentError = { error: "reordering.current_item.not_found" } as const;
7
- export const ReorderingCannotFindTargetError = { error: "reordering.target_item.not_found" } as const;
8
- export const ReorderingCorrelationIdError = { error: "reordering.correlation_id.invalid" } as const;
9
- export const ReorderingItemIdError = { error: "reordering.item_id.invalid" } as const;
8
+ export const ReorderingError = {
9
+ CannotFindItem: "reordering.cannot.find.item",
10
+ CannotFindCurrent: "reordering.cannot.find.current",
11
+ CannotFindTarget: "reordering.cannot.find.target",
12
+ };
10
13
 
11
- export const ReorderingItemPositionValue = z
12
- .number(ReorderingPositionError)
13
- .int(ReorderingPositionError)
14
- .min(0, ReorderingPositionError);
15
- export type ReorderingItemPositionValueType = z.infer<typeof ReorderingItemPositionValue>;
14
+ const ReorderingIdError = { Type: "reordering.id.type" } as const;
16
15
 
17
- export const ReorderingCorrelationId = z
18
- .string(ReorderingCorrelationIdError)
19
- .min(1, ReorderingCorrelationIdError);
20
- export type ReorderingCorrelationIdType = z.infer<typeof ReorderingCorrelationId>;
21
-
22
- export const ReorderingItemId = z.string(ReorderingItemIdError);
23
- export type ReorderingItemIdType = z.infer<typeof ReorderingItemId>;
16
+ export const ReorderingId = z.string(ReorderingIdError.Type).min(1, ReorderingIdError.Type);
17
+ export type ReorderingItemIdType = z.infer<typeof ReorderingId>;
24
18
 
25
19
  export const Reordering = z.object({
26
- correlationId: ReorderingCorrelationId,
27
- id: ReorderingItemId,
20
+ correlationId: ReorderingId,
21
+ id: ReorderingId,
28
22
  position: ReorderingItemPositionValue,
29
23
  });
30
24
  export type ReorderingType = z.infer<typeof Reordering>;
@@ -35,10 +29,7 @@ export class ReorderingPosition {
35
29
  readonly value: ReorderingItemPositionValueType;
36
30
 
37
31
  constructor(value: ReorderingItemPositionValueType) {
38
- const parsed = ReorderingItemPositionValue.safeParse(value);
39
- if (!parsed.success) throw new Error(ReorderingPositionError.error);
40
-
41
- this.value = value;
32
+ this.value = ReorderingItemPositionValue.parse(value);
42
33
  }
43
34
 
44
35
  eq(another: ReorderingPosition): boolean {
@@ -108,7 +99,7 @@ export class ReorderingCalculator {
108
99
 
109
100
  delete(id: ReorderingItem["id"]) {
110
101
  const node = this.dll.find((x) => x.data.eq(id));
111
- if (!node) throw new Error(ReorderingCannotFindItemError.error);
102
+ if (!node) throw new Error(ReorderingError.CannotFindItem);
112
103
 
113
104
  this.dll.remove(node);
114
105
  this.recalculate();
@@ -116,10 +107,10 @@ export class ReorderingCalculator {
116
107
 
117
108
  transfer(transfer: ReorderingTransfer): ReturnType<ReorderingCalculator["read"]> {
118
109
  const current = this.dll.find((node) => node.data.eq(transfer.id));
119
- if (!current) throw new Error(ReorderingCannotFindCurrentError.error);
110
+ if (!current) throw new Error(ReorderingError.CannotFindCurrent);
120
111
 
121
112
  const target = this.dll.find((node) => node.data.position.eq(transfer.to));
122
- if (!target) throw new Error(ReorderingCannotFindTargetError.error);
113
+ if (!target) throw new Error(ReorderingError.CannotFindTarget);
123
114
 
124
115
  const direction = transfer.getDirection(current.data.position);
125
116
  if (direction === ReorderingTransferDirection.noop) return this.read();
@@ -147,7 +138,7 @@ export class ReorderingCalculator {
147
138
  let index = 0;
148
139
  for (const node of this.dll) {
149
140
  const id = node.data.id;
150
- const position = new ReorderingPosition(index);
141
+ const position = new ReorderingPosition(ReorderingItemPositionValue.parse(index));
151
142
  node.data = new ReorderingItem(id, position);
152
143
  index += 1;
153
144
  }
@@ -0,0 +1,10 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const RevisionValueError = { Type: "revision.value.type", Invalid: "revision.value.invalid" } as const;
4
+
5
+ export const RevisionValue = z
6
+ .number(RevisionValueError.Type)
7
+ .int(RevisionValueError.Type)
8
+ .min(0, RevisionValueError.Invalid);
9
+
10
+ export type RevisionValueType = z.infer<typeof RevisionValue>;