@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.
- package/dist/clock.vo.d.ts +25 -0
- package/dist/clock.vo.js +45 -0
- package/dist/dll.service.d.ts +29 -0
- package/dist/dll.service.js +148 -0
- package/dist/email-mask.service.d.ts +6 -0
- package/dist/email-mask.service.js +14 -0
- package/dist/etags.vo.d.ts +4 -4
- package/dist/etags.vo.js +1 -1
- package/dist/feature-flag.vo.d.ts +11 -0
- package/dist/feature-flag.vo.js +15 -0
- package/dist/filter.vo.d.ts +17 -0
- package/dist/filter.vo.js +21 -0
- package/dist/hour.vo.d.ts +24 -0
- package/dist/hour.vo.js +52 -0
- package/dist/image.vo.d.ts +5 -0
- package/dist/image.vo.js +3 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/leap-year-checker.service.d.ts +4 -0
- package/dist/leap-year-checker.service.js +9 -0
- package/dist/mean.service.d.ts +4 -0
- package/dist/mean.service.js +11 -0
- package/dist/mime-types.vo.d.ts +2 -0
- package/dist/mime-types.vo.js +7 -0
- package/dist/mime.vo.d.ts +1 -1
- package/dist/min-max-scaler.service.d.ts +36 -0
- package/dist/min-max-scaler.service.js +58 -0
- package/dist/minute.vo.d.ts +14 -0
- package/dist/minute.vo.js +34 -0
- package/dist/money.vo.d.ts +24 -0
- package/dist/money.vo.js +64 -0
- package/dist/outlier-detector.service.d.ts +6 -0
- package/dist/outlier-detector.service.js +13 -0
- package/dist/package-version.vo.d.ts +2 -2
- package/dist/package-version.vo.js +3 -3
- package/dist/pagination.service.d.ts +58 -0
- package/dist/pagination.service.js +56 -0
- package/dist/percentage.service.d.ts +4 -0
- package/dist/percentage.service.js +11 -0
- package/dist/population-standard-deviation.service.d.ts +4 -0
- package/dist/population-standard-deviation.service.js +16 -0
- package/dist/random.service.d.ts +8 -0
- package/dist/random.service.js +19 -0
- package/dist/reordering.service.d.ts +61 -0
- package/dist/reordering.service.js +121 -0
- package/dist/revision.vo.d.ts +20 -0
- package/dist/revision.vo.js +45 -0
- package/dist/simple-linear-regression.service.d.ts +18 -0
- package/dist/simple-linear-regression.service.js +50 -0
- package/dist/size.vo.d.ts +2 -2
- package/dist/size.vo.js +2 -2
- package/dist/stepper.service.d.ts +23 -0
- package/dist/stepper.service.js +34 -0
- package/dist/streak-calculator.service.d.ts +14 -0
- package/dist/streak-calculator.service.js +27 -0
- package/dist/sum.service.d.ts +3 -0
- package/dist/sum.service.js +5 -0
- package/dist/thousands-separator.service.d.ts +4 -0
- package/dist/thousands-separator.service.js +6 -0
- package/dist/visually-unambiguous-characters-generator.service.d.ts +4 -0
- package/dist/visually-unambiguous-characters-generator.service.js +35 -0
- package/dist/z-score.service.d.ts +8 -0
- package/dist/z-score.service.js +16 -0
- package/package.json +2 -2
- package/src/api-key.vo.ts +1 -0
- package/src/clock.vo.ts +67 -0
- package/src/dll.service.ts +185 -0
- package/src/email-mask.service.ts +22 -0
- package/src/etags.vo.ts +4 -4
- package/src/feature-flag.vo.ts +19 -0
- package/src/filter.vo.ts +38 -0
- package/src/hour.vo.ts +71 -0
- package/src/image.vo.ts +9 -0
- package/src/index.ts +27 -0
- package/src/leap-year-checker.service.ts +11 -0
- package/src/mean.service.ts +14 -0
- package/src/mime-types.vo.ts +9 -0
- package/src/mime.vo.ts +3 -1
- package/src/min-max-scaler.service.ts +94 -0
- package/src/minute.vo.ts +46 -0
- package/src/money.vo.ts +99 -0
- package/src/outlier-detector.service.ts +20 -0
- package/src/package-version.vo.ts +6 -4
- package/src/pagination.service.ts +111 -0
- package/src/percentage.service.ts +17 -0
- package/src/population-standard-deviation.service.ts +21 -0
- package/src/random.service.ts +29 -0
- package/src/rate-limiter.service.ts +1 -3
- package/src/reordering.service.ts +169 -0
- package/src/revision.vo.ts +61 -0
- package/src/simple-linear-regression.service.ts +74 -0
- package/src/size.vo.ts +3 -3
- package/src/stepper.service.ts +50 -0
- package/src/streak-calculator.service.ts +42 -0
- package/src/sum.service.ts +5 -0
- package/src/thousands-separator.service.ts +7 -0
- package/src/visually-unambiguous-characters-generator.service.ts +42 -0
- package/src/z-score.service.ts +24 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
export const RevisionValue = z.number().int().min(0);
|
|
3
|
+
export class Revision {
|
|
4
|
+
constructor(value) {
|
|
5
|
+
const result = RevisionValue.safeParse(value);
|
|
6
|
+
if (!result.success)
|
|
7
|
+
throw new InvalidRevisionError();
|
|
8
|
+
this.value = result.data;
|
|
9
|
+
}
|
|
10
|
+
matches(another) {
|
|
11
|
+
return this.value === another;
|
|
12
|
+
}
|
|
13
|
+
validate(another) {
|
|
14
|
+
if (!this.matches(another))
|
|
15
|
+
throw new RevisionMismatchError();
|
|
16
|
+
}
|
|
17
|
+
next() {
|
|
18
|
+
return new Revision(this.value + 1);
|
|
19
|
+
}
|
|
20
|
+
static fromETag(etag) {
|
|
21
|
+
if (!etag) {
|
|
22
|
+
throw new InvalidRevisionError();
|
|
23
|
+
}
|
|
24
|
+
return new Revision(etag.revision);
|
|
25
|
+
}
|
|
26
|
+
static fromWeakETag(weakEtag) {
|
|
27
|
+
if (!weakEtag) {
|
|
28
|
+
throw new InvalidRevisionError();
|
|
29
|
+
}
|
|
30
|
+
return new Revision(weakEtag.revision);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
Revision.initial = 0;
|
|
34
|
+
export class RevisionMismatchError extends Error {
|
|
35
|
+
constructor() {
|
|
36
|
+
super();
|
|
37
|
+
Object.setPrototypeOf(this, RevisionMismatchError.prototype);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class InvalidRevisionError extends Error {
|
|
41
|
+
constructor() {
|
|
42
|
+
super();
|
|
43
|
+
Object.setPrototypeOf(this, InvalidRevisionError.prototype);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RoundingStrategy } from "./rounding.service";
|
|
2
|
+
export type SLRPairType = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
};
|
|
6
|
+
export type SLRParamsType = {
|
|
7
|
+
a: number;
|
|
8
|
+
b: number;
|
|
9
|
+
};
|
|
10
|
+
export type SLRPredictionType = number;
|
|
11
|
+
export declare class SimpleLinearRegression {
|
|
12
|
+
private readonly params;
|
|
13
|
+
private readonly rounding;
|
|
14
|
+
constructor(params: SLRParamsType, rounding?: RoundingStrategy);
|
|
15
|
+
static fromPairs(pairs: SLRPairType[], rounding?: RoundingStrategy): SimpleLinearRegression;
|
|
16
|
+
predict(x: SLRPairType["x"], strategy?: RoundingStrategy): SLRPredictionType;
|
|
17
|
+
inspect(): SimpleLinearRegression["params"];
|
|
18
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { RoundToNearest } from "./rounding.service";
|
|
2
|
+
import { Sum } from "./sum.service";
|
|
3
|
+
export class SimpleLinearRegression {
|
|
4
|
+
constructor(params, rounding) {
|
|
5
|
+
this.params = params;
|
|
6
|
+
this.rounding = new RoundToNearest();
|
|
7
|
+
this.rounding = rounding ?? this.rounding;
|
|
8
|
+
}
|
|
9
|
+
static fromPairs(pairs, rounding) {
|
|
10
|
+
const n = pairs.length;
|
|
11
|
+
if (n < 2) {
|
|
12
|
+
throw new Error("At least two pairs needed");
|
|
13
|
+
}
|
|
14
|
+
const x = pairs.map((pair) => pair.x);
|
|
15
|
+
const y = pairs.map((pair) => pair.y);
|
|
16
|
+
const xx = x.map((x) => x ** 2);
|
|
17
|
+
const xy = pairs.map((pair) => pair.x * pair.y);
|
|
18
|
+
const sX = Sum.of(x);
|
|
19
|
+
if (sX >= Number.MAX_SAFE_INTEGER) {
|
|
20
|
+
throw new Error("Sum of x values is too big");
|
|
21
|
+
}
|
|
22
|
+
const sY = Sum.of(y);
|
|
23
|
+
if (sY >= Number.MAX_SAFE_INTEGER) {
|
|
24
|
+
throw new Error("Sum of y values is too big");
|
|
25
|
+
}
|
|
26
|
+
const sSX = Sum.of(xx);
|
|
27
|
+
if (sSX >= Number.MAX_SAFE_INTEGER) {
|
|
28
|
+
throw new Error("Sum of x squared values is too big");
|
|
29
|
+
}
|
|
30
|
+
const sXY = Sum.of(xy);
|
|
31
|
+
if (sXY >= Number.MAX_SAFE_INTEGER) {
|
|
32
|
+
throw new Error("Sum of x times y values is too big");
|
|
33
|
+
}
|
|
34
|
+
const bDenominator = sSX - sX ** 2 / n;
|
|
35
|
+
if (bDenominator === 0) {
|
|
36
|
+
throw new Error("Unable to create the model");
|
|
37
|
+
}
|
|
38
|
+
const b = (sXY - (sX * sY) / n) / bDenominator;
|
|
39
|
+
const a = (sY - b * sX) / n;
|
|
40
|
+
return new SimpleLinearRegression({ a, b }, rounding);
|
|
41
|
+
}
|
|
42
|
+
predict(x, strategy) {
|
|
43
|
+
const rounding = strategy ?? this.rounding;
|
|
44
|
+
const prediction = this.params.b * x + this.params.a;
|
|
45
|
+
return rounding.round(prediction);
|
|
46
|
+
}
|
|
47
|
+
inspect() {
|
|
48
|
+
return this.params;
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/size.vo.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ export declare enum SizeUnit {
|
|
|
5
5
|
MB = "MB",
|
|
6
6
|
GB = "GB"
|
|
7
7
|
}
|
|
8
|
-
declare const
|
|
9
|
-
export type SizeValueType = z.infer<typeof
|
|
8
|
+
declare const SizeValue: z.ZodNumber;
|
|
9
|
+
export type SizeValueType = z.infer<typeof SizeValue>;
|
|
10
10
|
type SizeConfigType = {
|
|
11
11
|
unit: SizeUnit;
|
|
12
12
|
value: SizeValueType;
|
package/dist/size.vo.js
CHANGED
|
@@ -7,11 +7,11 @@ export var SizeUnit;
|
|
|
7
7
|
SizeUnit["MB"] = "MB";
|
|
8
8
|
SizeUnit["GB"] = "GB";
|
|
9
9
|
})(SizeUnit || (SizeUnit = {}));
|
|
10
|
-
const
|
|
10
|
+
const SizeValue = z.number().positive();
|
|
11
11
|
export class Size {
|
|
12
12
|
constructor(config) {
|
|
13
13
|
this.unit = config.unit;
|
|
14
|
-
this.value =
|
|
14
|
+
this.value = SizeValue.parse(config.value);
|
|
15
15
|
this.bytes = this.calculateBytes(config);
|
|
16
16
|
}
|
|
17
17
|
toString() {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
type StepType = number;
|
|
2
|
+
type StepperConfigType = {
|
|
3
|
+
total: StepType;
|
|
4
|
+
};
|
|
5
|
+
export declare class Stepper {
|
|
6
|
+
static readonly DEFAULT_CURRENT = 1;
|
|
7
|
+
private current;
|
|
8
|
+
private readonly total;
|
|
9
|
+
constructor(config: StepperConfigType);
|
|
10
|
+
continue(): Stepper;
|
|
11
|
+
read(): {
|
|
12
|
+
finished: boolean;
|
|
13
|
+
raw: {
|
|
14
|
+
current: number;
|
|
15
|
+
total: number;
|
|
16
|
+
};
|
|
17
|
+
formatted: string;
|
|
18
|
+
};
|
|
19
|
+
reset(): Stepper;
|
|
20
|
+
format(): string;
|
|
21
|
+
isFinished(): boolean;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class Stepper {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.current = Stepper.DEFAULT_CURRENT;
|
|
4
|
+
if (!Number.isInteger(config.total)) {
|
|
5
|
+
throw new Error("Total value is not an integer");
|
|
6
|
+
}
|
|
7
|
+
if (config.total <= Stepper.DEFAULT_CURRENT) {
|
|
8
|
+
throw new Error("Total value should be greater than one");
|
|
9
|
+
}
|
|
10
|
+
this.total = config.total;
|
|
11
|
+
}
|
|
12
|
+
continue() {
|
|
13
|
+
this.current = Math.min(this.current + 1, this.total);
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
read() {
|
|
17
|
+
return {
|
|
18
|
+
finished: this.isFinished(),
|
|
19
|
+
raw: { current: this.current, total: this.total },
|
|
20
|
+
formatted: `${this.current}/${this.total}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
reset() {
|
|
24
|
+
this.current = Stepper.DEFAULT_CURRENT;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
format() {
|
|
28
|
+
return this.read().formatted;
|
|
29
|
+
}
|
|
30
|
+
isFinished() {
|
|
31
|
+
return this.current === this.total;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
Stepper.DEFAULT_CURRENT = 1;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { format } from "date-fns";
|
|
2
|
+
type DateType = string;
|
|
3
|
+
export type StreakType = {
|
|
4
|
+
cutoff: DateType;
|
|
5
|
+
dates: DateType[];
|
|
6
|
+
streak: number;
|
|
7
|
+
};
|
|
8
|
+
export declare class StreakCalculator {
|
|
9
|
+
private readonly cutoff;
|
|
10
|
+
constructor();
|
|
11
|
+
calculate(_dates: DateType[]): StreakType;
|
|
12
|
+
static format(date: Parameters<typeof format>[0]): DateType;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { format, isAfter, isEqual, subDays } from "date-fns";
|
|
2
|
+
export class StreakCalculator {
|
|
3
|
+
constructor() {
|
|
4
|
+
const today = new Date();
|
|
5
|
+
this.cutoff = StreakCalculator.format(today);
|
|
6
|
+
}
|
|
7
|
+
calculate(_dates) {
|
|
8
|
+
const dates = Array.from(new Set(_dates));
|
|
9
|
+
let streak = 0;
|
|
10
|
+
let streakTail = this.cutoff;
|
|
11
|
+
for (let i = 0; i < dates.length; i++) {
|
|
12
|
+
const date = dates[i];
|
|
13
|
+
if (isAfter(date, streakTail))
|
|
14
|
+
continue;
|
|
15
|
+
if (isEqual(streakTail, date) || isAfter(date, streakTail)) {
|
|
16
|
+
streakTail = StreakCalculator.format(subDays(date, 1));
|
|
17
|
+
streak++;
|
|
18
|
+
}
|
|
19
|
+
else
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
return { cutoff: this.cutoff, dates, streak };
|
|
23
|
+
}
|
|
24
|
+
static format(date) {
|
|
25
|
+
return format(date, "yyyy-MM-dd");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Random } from "./random.service";
|
|
2
|
+
export class VisuallyUnambiguousCharactersGenerator {
|
|
3
|
+
static generate(length = 1) {
|
|
4
|
+
return Array.from({ length })
|
|
5
|
+
.map(() => VisuallyUnambiguousCharactersGenerator.chars[Random.generate({
|
|
6
|
+
min: 0,
|
|
7
|
+
max: VisuallyUnambiguousCharactersGenerator.chars.length - 1,
|
|
8
|
+
})])
|
|
9
|
+
.join("");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
VisuallyUnambiguousCharactersGenerator.chars = [
|
|
13
|
+
"a",
|
|
14
|
+
"b",
|
|
15
|
+
"c",
|
|
16
|
+
"d",
|
|
17
|
+
"e",
|
|
18
|
+
"f",
|
|
19
|
+
"h",
|
|
20
|
+
"i",
|
|
21
|
+
"j",
|
|
22
|
+
"k",
|
|
23
|
+
"m",
|
|
24
|
+
"n",
|
|
25
|
+
"o",
|
|
26
|
+
"p",
|
|
27
|
+
"r",
|
|
28
|
+
"s",
|
|
29
|
+
"t",
|
|
30
|
+
"w",
|
|
31
|
+
"x",
|
|
32
|
+
"y",
|
|
33
|
+
"3",
|
|
34
|
+
"4",
|
|
35
|
+
];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RoundingStrategy } from "./rounding.service";
|
|
2
|
+
export declare class ZScore {
|
|
3
|
+
private readonly rounding;
|
|
4
|
+
private readonly mean;
|
|
5
|
+
private readonly standardDeviation;
|
|
6
|
+
constructor(values: number[], rounding?: RoundingStrategy);
|
|
7
|
+
calculate(value: number): number;
|
|
8
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Mean } from "./mean.service";
|
|
2
|
+
import { PopulationStandardDeviation } from "./population-standard-deviation.service";
|
|
3
|
+
import { RoundToDecimal } from "./rounding.service";
|
|
4
|
+
export class ZScore {
|
|
5
|
+
constructor(values, rounding = new RoundToDecimal(2)) {
|
|
6
|
+
this.rounding = rounding;
|
|
7
|
+
if (values.length < 2) {
|
|
8
|
+
throw new Error("At least two values are needed");
|
|
9
|
+
}
|
|
10
|
+
this.mean = Mean.calculate(values);
|
|
11
|
+
this.standardDeviation = PopulationStandardDeviation.calculate(values);
|
|
12
|
+
}
|
|
13
|
+
calculate(value) {
|
|
14
|
+
return this.rounding.round((value - this.mean) / this.standardDeviation);
|
|
15
|
+
}
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bgord/tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bartosz Gordon",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"date-fns": "4.1.0",
|
|
33
|
-
"zod": "3.25.
|
|
33
|
+
"zod": "3.25.55"
|
|
34
34
|
},
|
|
35
35
|
"sideEffects": false
|
|
36
36
|
}
|
package/src/api-key.vo.ts
CHANGED
package/src/clock.vo.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Hour, HourFormatters } from "./hour.vo";
|
|
2
|
+
import { Minute } from "./minute.vo";
|
|
3
|
+
|
|
4
|
+
export type ClockFormatter = (hour: Hour, minute: Minute) => string;
|
|
5
|
+
|
|
6
|
+
enum ClockFormatterEnum {
|
|
7
|
+
TWENTY_FOUR_HOURS = "TWENTY_FOUR_HOURS",
|
|
8
|
+
TWELVE_HOURS = "TWELVE_HOURS",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ClockFormatters: Record<ClockFormatterEnum, ClockFormatter> = {
|
|
12
|
+
TWENTY_FOUR_HOURS: (hour, minute) => `${hour.get().formatted}:${minute.get().formatted}`,
|
|
13
|
+
|
|
14
|
+
TWELVE_HOURS: (hour, minute) =>
|
|
15
|
+
`${hour.get(HourFormatters.TWELVE_HOURS).formatted}:${minute.get().formatted}`,
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export class Clock {
|
|
19
|
+
private readonly formatter: ClockFormatter;
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly hour: Hour,
|
|
23
|
+
private readonly minute: Minute,
|
|
24
|
+
formatter?: ClockFormatter,
|
|
25
|
+
) {
|
|
26
|
+
this.formatter = (formatter as ClockFormatter) ?? ClockFormatters.TWENTY_FOUR_HOURS;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get(formatter?: ClockFormatter) {
|
|
30
|
+
const format = formatter ?? this.formatter;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
raw: { hour: this.hour.get().raw, minute: this.minute.get().raw },
|
|
34
|
+
formatted: format(this.hour, this.minute),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
equals(another: Clock): boolean {
|
|
39
|
+
return (
|
|
40
|
+
this.hour.get().raw === another.get().raw.hour && this.minute.get().raw === another.get().raw.minute
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
isAfter(another: Clock): boolean {
|
|
45
|
+
if (this.hour.get().raw > another.hour.get().raw) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (this.hour.get().raw === another.hour.get().raw && this.minute.get().raw > another.minute.get().raw) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
isBefore(another: Clock): boolean {
|
|
57
|
+
if (this.hour.get().raw < another.hour.get().raw) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (this.hour.get().raw === another.hour.get().raw && this.minute.get().raw < another.minute.get().raw) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export class Node<T> {
|
|
2
|
+
data: T;
|
|
3
|
+
|
|
4
|
+
prev: Node<T> | null = null;
|
|
5
|
+
|
|
6
|
+
next: Node<T> | null = null;
|
|
7
|
+
|
|
8
|
+
constructor(data: Node<T>["data"]) {
|
|
9
|
+
this.data = data;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
forward(n: number): Node<T> | null {
|
|
13
|
+
let currentNode: Node<T> | null = this;
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < n; i++) {
|
|
16
|
+
if (currentNode === null) {
|
|
17
|
+
return currentNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
currentNode = currentNode.next;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return currentNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
backward(n: number): Node<T> | null {
|
|
27
|
+
let currentNode: Node<T> | null = this;
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < n; i++) {
|
|
30
|
+
if (currentNode === null) {
|
|
31
|
+
return currentNode;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
currentNode = currentNode.prev;
|
|
35
|
+
}
|
|
36
|
+
return currentNode;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class DoublyLinkedList<T> {
|
|
41
|
+
static EMPTY_SIZE = 0;
|
|
42
|
+
|
|
43
|
+
private size = DoublyLinkedList.EMPTY_SIZE;
|
|
44
|
+
|
|
45
|
+
private head: Node<T> | null = null;
|
|
46
|
+
|
|
47
|
+
private tail: Node<T> | null = null;
|
|
48
|
+
|
|
49
|
+
getSize(): DoublyLinkedList<T>["size"] {
|
|
50
|
+
return this.size;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
isEmpty(): boolean {
|
|
54
|
+
return this.size === 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getHead(): DoublyLinkedList<T>["head"] {
|
|
58
|
+
return this.head;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getTail(): DoublyLinkedList<T>["tail"] {
|
|
62
|
+
return this.tail;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
append(node: Node<T>): void {
|
|
66
|
+
if (this.head === null || this.tail === null) {
|
|
67
|
+
this.size++;
|
|
68
|
+
|
|
69
|
+
this.head = node;
|
|
70
|
+
this.tail = node;
|
|
71
|
+
} else {
|
|
72
|
+
this.size++;
|
|
73
|
+
|
|
74
|
+
this.tail.next = node;
|
|
75
|
+
node.prev = this.tail;
|
|
76
|
+
|
|
77
|
+
this.tail = node;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
prepend(node: Node<T>): void {
|
|
82
|
+
if (this.head === null || this.tail === null) {
|
|
83
|
+
this.size++;
|
|
84
|
+
|
|
85
|
+
this.head = node;
|
|
86
|
+
this.tail = node;
|
|
87
|
+
} else {
|
|
88
|
+
this.size++;
|
|
89
|
+
|
|
90
|
+
node.next = this.head;
|
|
91
|
+
this.head.prev = node;
|
|
92
|
+
this.head = node;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
clear(): void {
|
|
97
|
+
this.size = 0;
|
|
98
|
+
|
|
99
|
+
this.head = null;
|
|
100
|
+
this.tail = null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
remove(node: Node<T>): void {
|
|
104
|
+
if (node.prev) {
|
|
105
|
+
node.prev.next = node.next;
|
|
106
|
+
} else {
|
|
107
|
+
this.head = node.next;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (node.next) {
|
|
111
|
+
node.next.prev = node.prev;
|
|
112
|
+
} else {
|
|
113
|
+
this.tail = node.prev;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.size--;
|
|
117
|
+
node.prev = null;
|
|
118
|
+
node.next = null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
insertAfter(node: Node<T>, target: Node<T>): void {
|
|
122
|
+
if (target === this.tail) {
|
|
123
|
+
this.append(node);
|
|
124
|
+
} else {
|
|
125
|
+
this.size++;
|
|
126
|
+
|
|
127
|
+
node.prev = target;
|
|
128
|
+
node.next = target.next;
|
|
129
|
+
// biome-ignore lint: lint/style/noNonNullAssertion
|
|
130
|
+
target.next!.prev = node;
|
|
131
|
+
target.next = node;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
insertBefore(node: Node<T>, target: Node<T>): void {
|
|
136
|
+
if (target === this.head) {
|
|
137
|
+
this.prepend(node);
|
|
138
|
+
} else {
|
|
139
|
+
this.size++;
|
|
140
|
+
|
|
141
|
+
node.next = target;
|
|
142
|
+
node.prev = target.prev;
|
|
143
|
+
// biome-ignore lint: lint/style/noNonNullAssertion
|
|
144
|
+
target.prev!.next = node;
|
|
145
|
+
target.prev = node;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
find(callback: (node: Node<T>) => boolean): Node<T> | null {
|
|
150
|
+
return Array.from(this).find(callback) ?? null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
reverse(): void {
|
|
154
|
+
[this.head, this.tail] = [this.tail, this.head];
|
|
155
|
+
|
|
156
|
+
// @ts-ignore
|
|
157
|
+
for (const node of this) {
|
|
158
|
+
[node.prev, node.next] = [node.next, node.prev];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
toArray(): Node<T>[] {
|
|
163
|
+
return Array.from(this);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static fromArray<T>(array: T[]): DoublyLinkedList<T> {
|
|
167
|
+
const dll = new DoublyLinkedList<T>();
|
|
168
|
+
|
|
169
|
+
for (const item of array) {
|
|
170
|
+
dll.append(new Node<T>(item));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return dll;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
*[Symbol.iterator](): IterableIterator<Node<T>> {
|
|
177
|
+
let current: Node<T> | null = this.head;
|
|
178
|
+
|
|
179
|
+
while (current) {
|
|
180
|
+
yield current;
|
|
181
|
+
|
|
182
|
+
current = current.next;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
export const Email = z.email();
|
|
4
|
+
|
|
5
|
+
export type EmailType = z.infer<typeof Email>;
|
|
6
|
+
|
|
7
|
+
export class EmailMask {
|
|
8
|
+
static censor(email: EmailType): EmailType {
|
|
9
|
+
const [beforeAt, afterAt] = email.split("@");
|
|
10
|
+
|
|
11
|
+
const local = beforeAt as string;
|
|
12
|
+
const domain = afterAt as string;
|
|
13
|
+
|
|
14
|
+
if (local.length <= 2) {
|
|
15
|
+
return `${"*".repeat(local.length)}@${domain}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const censoredLocal = `${local.at(0)}${"*".repeat(local.length - 2)}${local.at(-1)}`;
|
|
19
|
+
|
|
20
|
+
return `${censoredLocal}@${domain}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/etags.vo.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const RevisionValue = z.number().int().min(0);
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type RevisionValueType = z.infer<typeof RevisionValue>;
|
|
6
6
|
|
|
7
7
|
type ETagValueType = string;
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ export class ETag {
|
|
|
13
13
|
|
|
14
14
|
readonly value: ETagValueType;
|
|
15
15
|
|
|
16
|
-
private constructor(readonly revision:
|
|
16
|
+
private constructor(readonly revision: RevisionValueType) {
|
|
17
17
|
this.value = revision.toString();
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -36,7 +36,7 @@ export class WeakETag {
|
|
|
36
36
|
|
|
37
37
|
readonly value: WeakETagValueType;
|
|
38
38
|
|
|
39
|
-
private constructor(readonly revision:
|
|
39
|
+
private constructor(readonly revision: RevisionValueType) {
|
|
40
40
|
this.value = `W/${revision.toString()}`;
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
export enum FeatureFlagEnum {
|
|
4
|
+
yes = "yes",
|
|
5
|
+
no = "no",
|
|
6
|
+
}
|
|
7
|
+
export const FeatureFlagValue = z.enum(FeatureFlagEnum);
|
|
8
|
+
|
|
9
|
+
export type FeatureFlagValueType = z.infer<typeof FeatureFlagValue>;
|
|
10
|
+
|
|
11
|
+
export class FeatureFlag {
|
|
12
|
+
static isEnabled(flag: FeatureFlagValueType): boolean {
|
|
13
|
+
return flag === FeatureFlagEnum.yes;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static isDisabled(flag: FeatureFlagValueType): boolean {
|
|
17
|
+
return flag === FeatureFlagEnum.no;
|
|
18
|
+
}
|
|
19
|
+
}
|