@bgord/tools 0.5.0 → 0.7.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 (98) hide show
  1. package/dist/clock.vo.d.ts +25 -0
  2. package/dist/clock.vo.js +45 -0
  3. package/dist/dll.service.d.ts +29 -0
  4. package/dist/dll.service.js +148 -0
  5. package/dist/email-mask.service.d.ts +6 -0
  6. package/dist/email-mask.service.js +14 -0
  7. package/dist/etags.vo.d.ts +4 -4
  8. package/dist/etags.vo.js +1 -1
  9. package/dist/feature-flag.vo.d.ts +11 -0
  10. package/dist/feature-flag.vo.js +15 -0
  11. package/dist/filter.vo.d.ts +17 -0
  12. package/dist/filter.vo.js +21 -0
  13. package/dist/hour.vo.d.ts +24 -0
  14. package/dist/hour.vo.js +52 -0
  15. package/dist/image.vo.d.ts +5 -0
  16. package/dist/image.vo.js +3 -0
  17. package/dist/index.d.ts +27 -0
  18. package/dist/index.js +27 -0
  19. package/dist/leap-year-checker.service.d.ts +4 -0
  20. package/dist/leap-year-checker.service.js +9 -0
  21. package/dist/mean.service.d.ts +4 -0
  22. package/dist/mean.service.js +11 -0
  23. package/dist/mime-types.vo.d.ts +2 -0
  24. package/dist/mime-types.vo.js +7 -0
  25. package/dist/mime.vo.d.ts +1 -1
  26. package/dist/min-max-scaler.service.d.ts +36 -0
  27. package/dist/min-max-scaler.service.js +58 -0
  28. package/dist/minute.vo.d.ts +14 -0
  29. package/dist/minute.vo.js +34 -0
  30. package/dist/money.vo.d.ts +24 -0
  31. package/dist/money.vo.js +64 -0
  32. package/dist/outlier-detector.service.d.ts +6 -0
  33. package/dist/outlier-detector.service.js +13 -0
  34. package/dist/package-version.vo.d.ts +2 -2
  35. package/dist/package-version.vo.js +3 -3
  36. package/dist/pagination.service.d.ts +58 -0
  37. package/dist/pagination.service.js +56 -0
  38. package/dist/percentage.service.d.ts +4 -0
  39. package/dist/percentage.service.js +11 -0
  40. package/dist/population-standard-deviation.service.d.ts +4 -0
  41. package/dist/population-standard-deviation.service.js +16 -0
  42. package/dist/random.service.d.ts +8 -0
  43. package/dist/random.service.js +19 -0
  44. package/dist/reordering.service.d.ts +61 -0
  45. package/dist/reordering.service.js +121 -0
  46. package/dist/revision.vo.d.ts +20 -0
  47. package/dist/revision.vo.js +45 -0
  48. package/dist/simple-linear-regression.service.d.ts +18 -0
  49. package/dist/simple-linear-regression.service.js +50 -0
  50. package/dist/size.vo.d.ts +2 -2
  51. package/dist/size.vo.js +2 -2
  52. package/dist/stepper.service.d.ts +23 -0
  53. package/dist/stepper.service.js +34 -0
  54. package/dist/streak-calculator.service.d.ts +14 -0
  55. package/dist/streak-calculator.service.js +27 -0
  56. package/dist/sum.service.d.ts +3 -0
  57. package/dist/sum.service.js +5 -0
  58. package/dist/thousands-separator.service.d.ts +4 -0
  59. package/dist/thousands-separator.service.js +6 -0
  60. package/dist/visually-unambiguous-characters-generator.service.d.ts +4 -0
  61. package/dist/visually-unambiguous-characters-generator.service.js +35 -0
  62. package/dist/z-score.service.d.ts +8 -0
  63. package/dist/z-score.service.js +16 -0
  64. package/package.json +2 -2
  65. package/src/api-key.vo.ts +1 -0
  66. package/src/clock.vo.ts +67 -0
  67. package/src/dll.service.ts +185 -0
  68. package/src/email-mask.service.ts +22 -0
  69. package/src/etags.vo.ts +4 -4
  70. package/src/feature-flag.vo.ts +19 -0
  71. package/src/filter.vo.ts +38 -0
  72. package/src/hour.vo.ts +71 -0
  73. package/src/image.vo.ts +9 -0
  74. package/src/index.ts +27 -0
  75. package/src/leap-year-checker.service.ts +11 -0
  76. package/src/mean.service.ts +14 -0
  77. package/src/mime-types.vo.ts +9 -0
  78. package/src/mime.vo.ts +3 -1
  79. package/src/min-max-scaler.service.ts +94 -0
  80. package/src/minute.vo.ts +46 -0
  81. package/src/money.vo.ts +99 -0
  82. package/src/outlier-detector.service.ts +20 -0
  83. package/src/package-version.vo.ts +6 -4
  84. package/src/pagination.service.ts +111 -0
  85. package/src/percentage.service.ts +17 -0
  86. package/src/population-standard-deviation.service.ts +21 -0
  87. package/src/random.service.ts +29 -0
  88. package/src/rate-limiter.service.ts +1 -3
  89. package/src/reordering.service.ts +169 -0
  90. package/src/revision.vo.ts +61 -0
  91. package/src/simple-linear-regression.service.ts +74 -0
  92. package/src/size.vo.ts +3 -3
  93. package/src/stepper.service.ts +50 -0
  94. package/src/streak-calculator.service.ts +42 -0
  95. package/src/sum.service.ts +5 -0
  96. package/src/thousands-separator.service.ts +7 -0
  97. package/src/visually-unambiguous-characters-generator.service.ts +42 -0
  98. package/src/z-score.service.ts +24 -0
