@bgord/tools 1.2.19 → 1.2.21

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 (108) hide show
  1. package/dist/duration.service.d.ts +3 -0
  2. package/dist/duration.service.js +7 -0
  3. package/package.json +2 -3
  4. package/src/age-years.vo.ts +0 -16
  5. package/src/age.vo.ts +0 -60
  6. package/src/api-key.vo.ts +0 -16
  7. package/src/basename.vo.ts +0 -33
  8. package/src/clock-format.service.ts +0 -15
  9. package/src/clock.vo.ts +0 -55
  10. package/src/date-calculator.service.ts +0 -18
  11. package/src/date-formatter.service.ts +0 -21
  12. package/src/date-range.vo.ts +0 -36
  13. package/src/day-iso-id.vo.ts +0 -21
  14. package/src/day.vo.ts +0 -59
  15. package/src/directory-path-absolute.vo.ts +0 -36
  16. package/src/directory-path-relative.vo.ts +0 -34
  17. package/src/distance-value.vo.ts +0 -13
  18. package/src/distance.vo.ts +0 -76
  19. package/src/division-factor.vo.ts +0 -12
  20. package/src/dll.service.ts +0 -177
  21. package/src/duration-ms.vo.ts +0 -9
  22. package/src/duration.service.ts +0 -89
  23. package/src/email-mask.service.ts +0 -17
  24. package/src/email.vo.ts +0 -9
  25. package/src/etags.vo.ts +0 -47
  26. package/src/extension.vo.ts +0 -27
  27. package/src/feature-flag-value.vo.ts +0 -12
  28. package/src/feature-flag.vo.ts +0 -11
  29. package/src/file-path-absolute-schema.vo.ts +0 -34
  30. package/src/file-path-relative-schema.vo.ts +0 -34
  31. package/src/file-path.vo.ts +0 -100
  32. package/src/filename-affix.vo.ts +0 -27
  33. package/src/filename-from-string.vo.ts +0 -27
  34. package/src/filename.vo.ts +0 -92
  35. package/src/height-milimiters.vo.ts +0 -16
  36. package/src/height.vo.ts +0 -79
  37. package/src/hour-format.service.ts +0 -22
  38. package/src/hour-schema.vo.ts +0 -14
  39. package/src/hour.vo.ts +0 -64
  40. package/src/iban-mask.service.ts +0 -18
  41. package/src/iban-schema.vo.ts +0 -17
  42. package/src/iban.vo.ts +0 -28
  43. package/src/image.vo.ts +0 -28
  44. package/src/index.ts +0 -105
  45. package/src/integer-non-negative.vo.ts +0 -16
  46. package/src/integer-positive.vo.ts +0 -13
  47. package/src/integer.vo.ts +0 -9
  48. package/src/language.vo.ts +0 -13
  49. package/src/linear-regression.service.ts +0 -73
  50. package/src/mean.service.ts +0 -18
  51. package/src/mime-types.vo.ts +0 -7
  52. package/src/mime-value.vo.ts +0 -14
  53. package/src/mime.vo.ts +0 -50
  54. package/src/min-max-scaler.service.ts +0 -96
  55. package/src/minute-schema.vo.ts +0 -14
  56. package/src/minute.vo.ts +0 -59
  57. package/src/money-amount.vo.ts +0 -13
  58. package/src/money.vo.ts +0 -83
  59. package/src/month-iso-id.vo.ts +0 -24
  60. package/src/month.vo.ts +0 -53
  61. package/src/multiplication-factor.vo.ts +0 -15
  62. package/src/noop.service.ts +0 -3
  63. package/src/notification-template.vo.ts +0 -10
  64. package/src/object-key.vo.ts +0 -36
  65. package/src/outlier-detector.service.ts +0 -21
  66. package/src/package-version-schema.vo.ts +0 -23
  67. package/src/package-version.vo.ts +0 -66
  68. package/src/pagination-page.vo.ts +0 -11
  69. package/src/pagination-skip.vo.ts +0 -10
  70. package/src/pagination-take.vo.ts +0 -10
  71. package/src/pagination.service.ts +0 -73
  72. package/src/percentage.service.ts +0 -15
  73. package/src/population-standard-deviation.service.ts +0 -23
  74. package/src/quarter-iso-id.vo.ts +0 -15
  75. package/src/quarter.vo.ts +0 -41
  76. package/src/random.service.ts +0 -19
  77. package/src/rate-limiter.service.ts +0 -30
  78. package/src/relative-date.vo.ts +0 -21
  79. package/src/reordering-item-position-value.vo.ts +0 -10
  80. package/src/reordering.service.ts +0 -163
  81. package/src/revision-value.vo.ts +0 -10
  82. package/src/revision.vo.ts +0 -44
  83. package/src/rounding-decimal.strategy.ts +0 -25
  84. package/src/rounding-down.strategy.ts +0 -7
  85. package/src/rounding-to-nearest.strategy.ts +0 -7
  86. package/src/rounding-up.strategy.ts +0 -7
  87. package/src/rounding.strategy.ts +0 -3
  88. package/src/size-bytes.vo.ts +0 -13
  89. package/src/size.vo.ts +0 -113
  90. package/src/slug.service.ts +0 -18
  91. package/src/sum.service.ts +0 -19
  92. package/src/thousands-separator.service.ts +0 -10
  93. package/src/time-zone-offset-value.vo.ts +0 -21
  94. package/src/timestamp-value.vo.ts +0 -13
  95. package/src/timestamp.vo.ts +0 -66
  96. package/src/timezone.vo.ts +0 -26
  97. package/src/ts-utils.ts +0 -3
  98. package/src/url-with-slash.vo.ts +0 -12
  99. package/src/url-without-slash.vo.ts +0 -12
  100. package/src/visually-unambiguous-characters-generator.service.ts +0 -39
  101. package/src/week-iso-id.vo.ts +0 -28
  102. package/src/week.vo.ts +0 -57
  103. package/src/weekday.vo.ts +0 -119
  104. package/src/weight-grams.vo.ts +0 -13
  105. package/src/weight.vo.ts +0 -91
  106. package/src/year-iso-id.vo.ts +0 -15
  107. package/src/year.vo.ts +0 -63
  108. package/src/z-score.service.ts +0 -25
