Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/init adapter type #159

Merged
merged 3 commits into from
Jun 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ All notable changes to this project will be documented in this file.

---

### [1.23.2] - 2024-06-02

#### Update

- Adapter: added new type for adapter - Adapter
- Init: added new method type to entity, value object and aggregates - init

---


### [1.23.1] - 2024-05-02

Expand Down
129 changes: 26 additions & 103 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,10 @@ export class Name extends ValueObject<NameProps>{
super(props);
}

public static init(value: string): Name {
return new Name(value);
}

public static create(value: string): Result<Name> {
return Result.Ok(new Name({ value }));
}
Expand Down Expand Up @@ -1179,26 +1183,25 @@ A validator instance is available in the "Value Object" domain class.

import { Result, Ok, Fail, ValueObject } from "rich-domain";

export interface NameProps {
value: string;
}

export class Name extends ValueObject<NameProps>{
private constructor(props: NameProps) {
export class Name extends ValueObject<string>{
private constructor(props: string) {
super(props);
}

public static isValidProps({ value }: NameProps): boolean {
const { string } = this.validator;
return string(value).hasLengthBetween(3, 30);
public static isValid(value: string): boolean {
const { string: Check } = this.validator;
return Check(value).hasLengthBetween(3, 30);
}

public static create(value: string): Result<Name> {
const message = 'name must have length min 3 and max 30 char';

if (!this.isValidProps({ value })) return Fail(message);
public static init(value: string): Name {
const isValid = this.isValid(value);
if(!isValid) throw new Error('invalid name');
return new Name(value);
}

return Ok(new Name({ value }));
public static create(value: string): Result<Name> {
if (!this.isValid(value)) return Fail('invalid name');
return Ok(new Name(value));
}
}

Expand All @@ -1220,107 +1223,27 @@ console.log(result.isFail());

console.log(result.error());

> "name must have length min 3 and max 30 char"
> "invalid name"

console.log(result.value());

> null

```

#### Validation before set

The `isValidProps` Method validates properties when creating a new instance, but which method validates before modifying a value?
For this there is the method `validation`

The validation method takes two arguments, the first the `key` of props and the second the `value`.
So when calling the `set` or `change` function, this method will be called automatically to validate the value, if it doesn't pass the validation, the value is not changed.

> There must be a validation for each "props" key

```ts

validation<Key extends keyof Props>(value: Props[Key], key: Key): boolean {

const { number } = this.validator;

const options: IPropsValidation<Props> = {
value: (value: number) => number.isBetween(0, 130),
}

return options[key](value);
};

```

In case your value object has only one attribute you can simply use the already created static validation method.<br>
Let's see a complete example as below

```ts

import { Result, Ok, Fail, ValueObject } from "rich-domain";

export interface NameProps {
value: string;
}

export class Name extends ValueObject<NameProps>{
private constructor(props: NameProps) {
super(props);
}

validation(value: string): boolean {
return Name.isValidProps({ value });
}

public static isValidProps({ value }: NameProps): boolean {
const { string } = this.validator;
return string(value).hasLengthBetween(3, 30);
}

public static create(value: string): Result<Name> {
const message = 'name must have length min 3 and max 30 char';
const isValidProps = this.isValidProps({ value });
if (!isValidProps) return Fail(message);

return Ok(new Name({ value }));
}
}

export default Name;

```

Let's test the instance and the validation method.<br>
Value is not modified if it does not pass validation.
Alternatively you can init a new instance and receive a throw if provide invalid value.

```ts

const result = Name.create('Jane');

console.log(result.isOk());

> true

const name = result.value();
const name = Name.init('Jane');

console.log(name.get('value'));

> "Jane"

const empty = '';

name.set('value').to(empty);

console.log(name.get('value'));

> "Jane"

name.set('value').to("Jack");

console.log(name.get('value'));
const other = Name.init('');

> "Jack"
> "throw error: invalid name"

```

Expand Down Expand Up @@ -1867,15 +1790,15 @@ How to adapt the data from persistence to domain or from domain to persistence.
```ts

// from domain to data layer
class MyAdapterToInfra implements IAdapter<DomainUser, DataUser>{
build(target: DomainUser): Result<DataUser> {
class MyAdapterToInfra implements Adapter<DomainUser, DataUser>{
adaptOne(target: DomainUser): DataUser {
// ...
}
}

// from data layer to domain
class MyAdapterToDomain implements IAdapter<DataUser, DomainUser>{
build(target: DataUser): Result<DomainUser> {
class MyAdapterToDomain implements Adapter<DataUser, DomainUser>{
adaptOne(target: DataUser): DomainUser {
// ...
}
}
Expand Down
32 changes: 28 additions & 4 deletions lib/core/entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AutoMapperSerializer, EntityMapperPayload, EntityProps, IAdapter, IEntity, IResult, ISettings, UID } from "../types";
import { Adapter, AutoMapperSerializer, EntityMapperPayload, EntityProps, IAdapter, IEntity, IResult, ISettings, UID } from "../types";
import { ReadonlyDeep } from "../types-util";
import { deepFreeze } from "../utils/deep-freeze.util";
import AutoMapper from "./auto-mapper";
Expand Down Expand Up @@ -50,12 +50,16 @@ export class Entity<Props extends EntityProps> extends GettersAndSetters<Props>
* @description Get value as object from entity.
* @returns object with properties.
*/
toObject<T>(adapter?: IAdapter<this, T>)
toObject<T>(adapter?: Adapter<this, T> | IAdapter<this, T>)
: T extends {}
? T & EntityMapperPayload
: ReadonlyDeep<AutoMapperSerializer<Props> & EntityMapperPayload> {
if (adapter && typeof adapter?.build === 'function') return adapter.build(this).value() as any;

if(adapter && typeof (adapter as Adapter<this, T>)?.adaptOne === 'function') {
return (adapter as Adapter<this, T>).adaptOne(this) as any;
}
if (adapter && typeof (adapter as IAdapter<this, T>)?.build === 'function') {
return (adapter as IAdapter<this, T>).build(this).value() as any;
}
const serializedObject = this.autoMapper.entityToObj(this) as ReadonlyDeep<AutoMapperSerializer<Props>>;
const frozenObject = deepFreeze<any>(serializedObject);
return frozenObject
Expand Down Expand Up @@ -104,6 +108,15 @@ export class Entity<Props extends EntityProps> extends GettersAndSetters<Props>
return Reflect.construct(instance!.constructor, args);
}

/**
* @description Method to validate value.
* @param value to validate
* @returns boolean
*/
public static isValid(value: any): boolean {
return this.isValidProps(value);
};

/**
* @description Method to validate props. This method is used to validate props on create a instance.
* @param props to validate
Expand All @@ -113,6 +126,17 @@ export class Entity<Props extends EntityProps> extends GettersAndSetters<Props>
return !this.validator.isUndefined(props) && !this.validator.isNull(props);
};

/**
* @description method to create a new instance
* @param value as props
* @returns instance of Entity or throw an error.
*/
public static init(props: any): any {
throw new Error('method not implemented: init', {
cause: props
});
};

public static create(props: any): IResult<any, any, any>;
/**
*
Expand Down
31 changes: 28 additions & 3 deletions lib/core/value-object.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AutoMapperSerializer, IAdapter, IResult, IValueObject, IVoSettings, UID } from "../types";
import { Adapter, AutoMapperSerializer, IAdapter, IResult, IValueObject, IVoSettings, UID } from "../types";
import { ReadonlyDeep } from "../types-util";
import { deepFreeze } from "../utils/deep-freeze.util";
import AutoMapper from "./auto-mapper";
Expand Down Expand Up @@ -85,16 +85,30 @@ export class ValueObject<Props> extends BaseGettersAndSetters<Props> implements
* @description Get value from value object.
* @returns value as string, number or any type defined.
*/
toObject<T>(adapter?: IAdapter<this, T>)
toObject<T>(adapter?: Adapter<this, T> | IAdapter<this, T>)
: T extends {}
? T
: ReadonlyDeep<AutoMapperSerializer<Props>> {
if (adapter && typeof adapter?.build === 'function') return adapter.build(this).value() as any
if (adapter && typeof (adapter as Adapter<this, T>)?.adaptOne === 'function') {
return (adapter as Adapter<this, T>).adaptOne(this) as any;
}
if (adapter && typeof (adapter as IAdapter<this, T>)?.build === 'function') {
return (adapter as IAdapter<this, T>).build(this).value() as any;
}
const serializedObject = this.autoMapper.valueObjectToObj(this) as ReadonlyDeep<AutoMapperSerializer<Props>>;
const frozenObject = deepFreeze<any>(serializedObject);
return frozenObject;
}

/**
* @description Method to validate value.
* @param value to validate
* @returns boolean
*/
public static isValid(value: any): boolean {
return this.isValidProps(value);
};

/**
* @description Method to validate prop value.
* @param props to validate
Expand All @@ -103,6 +117,17 @@ export class ValueObject<Props> extends BaseGettersAndSetters<Props> implements
return !this.validator.isUndefined(props) && !this.validator.isNull(props);
};

/**
* @description method to create a new instance
* @param value as props
* @returns instance of Value Object or throw an error.
*/
public static init(value: any): any {
throw new Error('method not implemented: init', {
cause: value
});
};

public static create(props: any): IResult<any, any, any>;
/**
*
Expand Down
5 changes: 5 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ export interface IAdapter<F, T, E = any, M = any> {
build(target: F): IResult<T, E, M>;
}

export interface Adapter<A = any, B = any> {
adaptOne(item: A): B;
adaptMany?(itens: Array<A>): Array<B>;
}

export interface IEntity<Props> {
toObject<T>(adapter?: IAdapter<IEntity<Props>, any>): T extends {}
? T & EntityMapperPayload
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rich-domain",
"version": "1.23.1",
"version": "1.23.2",
"description": "This package provide utils file and interfaces to assistant build a complex application with domain driving design",
"main": "index.js",
"types": "index.d.ts",
Expand All @@ -15,7 +15,7 @@
"homepage": "https://github.com/4lessandrodev/rich-domain",
"license": "MIT",
"engines": {
"node": ">=16.x <=21"
"node": ">=16.x <=22"
},
"devDependencies": {
"@types/jest": "^28.1.8",
Expand Down
Loading
Loading