@haskou/value-objects 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +15 -0
- package/.eslintignore +3 -0
- package/.eslintrc.json +218 -0
- package/.prettierrc.json +13 -0
- package/README.md +156 -0
- package/TECHNICAL_DOCUMENTATION.md +1091 -0
- package/jest.config.ts +30 -0
- package/package.json +49 -0
- package/src/errors/BaseError.ts +20 -0
- package/src/errors/DomainError.ts +12 -0
- package/src/errors/InvalidColorError.ts +7 -0
- package/src/errors/InvalidDayError.ts +7 -0
- package/src/errors/InvalidDayFormatError.ts +7 -0
- package/src/errors/InvalidEmailError.ts +7 -0
- package/src/errors/InvalidHourError.ts +7 -0
- package/src/errors/InvalidIntegerError.ts +7 -0
- package/src/errors/InvalidLatitudeError.ts +7 -0
- package/src/errors/InvalidLongitudeError.ts +7 -0
- package/src/errors/InvalidMinutesError.ts +7 -0
- package/src/errors/InvalidNumberError.ts +7 -0
- package/src/errors/InvalidPositiveNumberError.ts +7 -0
- package/src/errors/InvalidStringLengthError.ts +9 -0
- package/src/errors/InvalidTimestampIntervalError.ts +10 -0
- package/src/errors/NullObjectError.ts +8 -0
- package/src/errors/ValueNotInEnumError.ts +9 -0
- package/src/errors/index.ts +17 -0
- package/src/index.ts +5 -0
- package/src/interfaces/PrimitiveOf.ts +5 -0
- package/src/interfaces/index.ts +1 -0
- package/src/patterns/Assert.ts +15 -0
- package/src/patterns/NullObject.ts +60 -0
- package/src/patterns/ValueObject.ts +40 -0
- package/src/patterns/index.ts +3 -0
- package/src/types/Nullish.ts +1 -0
- package/src/types/Primitive.ts +1 -0
- package/src/types/index.ts +2 -0
- package/src/value-objects/Color.ts +39 -0
- package/src/value-objects/Email.ts +23 -0
- package/src/value-objects/Enum.ts +31 -0
- package/src/value-objects/Integer.ts +22 -0
- package/src/value-objects/NumberValueObject.ts +56 -0
- package/src/value-objects/PositiveNumber.ts +20 -0
- package/src/value-objects/StringValueObject.ts +27 -0
- package/src/value-objects/coordinates/Coordinates.ts +30 -0
- package/src/value-objects/coordinates/Latitude.ts +22 -0
- package/src/value-objects/coordinates/Longitude.ts +25 -0
- package/src/value-objects/coordinates/index.ts +3 -0
- package/src/value-objects/index.ts +9 -0
- package/src/value-objects/time/CalendarDay.ts +91 -0
- package/src/value-objects/time/Day.ts +17 -0
- package/src/value-objects/time/DayOfWeek.ts +60 -0
- package/src/value-objects/time/Duration.ts +142 -0
- package/src/value-objects/time/Hour.ts +105 -0
- package/src/value-objects/time/Month.ts +39 -0
- package/src/value-objects/time/MonthOfYear.ts +52 -0
- package/src/value-objects/time/Timestamp.ts +208 -0
- package/src/value-objects/time/TimestampInterval.ts +122 -0
- package/src/value-objects/time/Year.ts +27 -0
- package/src/value-objects/time/index.ts +10 -0
- package/tests/errors/BaseError.spec.ts +63 -0
- package/tests/errors/DomainError.spec.ts +52 -0
- package/tests/patterns/Assert.spec.ts +29 -0
- package/tests/patterns/NullObject.spec.ts +55 -0
- package/tests/setup.jest.ts +2 -0
- package/tests/value-objects/Color.spec.ts +214 -0
- package/tests/value-objects/Email.spec.ts +145 -0
- package/tests/value-objects/Enum.spec.ts +293 -0
- package/tests/value-objects/Integer.spec.ts +38 -0
- package/tests/value-objects/NumberValueObject.spec.ts +446 -0
- package/tests/value-objects/PositiveNumber.spec.ts +274 -0
- package/tests/value-objects/StringValueObject.spec.ts +135 -0
- package/tests/value-objects/coordinates/Coordinates.spec.ts +90 -0
- package/tests/value-objects/coordinates/Latitude.spec.ts +24 -0
- package/tests/value-objects/coordinates/Longitude.spec.ts +24 -0
- package/tests/value-objects/time/CalendarDay.spec.ts +182 -0
- package/tests/value-objects/time/Day.spec.ts +29 -0
- package/tests/value-objects/time/DayOfWeek.spec.ts +71 -0
- package/tests/value-objects/time/Duration.spec.ts +278 -0
- package/tests/value-objects/time/Hour.spec.ts +197 -0
- package/tests/value-objects/time/MonthOfYear.spec.ts +111 -0
- package/tests/value-objects/time/Timestamp.spec.ts +497 -0
- package/tests/value-objects/time/TimestampInterval.spec.ts +383 -0
- package/tests/value-objects/time/Year.spec.ts +48 -0
- package/tsconfig.jest.json +33 -0
- package/tsconfig.json +42 -0
package/jest.config.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Config } from '@jest/types';
|
|
2
|
+
|
|
3
|
+
// Sync object
|
|
4
|
+
const config: Config.InitialOptions = {
|
|
5
|
+
collectCoverageFrom: ['<rootDir>/src/**/*.ts'],
|
|
6
|
+
coverageDirectory: '<rootDir>/tests/coverage',
|
|
7
|
+
coveragePathIgnorePatterns: ['<rootDir>/node_modules/', 'index.ts'],
|
|
8
|
+
coverageReporters: ['html', 'lcov', 'text'],
|
|
9
|
+
globals: {
|
|
10
|
+
'ts-jest': {
|
|
11
|
+
tsconfig: 'tsconfig.jest.json',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
moduleDirectories: ['node_modules', 'src'],
|
|
15
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
16
|
+
moduleNameMapper: {
|
|
17
|
+
'^@app/(.*)$': '<rootDir>/src/$1',
|
|
18
|
+
},
|
|
19
|
+
preset: 'ts-jest',
|
|
20
|
+
roots: ['<rootDir>/src/', '<rootDir>/tests/'],
|
|
21
|
+
setupFilesAfterEnv: ['<rootDir>/tests/setup.jest.ts'],
|
|
22
|
+
testEnvironment: 'node',
|
|
23
|
+
testPathIgnorePatterns: ['<rootDir>/node_modules/', 'index.ts'],
|
|
24
|
+
transform: {
|
|
25
|
+
'^.+\\.ts': 'ts-jest',
|
|
26
|
+
},
|
|
27
|
+
verbose: true,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default config;
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haskou/value-objects",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/haskou/value-objects.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/haskou/value-objects/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/haskou/value-objects#readme",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"lint": "eslint ./src --ext .ts",
|
|
19
|
+
"lint:fix": "eslint ./src --ext .ts --fix",
|
|
20
|
+
"test": "yarn lint && yarn test:unit",
|
|
21
|
+
"test:unit": "jest --passWithNoTests --runInBand",
|
|
22
|
+
"test:coverage": "NODE_ENV=test jest --collect-coverage",
|
|
23
|
+
"build": "rm -rf dist/ && tsc",
|
|
24
|
+
"publish:patch": "yarn lint && yarn test:coverage && yarn build && yarn version --patch && yarn publish && push",
|
|
25
|
+
"publish:minor": "yarn lint && yarn test:coverage && yarn build && yarn version --minor && yarn publish && push",
|
|
26
|
+
"publish:major": "yarn lint && yarn test:coverage && yarn build && yarn version --major && yarn publish && push"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/jest": "^30.0.0",
|
|
30
|
+
"@types/node": "^24.0.13",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "6.20.0",
|
|
32
|
+
"@typescript-eslint/parser": "6.20.0",
|
|
33
|
+
"eslint": "8.57.0",
|
|
34
|
+
"eslint-config-prettier": "^10.1.5",
|
|
35
|
+
"eslint-plugin-perfectionist": "^4.15.0",
|
|
36
|
+
"eslint-plugin-prettier": "^5.5.1",
|
|
37
|
+
"eslint-plugin-sonarjs": "^3.0.4",
|
|
38
|
+
"eslint-plugin-unused-imports": "^4.1.4",
|
|
39
|
+
"faker": "^6.6.6",
|
|
40
|
+
"jest": "^30.0.4",
|
|
41
|
+
"jest-extended": "^6.0.0",
|
|
42
|
+
"jest-runner-tsc": "^1.6.0",
|
|
43
|
+
"prettier": "^3.6.2",
|
|
44
|
+
"prettier-eslint": "^16.4.2",
|
|
45
|
+
"ts-jest": "^29.4.0",
|
|
46
|
+
"ts-node": "^10.9.2",
|
|
47
|
+
"typescript": "^5.8.3"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class BaseError extends Error {
|
|
2
|
+
private readonly isBaseError = true;
|
|
3
|
+
public static isBaseError(error: unknown): boolean {
|
|
4
|
+
return !!(error as BaseError).isBaseError;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
constructor(message: string, prototype: object = BaseError.prototype) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = this.constructor.name;
|
|
10
|
+
Object.setPrototypeOf(this, prototype);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public toString(): string {
|
|
14
|
+
return `[${this.name}]: ${this.message}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public getStack(): string {
|
|
18
|
+
return this.stack || '';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseError } from './BaseError';
|
|
2
|
+
|
|
3
|
+
export class DomainError extends BaseError {
|
|
4
|
+
protected readonly isDomainError = true;
|
|
5
|
+
public static isDomainError(error: unknown): boolean {
|
|
6
|
+
return !!(error as DomainError).isDomainError;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
constructor(message: string) {
|
|
10
|
+
super(message, new.target.prototype);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Timestamp } from '../value-objects/time/Timestamp';
|
|
2
|
+
import { DomainError } from './DomainError';
|
|
3
|
+
|
|
4
|
+
export class InvalidTimestampIntervalError extends DomainError {
|
|
5
|
+
constructor(start: Timestamp, end: Timestamp) {
|
|
6
|
+
super(
|
|
7
|
+
`Invalid TimestampInterval, start (${start.valueOf()}) must be before the end (${end.valueOf()})`,
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DomainError } from './DomainError';
|
|
2
|
+
|
|
3
|
+
export class ValueNotInEnumError extends DomainError {
|
|
4
|
+
constructor(value: unknown, enumerate: unknown) {
|
|
5
|
+
super(
|
|
6
|
+
`${Object.values(enumerate as object).join(',')} enum does not include value: ${value}`,
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './BaseError';
|
|
2
|
+
export * from './DomainError';
|
|
3
|
+
export * from './InvalidColorError';
|
|
4
|
+
export * from './InvalidDayError';
|
|
5
|
+
export * from './InvalidDayFormatError';
|
|
6
|
+
export * from './InvalidHourError';
|
|
7
|
+
export * from './InvalidIntegerError';
|
|
8
|
+
export * from './InvalidLatitudeError';
|
|
9
|
+
export * from './InvalidLongitudeError';
|
|
10
|
+
export * from './InvalidEmailError';
|
|
11
|
+
export * from './InvalidMinutesError';
|
|
12
|
+
export * from './InvalidNumberError';
|
|
13
|
+
export * from './InvalidPositiveNumberError';
|
|
14
|
+
export * from './InvalidStringLengthError';
|
|
15
|
+
export * from './InvalidTimestampIntervalError';
|
|
16
|
+
export * from './NullObjectError';
|
|
17
|
+
export * from './ValueNotInEnumError';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PrimitiveOf';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseError } from '../errors/BaseError';
|
|
2
|
+
import { DomainError } from '../errors/DomainError';
|
|
3
|
+
|
|
4
|
+
export type Errors = Error | BaseError | DomainError | string;
|
|
5
|
+
|
|
6
|
+
export function assert(
|
|
7
|
+
condition: boolean | unknown | undefined,
|
|
8
|
+
error: string | Errors,
|
|
9
|
+
): void {
|
|
10
|
+
if (condition) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
throw typeof error === 'string' ? new DomainError(error) : error;
|
|
15
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { NullObjectError } from '../errors/NullObjectError';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
type Constructor<T> = abstract new (...args: any[]) => T;
|
|
5
|
+
type NullableObject = Nullable & Record<string, unknown>;
|
|
6
|
+
interface Nullable {
|
|
7
|
+
isNullObject: boolean;
|
|
8
|
+
valueOf(): undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export abstract class NullObject {
|
|
12
|
+
private static setFakeProps<T>(
|
|
13
|
+
nullObject: NullableObject,
|
|
14
|
+
klass: Constructor<T>,
|
|
15
|
+
): void {
|
|
16
|
+
const visited = new Set<string>();
|
|
17
|
+
let proto = klass.prototype;
|
|
18
|
+
|
|
19
|
+
while (proto && proto !== Object.prototype) {
|
|
20
|
+
const props = Object.getOwnPropertyNames(proto);
|
|
21
|
+
|
|
22
|
+
for (const prop of props) {
|
|
23
|
+
if (
|
|
24
|
+
prop === 'constructor' ||
|
|
25
|
+
prop === 'isNullObject' ||
|
|
26
|
+
prop === 'valueOf' ||
|
|
27
|
+
visited.has(prop)
|
|
28
|
+
) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
visited.add(prop);
|
|
33
|
+
// eslint-disable-next-line no-param-reassign
|
|
34
|
+
nullObject[prop] = () => {
|
|
35
|
+
throw new NullObjectError(klass.name);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
proto = Object.getPrototypeOf(proto);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Object.setPrototypeOf(nullObject, NullObject.prototype);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public static new<T>(klass: Constructor<T>): T {
|
|
46
|
+
const nullObject: NullableObject = {
|
|
47
|
+
isNullObject: true,
|
|
48
|
+
valueOf: () => {
|
|
49
|
+
return undefined;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
NullObject.setFakeProps(nullObject, klass);
|
|
53
|
+
|
|
54
|
+
return nullObject as T;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public static isNullObject(nullable: unknown): boolean {
|
|
58
|
+
return !!(nullable as NullableObject)?.isNullObject;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Nullish } from '../types/Nullish';
|
|
2
|
+
import { Primitive } from '../types/Primitive';
|
|
3
|
+
import { NullObject } from './NullObject';
|
|
4
|
+
|
|
5
|
+
export abstract class ValueObject<T extends Primitive = Primitive> {
|
|
6
|
+
protected readonly value!: T;
|
|
7
|
+
|
|
8
|
+
constructor(value: T | Nullish) {
|
|
9
|
+
if (this.isNullish(value)) {
|
|
10
|
+
return NullObject.new(new.target);
|
|
11
|
+
}
|
|
12
|
+
this.value = value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private isNullish(value: T | Nullish): value is null | undefined {
|
|
16
|
+
return value === null || value === undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected clone(value: T): this {
|
|
20
|
+
return new (this.constructor as new (value: Primitive) => this)(
|
|
21
|
+
this.isNullish(value) ? this.value : value,
|
|
22
|
+
) as this;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public isEqual(other: unknown): boolean {
|
|
26
|
+
return this.value === other?.valueOf();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public isNotEqual(other: unknown): boolean {
|
|
30
|
+
return this.isEqual(other) === false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public valueOf(): T {
|
|
34
|
+
return this.value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public toString(): string {
|
|
38
|
+
return this.value!.toString();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Nullish = null | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Primitive = string | number | boolean | bigint | symbol;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { InvalidColorError } from '../errors/InvalidColorError';
|
|
2
|
+
import { assert } from '../patterns/Assert';
|
|
3
|
+
import { NullObject } from '../patterns/NullObject';
|
|
4
|
+
import { StringValueObject } from './StringValueObject';
|
|
5
|
+
|
|
6
|
+
export class Color extends StringValueObject {
|
|
7
|
+
public static readonly RED = new Color('#FF0000');
|
|
8
|
+
public static readonly GREEN = new Color('#00FF00');
|
|
9
|
+
public static readonly BLUE = new Color('#0000FF');
|
|
10
|
+
public static readonly BLACK = new Color('#000000');
|
|
11
|
+
public static readonly WHITE = new Color('#FFFFFF');
|
|
12
|
+
public static readonly YELLOW = new Color('#FFFF00');
|
|
13
|
+
public static readonly CYAN = new Color('#00FFFF');
|
|
14
|
+
public static readonly MAGENTA = new Color('#FF00FF');
|
|
15
|
+
public static readonly ORANGE = new Color('#FFA500');
|
|
16
|
+
public static readonly PURPLE = new Color('#800080');
|
|
17
|
+
public static readonly PINK = new Color('#FFC0CB');
|
|
18
|
+
public static readonly BROWN = new Color('#A52A2A');
|
|
19
|
+
|
|
20
|
+
constructor(value: string | StringValueObject) {
|
|
21
|
+
super(value?.valueOf());
|
|
22
|
+
|
|
23
|
+
if (NullObject.isNullObject(this)) {
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.ensureIsValidColor();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private ensureIsValidColor(): void {
|
|
31
|
+
const hexColorPattern = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
|
|
32
|
+
|
|
33
|
+
assert(hexColorPattern.test(this.value), new InvalidColorError(this.value));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public isEqual(other: Color): boolean {
|
|
37
|
+
return this.value.toLowerCase() === other?.toString().toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { InvalidEmailError } from '../errors/InvalidEmailError';
|
|
2
|
+
import { assert } from '../patterns/Assert';
|
|
3
|
+
import { NullObject } from '../patterns/NullObject';
|
|
4
|
+
import { StringValueObject } from './StringValueObject';
|
|
5
|
+
|
|
6
|
+
export class Email extends StringValueObject {
|
|
7
|
+
constructor(value: string | StringValueObject) {
|
|
8
|
+
super(value?.valueOf());
|
|
9
|
+
|
|
10
|
+
if (NullObject.isNullObject(this)) {
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.ensureIsValidEmail(this.value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private ensureIsValidEmail(value: string): void {
|
|
18
|
+
assert(
|
|
19
|
+
new RegExp(/^[\w+\-.]+@(?:[\w-]+\.)+[a-zA-Z]{2,13}$/).test(value),
|
|
20
|
+
new InvalidEmailError(value),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ValueNotInEnumError } from '../errors/ValueNotInEnumError';
|
|
2
|
+
import { assert } from '../patterns/Assert';
|
|
3
|
+
import { NullObject } from '../patterns/NullObject';
|
|
4
|
+
import { ValueObject } from '../patterns/ValueObject';
|
|
5
|
+
import { Primitive } from '../types/Primitive';
|
|
6
|
+
|
|
7
|
+
export abstract class Enum<
|
|
8
|
+
T extends Primitive = Primitive,
|
|
9
|
+
> extends ValueObject<T> {
|
|
10
|
+
constructor(protected readonly value: T) {
|
|
11
|
+
super(value);
|
|
12
|
+
|
|
13
|
+
if (NullObject.isNullObject(this)) {
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.ensureIsValidValue();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private ensureIsValidValue(): void {
|
|
21
|
+
const isValueInEnum = this.getValues().some((enumValue) =>
|
|
22
|
+
this.isEqual(enumValue),
|
|
23
|
+
);
|
|
24
|
+
assert(
|
|
25
|
+
isValueInEnum,
|
|
26
|
+
new ValueNotInEnumError(this.valueOf(), this.getValues()),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public abstract getValues(): T[];
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { InvalidIntegerError } from '../errors/InvalidIntegerError';
|
|
2
|
+
import { assert } from '../patterns/Assert';
|
|
3
|
+
import { NullObject } from '../patterns/NullObject';
|
|
4
|
+
import { NumberValueObject } from './NumberValueObject';
|
|
5
|
+
|
|
6
|
+
export class Integer extends NumberValueObject {
|
|
7
|
+
constructor(value: number | NumberValueObject) {
|
|
8
|
+
super(value?.valueOf());
|
|
9
|
+
|
|
10
|
+
if (NullObject.isNullObject(this)) {
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
this.ensureIsInteger();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private ensureIsInteger(): void {
|
|
17
|
+
assert(
|
|
18
|
+
this.value.valueOf() % 1 === 0,
|
|
19
|
+
new InvalidIntegerError(this.value.valueOf()),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { InvalidNumberError } from '../errors/InvalidNumberError';
|
|
2
|
+
import { assert } from '../patterns/Assert';
|
|
3
|
+
import { NullObject } from '../patterns/NullObject';
|
|
4
|
+
import { ValueObject } from '../patterns/ValueObject';
|
|
5
|
+
|
|
6
|
+
export class NumberValueObject extends ValueObject<number> {
|
|
7
|
+
constructor(value: number | NumberValueObject) {
|
|
8
|
+
super(value?.valueOf());
|
|
9
|
+
|
|
10
|
+
if (NullObject.isNullObject(this)) {
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
assert(this.isValidNumber(this.value), new InvalidNumberError(this.value));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private isValidNumber(value: number): boolean {
|
|
18
|
+
return !isNaN(value.valueOf());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public isZero(): boolean {
|
|
22
|
+
return this.value === 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public isGreaterThan(other: number | NumberValueObject): boolean {
|
|
26
|
+
return this.value > other.valueOf();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public isGreaterOrEqualThan(other: number | NumberValueObject): boolean {
|
|
30
|
+
return this.value >= other.valueOf();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public isLessThan(other: number | NumberValueObject): boolean {
|
|
34
|
+
return this.value < other.valueOf();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public isLessOrEqualThan(other: number | NumberValueObject): boolean {
|
|
38
|
+
return this.value <= other.valueOf();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public add(other: number | NumberValueObject): NumberValueObject {
|
|
42
|
+
return this.clone(this.value + other.valueOf());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public subtract(other: number | NumberValueObject): NumberValueObject {
|
|
46
|
+
return this.clone(this.value - other.valueOf());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public multiply(other: number | NumberValueObject): NumberValueObject {
|
|
50
|
+
return this.clone(this.value * other.valueOf());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public divide(other: number | NumberValueObject): NumberValueObject {
|
|
54
|
+
return this.clone(this.value / other.valueOf());
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { InvalidPositiveNumberError } from '../errors/InvalidPositiveNumberError';
|
|
2
|
+
import { assert } from '../patterns/Assert';
|
|
3
|
+
import { NullObject } from '../patterns/NullObject';
|
|
4
|
+
import { NumberValueObject } from './NumberValueObject';
|
|
5
|
+
|
|
6
|
+
export class PositiveNumber extends NumberValueObject {
|
|
7
|
+
constructor(value: number | NumberValueObject) {
|
|
8
|
+
super(value?.valueOf());
|
|
9
|
+
|
|
10
|
+
if (NullObject.isNullObject(this)) {
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.ensureIsPositive(this.value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private ensureIsPositive(value: number): void {
|
|
18
|
+
assert(value >= 0, new InvalidPositiveNumberError());
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { InvalidStringLengthError } from '../errors/InvalidStringLengthError';
|
|
2
|
+
import { assert } from '../patterns/Assert';
|
|
3
|
+
import { NullObject } from '../patterns/NullObject';
|
|
4
|
+
import { ValueObject } from '../patterns/ValueObject';
|
|
5
|
+
|
|
6
|
+
export class StringValueObject extends ValueObject<string> {
|
|
7
|
+
constructor(value: string | StringValueObject, maxLength: number = 512) {
|
|
8
|
+
super(value?.valueOf());
|
|
9
|
+
|
|
10
|
+
if (NullObject.isNullObject(this)) {
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
this.ensureDoesNotExceedsMaxLength(maxLength);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private ensureDoesNotExceedsMaxLength(maxLength: number): void {
|
|
18
|
+
assert(
|
|
19
|
+
this.value.length <= maxLength,
|
|
20
|
+
new InvalidStringLengthError(this.value, maxLength),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public isEmpty(): boolean {
|
|
25
|
+
return !this.value;
|
|
26
|
+
}
|
|
27
|
+
}
|