@@ -1,177 +0,0 @@
1
- export class Node<T> {
2
- data: T;
3
- prev: Node<T> | null = null;
4
- next: Node<T> | null = null;
5
-
6
- constructor(data: Node<T>["data"]) {
7
- this.data = data;
8
- }
9
-
10
- forward(n: number): Node<T> | null {
11
- let currentNode: Node<T> | null = this;
12
- let steps = n;
13
-
14
- while (steps > 0 && currentNode) {
15
- currentNode = currentNode.next;
16
- steps -= 1;
17
- }
18
-
19
- return currentNode;
20
- }
21
-
22
- backward(n: number): Node<T> | null {
23
- let currentNode: Node<T> | null = this;
24
- let steps = n;
25
-
26
- while (steps > 0 && currentNode) {
27
- currentNode = currentNode.prev;
28
- steps -= 1;
29
- }
30
-
31
- return currentNode;
32
- }
33
- }
34
-
35
- export class DoublyLinkedList<T> {
36
- static EMPTY_SIZE = 0;
37
- private size = DoublyLinkedList.EMPTY_SIZE;
38
- private head: Node<T> | null = null;
39
- private tail: Node<T> | null = null;
40
-
41
- getSize(): DoublyLinkedList<T>["size"] {
42
- return this.size;
43
- }
44
-
45
- isEmpty(): boolean {
46
- return this.size === 0;
47
- }
48
-
49
- getHead(): DoublyLinkedList<T>["head"] {
50
- return this.head;
51
- }
52
-
53
- getTail(): DoublyLinkedList<T>["tail"] {
54
- return this.tail;
55
- }
56
-
57
- append(node: Node<T>): void {
58
- if (this.tail === null) {
59
- this.head = node;
60
- this.tail = node;
61
- } else {
62
- this.tail.next = node;
63
- node.prev = this.tail;
64
- this.tail = node;
65
- }
66
- this.size += 1;
67
- }
68
-
69
- prepend(node: Node<T>): void {
70
- if (this.head === null) {
71
- this.head = node;
72
- this.tail = node;
73
- } else {
74
- node.next = this.head;
75
- this.head.prev = node;
76
- this.head = node;
77
- }
78
- this.size += 1;
79
- }
80
-
81
- clear(): void {
82
- this.size = 0;
83
- this.head = null;
84
- this.tail = null;
85
- }
86
-
87
- remove(node: Node<T>): void {
88
- if (node.prev) {
89
- node.prev.next = node.next;
90
- } else {
91
- this.head = node.next;
92
- }
93
-
94
- if (node.next) {
95
- node.next.prev = node.prev;
96
- } else {
97
- this.tail = node.prev;
98
- }
99
-
100
- this.size -= 1;
101
- node.prev = null;
102
- node.next = null;
103
- }
104
-
105
- insertAfter(node: Node<T>, target: Node<T>): void {
106
- if (target === this.tail) {
107
- this.append(node);
108
- return;
109
- }
110
-
111
- const nextNode = target.next!;
112
- this.size += 1;
113
-
114
- node.prev = target;
115
- node.next = nextNode;
116
-
117
- nextNode.prev = node;
118
-
119
- target.next = node;
120
- }
121
-
122
- insertBefore(node: Node<T>, target: Node<T>): void {
123
- if (target === this.head) {
124
- this.prepend(node);
125
- return;
126
- }
127
-
128
- const prevNode = target.prev!;
129
- this.size += 1;
130
-
131
- node.next = target;
132
- node.prev = prevNode;
133
-
134
- prevNode.next = node;
135
-
136
- target.prev = node;
137
- }
138
-
139
- find(callback: (node: Node<T>) => boolean): Node<T> | null {
140
- let current = this.head;
141
- while (current) {
142
- if (callback(current)) return current;
143
- current = current.next;
144
- }
145
- return null;
146
- }
147
-
148
- reverse(): void {
149
- [this.head, this.tail] = [this.tail, this.head];
150
-
151
- for (const node of this) {
152
- const originalNext = node.next;
153
- node.next = node.prev;
154
- node.prev = originalNext;
155
- }
156
- }
157
-
158
- toArray(): Node<T>[] {
159
- return Array.from(this);
160
- }
161
-
162
- static fromArray<T>(array: T[]): DoublyLinkedList<T> {
163
- const dll = new DoublyLinkedList<T>();
164
- for (const item of array) {
165
- dll.append(new Node<T>(item));
166
- }
167
- return dll;
168
- }
169
-
170
- *[Symbol.iterator](): IterableIterator<Node<T>> {
171
- let current: Node<T> | null = this.head;
172
- while (current) {
173
- yield current;
174
- current = current.next;
175
- }
176
- }
177
- }
@@ -1,9 +0,0 @@
1
- import { z } from "zod/v4";
2
-
3
- export const DurationMsError = { Invalid: "duration.invalid" };
4
-
5
- // Stryker disable all
6
- export const DurationMs = z.number(DurationMsError.Invalid).int(DurationMsError.Invalid).brand("DurationMs");
7
- // Stryker restore all
8
-
9
- export type DurationMsType = z.infer<typeof DurationMs>;
@@ -1,89 +0,0 @@
1
- import { DurationMs, type DurationMsType } from "./duration-ms.vo";
2
- import type { MultiplicationFactorType } from "./multiplication-factor.vo";
3
- import type { RoundingStrategy } from "./rounding.strategy";
4
- import { RoundingDecimalStrategy } from "./rounding-decimal.strategy";
5
- import { RoundingToNearestStrategy } from "./rounding-to-nearest.strategy";
6
-
7
- export class Duration {
8
- private static readonly rounding: RoundingStrategy = new RoundingDecimalStrategy(2);
9
- private readonly internal: DurationMsType;
10
-
11
- static readonly MIN = Duration.Ms(1);
12
-
13
- private static readonly MS_IN_SECOND = 1_000;
14
- private static readonly MS_IN_MINUTE = 60 * Duration.MS_IN_SECOND;
15
- private static readonly MS_IN_HOUR = 60 * Duration.MS_IN_MINUTE;
16
- private static readonly MS_IN_DAY = 24 * Duration.MS_IN_HOUR;
17
- private static readonly MS_IN_WEEK = 7 * Duration.MS_IN_DAY;
18
-
19
- private constructor(candidateMs: number) {
20
- this.internal = DurationMs.parse(candidateMs);
21
- }
22
-
23
- static Weeks(value: number): Duration {
24
- return new Duration(value * Duration.MS_IN_WEEK);
25
- }
26
-
27
- static Days(value: number): Duration {
28
- return new Duration(value * Duration.MS_IN_DAY);
29
- }
30
- static Hours(value: number): Duration {
31
- return new Duration(value * Duration.MS_IN_HOUR);
32
- }
33
- static Minutes(value: number): Duration {
34
- return new Duration(value * Duration.MS_IN_MINUTE);
35
- }
36
- static Seconds(value: number): Duration {
37
- return new Duration(value * Duration.MS_IN_SECOND);
38
- }
39
- static Ms(value: number): Duration {
40
- return new Duration(value);
41
- }
42
-
43
- get weeks(): number {
44
- return Duration.rounding.round(this.internal / Duration.MS_IN_WEEK);
45
- }
46
-
47
- get days(): number {
48
- return Duration.rounding.round(this.internal / Duration.MS_IN_DAY);
49
- }
50
- get hours(): number {
51
- return Duration.rounding.round(this.internal / Duration.MS_IN_HOUR);
52
- }
53
- get minutes(): number {
54
- return Duration.rounding.round(this.internal / Duration.MS_IN_MINUTE);
55
- }
56
- get seconds(): number {
57
- return Duration.rounding.round(this.internal / Duration.MS_IN_SECOND);
58
- }
59
- get ms(): DurationMsType {
60
- return this.internal;
61
- }
62
-
63
- isLongerThan(another: Duration): boolean {
64
- return this.internal > another.internal;
65
- }
66
- isShorterThan(another: Duration): boolean {
67
- return this.internal < another.internal;
68
- }
69
-
70
- equals(other: Duration): boolean {
71
- return this.internal === other.internal;
72
- }
73
- add(another: Duration): Duration {
74
- return Duration.Ms(this.internal + another.internal);
75
- }
76
- subtract(another: Duration): Duration {
77
- return Duration.Ms(this.internal - another.internal);
78
- }
79
-
80
- times(factor: MultiplicationFactorType): Duration {
81
- const rounding = new RoundingToNearestStrategy();
82
-
83
- return Duration.Ms(rounding.round(this.internal * factor));
84
- }
85
-
86
- toAbsolute(): Duration {
87
- return Duration.Ms(Math.abs(this.internal));
88
- }
89
- }
@@ -1,17 +0,0 @@
1
- import type { EmailType } from "./email.vo";
2
-
3
- export class EmailMask {
4
- static censor(email: EmailType): string {
5
- const [local, domain] = email.split("@");
6
-
7
- if (local.length <= 2) return `${"*".repeat(local.length)}@${domain}`;
8
-
9
- const firstCharacter = local.at(0);
10
- const censoredPart = "*".repeat(local.length - 2);
11
- const lastCharacter = local.at(-1);
12
-
13
- const censoredLocal = `${firstCharacter}${censoredPart}${lastCharacter}`;
14
-
15
- return `${censoredLocal}@${domain}`;
16
- }
17
- }
package/src/email.vo.ts DELETED
@@ -1,9 +0,0 @@
1
- import { z } from "zod/v4";
2
-
3
- export const EmailError = { Invalid: "email.invalid" };
4
-
5
- // Stryker disable all
6
- export const Email = z.email(EmailError.Invalid).brand("Email");
7
- // Stryker restore all
8
-
9
- export type EmailType = z.infer<typeof Email>;
package/src/etags.vo.ts DELETED
@@ -1,47 +0,0 @@
1
- import { RevisionValue, type RevisionValueType } from "./revision-value.vo";
2
-
3
- type ETagValueType = string;
4
-
5
- export class ETag {
6
- static HEADER_NAME = "ETag";
7
-
8
- static IF_MATCH_HEADER_NAME = "if-match";
9
-
10
- readonly value: ETagValueType;
11
-
12
- private constructor(readonly revision: RevisionValueType) {
13
- this.value = revision.toString();
14
- }
15
-
16
- static fromHeader(value?: ETagValueType): ETag | null {
17
- const candidate = Number(value);
18
-
19
- if (Number.isNaN(candidate)) return null;
20
- return new ETag(RevisionValue.parse(candidate));
21
- }
22
- }
23
-
24
- export type WeakETagValueType = string;
25
-
26
- export const WeakETagError = { Invalid: "weak.etag.invalid" };
27
-
28
- export class WeakETag {
29
- static HEADER_NAME = "ETag";
30
-
31
- static IF_MATCH_HEADER_NAME = "if-match";
32
-
33
- readonly value: WeakETagValueType;
34
-
35
- private constructor(readonly revision: RevisionValueType) {
36
- this.value = `W/${revision.toString()}`;
37
- }
38
-
39
- static fromHeader(value?: WeakETagValueType): WeakETag | null {
40
- if (!value?.startsWith("W/")) throw new Error(WeakETagError.Invalid);
41
-
42
- const candidate = Number(value.split("W/")[1]);
43
-
44
- if (Number.isNaN(candidate)) return null;
45
- return new WeakETag(RevisionValue.parse(candidate));
46
- }
47
- }
@@ -1,27 +0,0 @@
1
- import { z } from "zod/v4";
2
-
3
- export const ExtensionError = {
4
- Type: "extension.type",
5
- Empty: "extension.empty",
6
- TooLong: "extension.too.long",
7
- BadChars: "extension.bad.chars",
8
- };
9
-
10
- // Lowercase letters and digits allowed
11
- const EXTENSION_WHITELIST = /^[a-z0-9]+$/;
12
-
13
- const LEADING_DOT_FILE = /^\./;
14
-
15
- // Stryker disable all
16
- export const Extension = z
17
- // Stryker restore all
18
- .string(ExtensionError.Type)
19
- .toLowerCase()
20
- .min(2, ExtensionError.Empty)
21
- .max(16, ExtensionError.TooLong)
22
- // Transform ".png" -> "png"
23
- .transform((value) => value.replace(LEADING_DOT_FILE, ""))
24
- .refine((value) => EXTENSION_WHITELIST.test(value), ExtensionError.BadChars)
25
- .brand("Extension");
26
-
27
- export type ExtensionType = z.infer<typeof Extension>;
@@ -1,12 +0,0 @@
1
- import { z } from "zod/v4";
2
-
3
- export const FeatureFlagValueError = { Invalid: "feature.flag.value.invalid" };
4
-
5
- export enum FeatureFlagEnum {
6
- yes = "yes",
7
- no = "no",
8
- }
9
-
10
- export const FeatureFlagValue = z.enum(FeatureFlagEnum, FeatureFlagValueError.Invalid);
11
-
12
- export type FeatureFlagValueType = z.infer<typeof FeatureFlagValue>;
@@ -1,11 +0,0 @@
1
- import { FeatureFlagEnum, type FeatureFlagValueType } from "./feature-flag-value.vo";
2
-
3
- export class FeatureFlag {
4
- static isEnabled(flag: FeatureFlagValueType): boolean {
5
- return flag === FeatureFlagEnum.yes;
6
- }
7
-
8
- static isDisabled(flag: FeatureFlagValueType): boolean {
9
- return flag === FeatureFlagEnum.no;
10
- }
11
- }
@@ -1,34 +0,0 @@
1
- import { z } from "zod/v4";
2
- import { DirectoryPathAbsoluteSchema } from "./directory-path-absolute.vo";
3
- import { Filename } from "./filename.vo";
4
-
5
- export const FilePathAbsoluteSchemaError = {
6
- Type: "file.path.absolute.type",
7
- LeadingSlash: "file.path.absolute.leading.slash",
8
- TrailingSlash: "file.path.absolute.trailing.slash",
9
- BackslashForbidden: "file.path.absolute.backslash.forbidden",
10
- Empty: "file.path.absolute.empty",
11
- };
12
-
13
- // Stryker disable all
14
- export const FilePathAbsoluteSchema = z
15
- // Stryker restore all
16
- .string(FilePathAbsoluteSchemaError.Type)
17
- .min(1, FilePathAbsoluteSchemaError.Empty)
18
- .refine((value) => value.startsWith("/"), FilePathAbsoluteSchemaError.LeadingSlash)
19
- .refine((value) => !value.endsWith("/"), FilePathAbsoluteSchemaError.TrailingSlash)
20
- .refine((value) => !value.includes("\\"), FilePathAbsoluteSchemaError.BackslashForbidden)
21
- .transform((normalized) => {
22
- const index = normalized.lastIndexOf("/");
23
-
24
- const directoryCandidate = index === 0 ? "/" : normalized.slice(0, index);
25
- const filenameCandidate = normalized.slice(index + 1);
26
-
27
- const directory = DirectoryPathAbsoluteSchema.parse(directoryCandidate);
28
- const filename = Filename.fromString(filenameCandidate);
29
-
30
- return { directory, filename };
31
- })
32
- .brand("FilePathAbsoluteSchema");
33
-
34
- export type FilePathAbsoluteType = z.infer<typeof FilePathAbsoluteSchema>;
@@ -1,34 +0,0 @@
1
- import { z } from "zod/v4";
2
- import { DirectoryPathRelativeSchema } from "./directory-path-relative.vo";
3
- import { Filename } from "./filename.vo";
4
-
5
- export const FilePathRelativeSchemaError = {
6
- Type: "file.path.relative.type",
7
- LeadingSlash: "file.path.relative.leading.slash",
8
- BackslashForbidden: "file.path.relative.backslash.forbidden",
9
- RequiresDirectory: "file.path.relative.requires.directory",
10
- Empty: "file.path.relative.empty",
11
- };
12
-
13
- // Stryker disable all
14
- export const FilePathRelativeSchema = z
15
- // Stryker restore all
16
- .string(FilePathRelativeSchemaError.Type)
17
- .min(1, FilePathRelativeSchemaError.Empty)
18
- .refine((value) => !value.startsWith("/"), FilePathRelativeSchemaError.LeadingSlash)
19
- .refine((value) => !value.includes("\\"), FilePathRelativeSchemaError.BackslashForbidden)
20
- .refine((value) => value.includes("/"), FilePathRelativeSchemaError.RequiresDirectory)
21
- .transform((normalized) => {
22
- const lastSlashIndex = normalized.lastIndexOf("/");
23
-
24
- const directoryCandidate = normalized.slice(0, lastSlashIndex);
25
- const filenameCandidate = normalized.slice(lastSlashIndex + 1);
26
-
27
- const directory = DirectoryPathRelativeSchema.parse(directoryCandidate);
28
- const filename = Filename.fromString(filenameCandidate);
29
-
30
- return { directory, filename };
31
- })
32
- .brand("FilePathRelativeSchema");
33
-
34
- export type FilePathRelativeType = z.infer<typeof FilePathRelativeSchema>;
@@ -1,100 +0,0 @@
1
- import { DirectoryPathAbsoluteSchema, type DirectoryPathAbsoluteType } from "./directory-path-absolute.vo";
2
- import { DirectoryPathRelativeSchema, type DirectoryPathRelativeType } from "./directory-path-relative.vo";
3
- import { FilePathAbsoluteSchema } from "./file-path-absolute-schema.vo";
4
- import { FilePathRelativeSchema } from "./file-path-relative-schema.vo";
5
- import type { Filename } from "./filename.vo";
6
-
7
- export class FilePathRelative {
8
- private constructor(
9
- private readonly directory: DirectoryPathRelativeType,
10
- private readonly filename: Filename,
11
- ) {}
12
-
13
- static fromParts(directoryCandidate: string, filename: Filename): FilePathRelative {
14
- const directory = DirectoryPathRelativeSchema.parse(directoryCandidate);
15
-
16
- return new FilePathRelative(directory, filename);
17
- }
18
-
19
- static fromPartsSafe(directory: DirectoryPathRelativeType, filename: Filename): FilePathRelative {
20
- return new FilePathRelative(directory, filename);
21
- }
22
-
23
- static fromString(candidate: string): FilePathRelative {
24
- const schema = FilePathRelativeSchema.parse(candidate);
25
-
26
- return new FilePathRelative(schema.directory, schema.filename);
27
- }
28
-
29
- get(): string {
30
- return `${this.directory}/${this.filename.get()}`;
31
- }
32
-
33
- getDirectory(): DirectoryPathRelativeType {
34
- return this.directory;
35
- }
36
-
37
- getFilename(): Filename {
38
- return this.filename;
39
- }
40
-
41
- withDirectory(newDirectory: DirectoryPathRelativeType): FilePathRelative {
42
- return new FilePathRelative(newDirectory, this.filename);
43
- }
44
-
45
- withFilename(newFilename: Filename): FilePathRelative {
46
- return new FilePathRelative(this.directory, newFilename);
47
- }
48
-
49
- toAbsolute(newDirectory: DirectoryPathAbsoluteType): FilePathAbsolute {
50
- return FilePathAbsolute.fromPartsSafe(newDirectory, this.filename);
51
- }
52
- }
53
-
54
- export class FilePathAbsolute {
55
- private constructor(
56
- private readonly directory: DirectoryPathAbsoluteType,
57
- private readonly filename: Filename,
58
- ) {}
59
-
60
- static fromParts(directoryCandidate: string, filename: Filename): FilePathAbsolute {
61
- const directory = DirectoryPathAbsoluteSchema.parse(directoryCandidate);
62
-
63
- return new FilePathAbsolute(directory, filename);
64
- }
65
-
66
- static fromPartsSafe(directory: DirectoryPathAbsoluteType, filename: Filename): FilePathAbsolute {
67
- return new FilePathAbsolute(directory, filename);
68
- }
69
-
70
- static fromString(candidate: string): FilePathAbsolute {
71
- const schema = FilePathAbsoluteSchema.parse(candidate);
72
-
73
- return new FilePathAbsolute(schema.directory, schema.filename);
74
- }
75
-
76
- get(): string {
77
- if (this.directory === "/") return `/${this.filename.get()}`;
78
- return `${this.directory}/${this.filename.get()}`;
79
- }
80
-
81
- getDirectory(): DirectoryPathAbsoluteType {
82
- return this.directory;
83
- }
84
-
85
- getFilename(): Filename {
86
- return this.filename;
87
- }
88
-
89
- withDirectory(newDirectory: DirectoryPathAbsoluteType): FilePathAbsolute {
90
- return new FilePathAbsolute(newDirectory, this.filename);
91
- }
92
-
93
- withFilename(newFilename: Filename): FilePathAbsolute {
94
- return new FilePathAbsolute(this.directory, newFilename);
95
- }
96
-
97
- toRelative(newDirectory: DirectoryPathRelativeType): FilePathRelative {
98
- return FilePathRelative.fromPartsSafe(newDirectory, this.filename);
99
- }
100
- }
@@ -1,27 +0,0 @@
1
- import { z } from "zod/v4";
2
-
3
- export enum FilenameAffixStrategy {
4
- prefix = "prefix",
5
- suffix = "suffix",
6
- }
7
-
8
- export const FilenameAffixError = {
9
- Type: "affix.type",
10
- Empty: "affix.empty",
11
- TooLong: "affix.too.long",
12
- BadChars: "affix.bad.chars",
13
- };
14
-
15
- // Letters, digits, underscores, and hyphens allowed
16
- const FILENAME_AFFIX_WHITELIST = /^[a-zA-Z0-9_-]+$/;
17
-
18
- // Stryker disable all
19
- export const FilenameAffix = z
20
- // Stryker restore all
21
- .string(FilenameAffixError.Type)
22
- .min(1, FilenameAffixError.Empty)
23
- .max(32, FilenameAffixError.TooLong)
24
- .regex(FILENAME_AFFIX_WHITELIST, FilenameAffixError.BadChars)
25
- .brand("FilenameAffix");
26
-
27
- export type FilenameAffixType = z.infer<typeof FilenameAffix>;
@@ -1,27 +0,0 @@
1
- import { z } from "zod/v4";
2
- import { Basename } from "./basename.vo";
3
- import { Extension } from "./extension.vo";
4
-
5
- export const FilenameFromStringError = {
6
- Type: "filename.from.string.type",
7
- Invalid: "filename.from.string.invalid",
8
- };
9
-
10
- // .+ at least one character, advances to the last dot
11
- // .
12
- // .+ at least one character
13
- const DOT_WITH_SIDES = /^.+\..+$/;
14
-
15
- export const FilenameFromString = z
16
- .string(FilenameFromStringError.Type)
17
- .regex(DOT_WITH_SIDES, FilenameFromStringError.Invalid)
18
- .transform((value) => {
19
- const index = value.lastIndexOf(".");
20
-
21
- const basename = Basename.parse(value.slice(0, index));
22
- const extension = Extension.parse(value.slice(index + 1));
23
-
24
- return { basename, extension };
25
- });
26
-
27
- export type FilenameFromStringType = z.infer<typeof FilenameFromString>;