@@ -0,0 +1,38 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export type DefaultFilterSchemaType = z.ZodRawShape;
4
+
5
+ export type FilterValuesType = Record<string, unknown>;
6
+
7
+ export type FilterSchemaType<T extends DefaultFilterSchemaType> = z.ZodObject<T>;
8
+
9
+ export type FilterParseConfigType<T extends DefaultFilterSchemaType> = {
10
+ schema: FilterSchemaType<T>;
11
+ values: FilterValuesType;
12
+ };
13
+
14
+ export class Filter<T extends DefaultFilterSchemaType> {
15
+ constructor(private readonly schema: FilterSchemaType<T>) {}
16
+
17
+ parse(values: FilterValuesType) {
18
+ return Filter._parse({ schema: this.schema, values });
19
+ }
20
+
21
+ default() {
22
+ return this.parse({});
23
+ }
24
+
25
+ static parse<T extends DefaultFilterSchemaType>(config: FilterParseConfigType<T>) {
26
+ return Filter._parse(config);
27
+ }
28
+
29
+ static default<T extends DefaultFilterSchemaType>(config: Omit<FilterParseConfigType<T>, "values">) {
30
+ return Filter._parse({ schema: config.schema, values: {} });
31
+ }
32
+
33
+ private static _parse<T extends DefaultFilterSchemaType>(config: FilterParseConfigType<T>) {
34
+ const result = config.schema.safeParse(config.values);
35
+
36
+ return result.success ? result.data : undefined;
37
+ }
38
+ }
package/src/hour.vo.ts ADDED
@@ -0,0 +1,71 @@
1
+ export type HourFormatter = (value: Hour["value"]) => string;
2
+
3
+ export enum HourFormatterEnum {
4
+ TWENTY_FOUR_HOURS = "TWENTY_FOUR_HOURS",
5
+ TWENTY_FOUR_HOURS_WO_PADDING = "TWENTY_FOUR_HOURS_WO_PADDING",
6
+ AM_PM = "AM_PM",
7
+ TWELVE_HOURS = "TWELVE_HOURS",
8
+ TWELVE_HOURS_WO_PADDING = "TWELVE_HOURS_WO_PADDING",
9
+ }
10
+
11
+ export const HourFormatters: Record<HourFormatterEnum, HourFormatter> = {
12
+ TWENTY_FOUR_HOURS: (value) => value.toString().padStart(2, "0"),
13
+
14
+ TWENTY_FOUR_HOURS_WO_PADDING: (value) => value.toString(),
15
+
16
+ AM_PM: (value) => {
17
+ if (value < 12) return `${value.toString()} a.m.`;
18
+ return `${value.toString()} p.m.`;
19
+ },
20
+
21
+ TWELVE_HOURS: (value) => (value % 12).toString().padStart(2, "0"),
22
+
23
+ TWELVE_HOURS_WO_PADDING: (value) => (value % 12).toString(),
24
+ } as const;
25
+
26
+ export class Hour {
27
+ private readonly value: number;
28
+
29
+ private readonly formatter: HourFormatter;
30
+
31
+ static readonly ZERO = new Hour(0);
32
+
33
+ static readonly MAX = new Hour(23);
34
+
35
+ constructor(candidate: number, formatter?: HourFormatter) {
36
+ if (!Number.isInteger(candidate)) {
37
+ throw new Error("Invalid hour");
38
+ }
39
+ if (candidate < 0) {
40
+ throw new Error("Invalid hour");
41
+ }
42
+ if (candidate >= 24) {
43
+ throw new Error("Invalid hour");
44
+ }
45
+
46
+ this.value = candidate;
47
+ this.formatter = (formatter as HourFormatter) ?? HourFormatters.TWENTY_FOUR_HOURS;
48
+ }
49
+
50
+ get(formatter?: HourFormatter) {
51
+ const format = formatter ?? this.formatter;
52
+
53
+ return { raw: this.value, formatted: format(this.value) };
54
+ }
55
+
56
+ equals(another: Hour): boolean {
57
+ return this.value === another.get().raw;
58
+ }
59
+
60
+ isAfter(another: Hour): boolean {
61
+ return this.value > another.get().raw;
62
+ }
63
+
64
+ isBefore(another: Hour): boolean {
65
+ return this.value < another.get().raw;
66
+ }
67
+
68
+ static list(formatter?: HourFormatter) {
69
+ return Array.from({ length: 24 }).map((_, index) => new Hour(index, formatter));
70
+ }
71
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from "zod/v4";
2
+
3
+ export const Width = z.number().int().positive().max(10000);
4
+
5
+ export type WidthType = z.infer<typeof Width>;
6
+
7
+ export const Height = z.number().int().positive().max(10000);
8
+
9
+ export type HeightType = z.infer<typeof Height>;
package/src/index.ts CHANGED
@@ -1,21 +1,48 @@
1
1
  export * from "./api-key.vo";
2
2
  export * from "./build-version.vo";
3
+ export * from "./clock.vo";
3
4
  export * from "./date-calculator.service";
4
5
  export * from "./date-formatter.service";
5
6
  export * from "./dates-of-the-week.vo";
7
+ export * from "./dll.service";
8
+ export * from "./email-mask.service";
6
9
  export * from "./etags.vo";
10
+ export * from "./feature-flag.vo";
11
+ export * from "./filter.vo";
12
+ export * from "./hour.vo";
13
+ export * from "./image.vo";
7
14
  export * from "./language.vo";
15
+ export * from "./leap-year-checker.service";
16
+ export * from "./mean.service";
17
+ export * from "./mime-types.vo";
8
18
  export * from "./mime.vo";
19
+ export * from "./min-max-scaler.service";
20
+ export * from "./minute.vo";
21
+ export * from "./money.vo";
9
22
  export * from "./noop.service";
23
+ export * from "./outlier-detector.service";
10
24
  export * from "./package-version.vo";
25
+ export * from "./pagination.service";
26
+ export * from "./percentage.service";
27
+ export * from "./population-standard-deviation.service";
28
+ export * from "./random.service";
11
29
  export * from "./rate-limiter.service";
12
30
  export * from "./relative-date.vo";
31
+ export * from "./reordering.service";
32
+ export * from "./revision.vo";
13
33
  export * from "./rounding.service";
34
+ export * from "./simple-linear-regression.service";
14
35
  export * from "./size.vo";
15
36
  export * from "./sleep.service";
37
+ export * from "./stepper.service";
16
38
  export * from "./stopwatch.service";
39
+ export * from "./streak-calculator.service";
40
+ export * from "./sum.service";
41
+ export * from "./thousands-separator.service";
17
42
  export * from "./time-zone-offset-value.vo";
18
43
  export * from "./time.service";
19
44
  export * from "./timestamp.vo";
20
45
  export * from "./timezone.vo";
21
46
  export * from "./ts-utils";
47
+ export * from "./visually-unambiguous-characters-generator.service";
48
+ export * from "./z-score.service";
@@ -0,0 +1,11 @@
1
+ export class LeapYearChecker {
2
+ static isLeapYear(year: number): boolean {
3
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
4
+ }
5
+
6
+ static isCurrentYearLeapYear(): boolean {
7
+ const year = new Date().getFullYear();
8
+
9
+ return LeapYearChecker.isLeapYear(year);
10
+ }
11
+ }
@@ -0,0 +1,14 @@
1
+ import { RoundToDecimal, RoundingStrategy } from "./rounding.service";
2
+ import { Sum } from "./sum.service";
3
+
4
+ export class Mean {
5
+ static calculate(values: number[], rounding: RoundingStrategy = new RoundToDecimal(2)): number {
6
+ if (values.length === 0) {
7
+ throw new Error("Values should not be empty");
8
+ }
9
+
10
+ const mean = Sum.of(values) / values.length;
11
+
12
+ return rounding.round(mean);
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ import type { MimeRawType } from "./mime.vo";
2
+
3
+ export const MIME_TYPES: Record<string, MimeRawType[]> = {
4
+ wildcard: ["*/*"],
5
+ jpeg: ["image/jpeg"],
6
+ png: ["image/png"],
7
+ wav: ["audio/x-wav", "audio/wav"],
8
+ mp4: ["video/mp4"],
9
+ };
package/src/mime.vo.ts CHANGED
@@ -1,5 +1,7 @@
1
- type MimeRawType = string;
1
+ export type MimeRawType = string;
2
+
2
3
  type MimeTypeType = string;
4
+
3
5
  type MimeSubtypeType = string;
4
6
 
5
7
  export class Mime {
@@ -0,0 +1,94 @@
1
+ import { RoundToDecimal, RoundingStrategy } from "./rounding.service";
2
+
3
+ type MinMaxScalerValueType = number;
4
+
5
+ type MinMaxScalerConfigType = {
6
+ min: MinMaxScalerValueType;
7
+ max: MinMaxScalerValueType;
8
+ bound?: {
9
+ lower: MinMaxScalerValueType;
10
+ upper: MinMaxScalerValueType;
11
+ };
12
+ rounding?: RoundingStrategy;
13
+ };
14
+
15
+ export class MinMaxScaler {
16
+ private readonly min: MinMaxScalerValueType;
17
+ private readonly max: MinMaxScalerValueType;
18
+ private readonly lower: MinMaxScalerValueType;
19
+ private readonly upper: MinMaxScalerValueType;
20
+
21
+ private readonly rounding: RoundingStrategy;
22
+
23
+ constructor(config: MinMaxScalerConfigType) {
24
+ const rounding = config.rounding ?? new RoundToDecimal(2);
25
+
26
+ const lower = config.bound?.lower ?? 0;
27
+ const upper = config.bound?.upper ?? 1;
28
+
29
+ if (config.max - config.min < 0) {
30
+ throw new Error("Invalid MinMaxScaler min-max config");
31
+ }
32
+
33
+ if (upper - lower <= 0) {
34
+ throw new Error("Invalid MinMaxScaler bound config");
35
+ }
36
+
37
+ this.rounding = rounding;
38
+
39
+ this.min = config.min;
40
+ this.max = config.max;
41
+ this.lower = lower;
42
+ this.upper = upper;
43
+ }
44
+
45
+ scale(value: MinMaxScalerValueType) {
46
+ const { min, max, lower, upper } = this;
47
+
48
+ if (value < min || value > max) {
49
+ throw new Error("Value out of min/max range");
50
+ }
51
+
52
+ if (min === max)
53
+ return {
54
+ original: value,
55
+ scaled: (lower + upper) / 2,
56
+ isMin: value === min,
57
+ isMax: value === max,
58
+ };
59
+
60
+ const result = ((value - min) / (max - min)) * (upper - lower) + lower;
61
+
62
+ return {
63
+ original: value,
64
+ scaled: this.rounding.round(result),
65
+ isMin: value === min,
66
+ isMax: value === max,
67
+ };
68
+ }
69
+
70
+ descale(scaled: MinMaxScalerValueType) {
71
+ const { min, max, lower, upper } = this;
72
+
73
+ if (scaled < lower || scaled > upper) {
74
+ throw new Error("Scaled value out of bounds");
75
+ }
76
+
77
+ const result = ((scaled - lower) / (upper - lower)) * (max - min) + min;
78
+
79
+ return {
80
+ original: this.rounding.round(result),
81
+ scaled,
82
+ isLowerBound: scaled === lower,
83
+ isUpperBound: scaled === upper,
84
+ };
85
+ }
86
+
87
+ static getMinMax(values: MinMaxScalerValueType[]) {
88
+ if (values.length === 0) {
89
+ throw new Error("An empty array supplied");
90
+ }
91
+
92
+ return { min: Math.min(...values), max: Math.max(...values) };
93
+ }
94
+ }
@@ -0,0 +1,46 @@
1
+ export class Minute {
2
+ private readonly value: number;
3
+
4
+ static readonly ZERO = new Minute(0);
5
+
6
+ static readonly MAX = new Minute(59);
7
+
8
+ constructor(candidate: number) {
9
+ if (!Number.isInteger(candidate)) {
10
+ throw new Error("Invalid minute");
11
+ }
12
+
13
+ if (candidate < 0) {
14
+ throw new Error("Invalid minute");
15
+ }
16
+
17
+ if (candidate >= 60) {
18
+ throw new Error("Invalid minute");
19
+ }
20
+
21
+ this.value = candidate;
22
+ }
23
+
24
+ get() {
25
+ return {
26
+ raw: this.value,
27
+ formatted: this.value.toString().padStart(2, "0"),
28
+ };
29
+ }
30
+
31
+ equals(another: Minute): boolean {
32
+ return this.value === another.get().raw;
33
+ }
34
+
35
+ isAfter(another: Minute): boolean {
36
+ return this.value > another.get().raw;
37
+ }
38
+
39
+ isBefore(another: Minute): boolean {
40
+ return this.value < another.get().raw;
41
+ }
42
+
43
+ static list() {
44
+ return Array.from({ length: 60 }).map((_, index) => new Minute(index));
45
+ }
46
+ }
@@ -0,0 +1,99 @@
1
+ import { z } from "zod/v4";
2
+
3
+ import { RoundToNearest, RoundingStrategy } from "./rounding.service";
4
+
5
+ export const MoneyAmount = z
6
+ .number()
7
+ .int({ message: "money.amount.invalid " })
8
+ .min(0, { message: "money.amount.invalid " });
9
+
10
+ export type MoneyAmountType = z.infer<typeof MoneyAmount>;
11
+
12
+ export const MoneyMultiplicationFactor = z
13
+ .number()
14
+ .min(0, { message: "money.multiplication-factor.invalid" });
15
+
16
+ export type MoneyMultiplicationFactorType = z.infer<typeof MoneyMultiplicationFactor>;
17
+
18
+ export const MoneyDivisionFactor = z
19
+ .number()
20
+ .min(0, { message: "money.division-factor.invalid" })
21
+ .refine((value) => value !== 0, { message: "money.division-factor.invalid" });
22
+
23
+ export type MoneyDivisionFactorType = z.infer<typeof MoneyDivisionFactor>;
24
+
25
+ export class Money {
26
+ private static readonly ZERO = 0;
27
+
28
+ private readonly amount: MoneyAmountType;
29
+
30
+ private readonly rounding: RoundingStrategy;
31
+
32
+ constructor(value: number = Money.ZERO, rounding?: RoundingStrategy) {
33
+ this.amount = MoneyAmount.parse(value);
34
+ this.rounding = rounding ?? new RoundToNearest();
35
+ }
36
+
37
+ getAmount(): MoneyAmountType {
38
+ return this.amount;
39
+ }
40
+
41
+ add(money: Money) {
42
+ const result = this.rounding.round(this.amount + money.getAmount());
43
+
44
+ return new Money(MoneyAmount.parse(result), this.rounding);
45
+ }
46
+
47
+ multiply(factor: MoneyMultiplicationFactorType) {
48
+ const result = this.rounding.round(this.amount * factor);
49
+
50
+ return new Money(MoneyAmount.parse(result), this.rounding);
51
+ }
52
+
53
+ subtract(money: Money) {
54
+ const result = this.rounding.round(this.amount - money.getAmount());
55
+
56
+ if (result < Money.ZERO) {
57
+ throw new Error("Less than zero");
58
+ }
59
+
60
+ return new Money(MoneyAmount.parse(result), this.rounding);
61
+ }
62
+
63
+ divide(factor: MoneyDivisionFactorType) {
64
+ if (factor === 0) {
65
+ throw new Error("Cannot divide by zero");
66
+ }
67
+
68
+ const result = this.rounding.round(this.amount / factor);
69
+
70
+ return new Money(MoneyAmount.parse(result), this.rounding);
71
+ }
72
+
73
+ equals(another: Money): boolean {
74
+ return this.amount === another.getAmount();
75
+ }
76
+
77
+ isGreaterThan(another: Money): boolean {
78
+ return this.amount > another.getAmount();
79
+ }
80
+
81
+ isLessThan(another: Money): boolean {
82
+ return this.amount < another.getAmount();
83
+ }
84
+
85
+ isZero(): boolean {
86
+ return this.amount === Money.ZERO;
87
+ }
88
+
89
+ format(): string {
90
+ const result = this.amount / 100;
91
+
92
+ const whole = Math.floor(result);
93
+
94
+ const fraction = this.amount % 100;
95
+ const fractionFormatted = fraction.toString().padStart(2, "0");
96
+
97
+ return `${whole}.${fractionFormatted}`;
98
+ }
99
+ }
@@ -0,0 +1,20 @@
1
+ import { ZScore } from "./z-score.service";
2
+
3
+ export class OutlierDetector {
4
+ private readonly zScore: ZScore;
5
+
6
+ private readonly threshold: number;
7
+
8
+ constructor(values: number[], threshold: number) {
9
+ if (values.length < 2) {
10
+ throw new Error("At least two values are needed");
11
+ }
12
+
13
+ this.zScore = new ZScore(values);
14
+ this.threshold = Math.abs(threshold);
15
+ }
16
+
17
+ check(value: number): boolean {
18
+ return this.zScore.calculate(value) <= this.threshold;
19
+ }
20
+ }
@@ -1,10 +1,12 @@
1
1
  import { z } from "zod/v4";
2
2
 
3
3
  type MajorType = number;
4
+
4
5
  type MinorType = number;
6
+
5
7
  type PatchType = number;
6
8
 
7
- export const PackageVersionSchema = z
9
+ export const PackageVersionValue = z
8
10
  .string()
9
11
  .min(1)
10
12
  .refine(
@@ -56,7 +58,7 @@ export const PackageVersionSchema = z
56
58
  patch: Number(patch),
57
59
  };
58
60
  });
59
- export type PackageVersionSchemaType = z.infer<typeof PackageVersionSchema>;
61
+ export type PackageVersionValueType = z.infer<typeof PackageVersionValue>;
60
62
 
61
63
  export class PackageVersion {
62
64
  constructor(
@@ -79,13 +81,13 @@ export class PackageVersion {
79
81
  }
80
82
 
81
83
  static fromStringWithV(value: string) {
82
- const version = PackageVersionSchema.parse(value);
84
+ const version = PackageVersionValue.parse(value);
83
85
 
84
86
  return new PackageVersion(version.major, version.minor, version.patch);
85
87
  }
86
88
 
87
89
  static fromString(value: string) {
88
- const version = PackageVersionSchema.parse(`v${value}`);
90
+ const version = PackageVersionValue.parse(`v${value}`);
89
91
 
90
92
  return new PackageVersion(version.major, version.minor, version.patch);
91
93
  }
@@ -0,0 +1,111 @@
1
+ import { z } from "zod/v4";
2
+
3
+ const Take = z.number().int().positive();
4
+
5
+ type TakeType = z.infer<typeof Take>;
6
+
7
+ const Skip = z.number().int().positive();
8
+
9
+ type SkipType = z.infer<typeof Skip>;
10
+
11
+ const Page = z.coerce
12
+ .number()
13
+ .int()
14
+ .transform((value) => (value <= 0 ? 1 : value))
15
+ .default(1);
16
+
17
+ export type PageType = z.infer<typeof Page>;
18
+
19
+ export type PaginationType = {
20
+ values: { take: TakeType; skip: SkipType };
21
+ page: PageType;
22
+ };
23
+
24
+ export type PaginationValuesType = Record<string, unknown>;
25
+
26
+ export type TotalType = number;
27
+
28
+ export type ExhaustedType = boolean;
29
+
30
+ export type PaginationExhaustedConfig = {
31
+ total: TotalType;
32
+ pagination: PaginationType;
33
+ };
34
+
35
+ export type PaginationPrepareConfigType<T> = {
36
+ total: TotalType;
37
+ pagination: PaginationType;
38
+ result: T[];
39
+ };
40
+
41
+ export class Pagination {
42
+ static parse(values: PaginationValuesType, _take: TakeType): PaginationType {
43
+ const page = Page.parse(values.page);
44
+ const take = Take.parse(_take);
45
+
46
+ const skip = (page - 1) * take;
47
+
48
+ return { values: { take, skip }, page };
49
+ }
50
+
51
+ static prepare<T>(config: PaginationPrepareConfigType<T>): Paged<T> {
52
+ const exhausted = Pagination.isExhausted(config);
53
+
54
+ const currentPage = config.pagination.page;
55
+ const lastPage = Pagination.getLastPage(config);
56
+
57
+ const previousPage = currentPage > 1 ? currentPage - 1 : undefined;
58
+ const nextPage = currentPage < lastPage ? currentPage + 1 : undefined;
59
+
60
+ return {
61
+ result: config.result,
62
+ meta: {
63
+ exhausted,
64
+ currentPage,
65
+ previousPage,
66
+ nextPage,
67
+ lastPage,
68
+ total: config.total,
69
+ },
70
+ };
71
+ }
72
+
73
+ static isExhausted(config: PaginationExhaustedConfig): ExhaustedType {
74
+ const lastPage = Pagination.getLastPage(config);
75
+ const currentPage = config.pagination.page;
76
+
77
+ return lastPage <= currentPage;
78
+ }
79
+
80
+ private static getLastPage(config: PaginationExhaustedConfig): PageType {
81
+ return Math.ceil(config.total / config.pagination.values.take);
82
+ }
83
+
84
+ static empty = {
85
+ result: [],
86
+ meta: {
87
+ exhausted: true,
88
+ currentPage: 1,
89
+ previousPage: undefined,
90
+ nextPage: undefined,
91
+ lastPage: 1,
92
+ total: 0,
93
+ },
94
+ };
95
+
96
+ static getFirstPage({ take }: { take: TakeType }): PaginationType {
97
+ return { values: { take, skip: 0 }, page: 1 };
98
+ }
99
+ }
100
+
101
+ export type Paged<T> = {
102
+ result: T[];
103
+ meta: {
104
+ exhausted: ExhaustedType;
105
+ currentPage: PageType;
106
+ previousPage: PageType | undefined;
107
+ nextPage: PageType | undefined;
108
+ lastPage: PageType;
109
+ total: TotalType;
110
+ };
111
+ };
@@ -0,0 +1,17 @@
1
+ import { RoundToNearest, RoundingStrategy } from "./rounding.service";
2
+
3
+ export class Percentage {
4
+ static of(
5
+ numerator: number,
6
+ denominator: number,
7
+ rounding: RoundingStrategy = new RoundToNearest(),
8
+ ): number {
9
+ if (denominator === 0) {
10
+ throw new Error("Invalid denominator");
11
+ }
12
+
13
+ if (numerator === 0) return 0;
14
+
15
+ return rounding.round((numerator / denominator) * 100);
16
+ }
17
+ }
@@ -0,0 +1,21 @@
1
+ import { Mean } from "./mean.service";
2
+ import { RoundToDecimal, RoundingStrategy } from "./rounding.service";
3
+ import { Sum } from "./sum.service";
4
+
5
+ export class PopulationStandardDeviation {
6
+ static calculate(values: number[], rounding: RoundingStrategy = new RoundToDecimal(2)): number {
7
+ if (values.length < 2) {
8
+ throw new Error("At least two values are needed");
9
+ }
10
+
11
+ const mean = Mean.calculate(values);
12
+ const n = values.length;
13
+
14
+ const squaredDifferences = values.map((value) => (value - mean) ** 2);
15
+ const sumOfSquaredDifferences = Sum.of(squaredDifferences);
16
+
17
+ const variance = sumOfSquaredDifferences / n;
18
+
19
+ return rounding.round(Math.sqrt(variance));
20
+ }
21
+ }