Skip to content

Commit

Permalink
feat: added types for adapter and init method
Browse files Browse the repository at this point in the history
  • Loading branch information
4lessandrodev committed Jun 2, 2024
1 parent 6408063 commit 53dace9
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 20 deletions.
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-05-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
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
52 changes: 45 additions & 7 deletions tests/core/adapter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ValueObject, Entity, Result, Ok, Fail } from '../../lib/core';
import { IAdapter, IResult } from '../../lib/types';
import { Adapter, IAdapter, IResult } from '../../lib/types';

describe('adapter', () => {
describe('adapter v1', () => {

interface NameProps { value: string; };

class DomainName extends ValueObject<NameProps>{
class DomainName extends ValueObject<NameProps> {
private constructor(props: NameProps) {
super(props);
}
Expand All @@ -18,7 +18,7 @@ describe('adapter', () => {

interface UserProps { id: string; name: DomainName; createdAt?: Date; updatedAt?: Date };

class DomainUser extends Entity<UserProps>{
class DomainUser extends Entity<UserProps> {
private constructor(props: UserProps) {
super(props)
}
Expand All @@ -35,7 +35,7 @@ describe('adapter', () => {
updatedAt: Date;
}

class DomainUserAdapter implements IAdapter<Model, DomainUser>{
class DomainUserAdapter implements IAdapter<Model, DomainUser> {
build(target: Model): IResult<DomainUser> {
return DomainUser.create({
id: target.id,
Expand All @@ -46,7 +46,7 @@ describe('adapter', () => {
}
}

class DataUserAdapter implements IAdapter<DomainUser, Model>{
class DataUserAdapter implements IAdapter<DomainUser, Model> {
build(target: DomainUser): IResult<Model> {

return Result.Ok({
Expand Down Expand Up @@ -103,7 +103,7 @@ describe('adapter', () => {
type Out = { b: string };
type Err = { err: string; stack?: string };

class CustomAdapter implements IAdapter<In, Out, Err>{
class CustomAdapter implements IAdapter<In, Out, Err> {
build(target: In): IResult<Out, Err> {
if (typeof target.a !== 'number') return Fail({ err: 'target.a is not a number' });
return Ok({ b: target.a.toString() });
Expand All @@ -125,3 +125,41 @@ describe('adapter', () => {
});
});
});

describe('adapter v2', () => {

describe('only one method', () => {
class AdapteV2 implements Adapter<number, string> {
adaptOne(item: number): string {
return item.toString();
}
}

it('should adapt one', () => {
const adapter = new AdapteV2();
const adapted = adapter.adaptOne(5);
expect(adapted).toBe('5');
});

});

describe('two methods', () => {
class AdapteV2 implements Adapter<number, string> {
adaptOne(item: number): string {
return item.toString();
}

adaptMany(itens: number[]): string[] {
return itens.map(this.adaptOne);
}

}

it('should adapt many', () => {

const adapter = new AdapteV2();
const values = adapter.adaptMany([1, 2, 3]);
expect(values).toEqual(['1', '2', '3'])
});
});
});
43 changes: 42 additions & 1 deletion tests/core/entity.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Entity, Id, Ok, Result, ValueObject } from "../../lib/core";
import { IResult, UID } from "../../lib/types";
import { Adapter, IResult, UID } from "../../lib/types";

describe("entity", () => {

Expand Down Expand Up @@ -791,4 +791,45 @@ Object {
expect(build).toThrowError();
});
});

describe('init', () => {
type Props = { name: string };
class User extends Entity<Props> {
private constructor(props: Props) {
super(props);
}

public static init(props: Props): User {
if (props.name.length < 2) throw new Error('invalid name');
return new User(props);
}
}

class UAdapter implements Adapter<User, Props> {
adaptOne(item: User): Props {
return { name: item.get('name') }
}
}

it('should init a new user', () => {
const user = User.init({ name: 'Jane' });
const model = user.toObject(new UAdapter());
expect(model).toEqual({ name: 'Jane' });
});

it('should throw an error', () => {
const init = () => User.init({ name: '' });
expect(init).toThrowError();
});
});

describe('native init', () => {
class User extends Entity<string> { };

it('should throw if method is not implemented', () => {
const init = () => User.init('Jane');
expect(init).toThrowError('method not implemented: init');
});

});
});
65 changes: 62 additions & 3 deletions tests/core/value-object.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Class, Ok, Result, ValueObject } from "../../lib/core";
import { ICommand, IResult } from "../../lib/types";
import { Adapter, ICommand, IResult } from "../../lib/types";
import { Utils, Validator } from "../../lib/utils";

describe('value-object', () => {
Expand Down Expand Up @@ -134,7 +134,7 @@ describe('value-object', () => {
expect(addressObject.street.toUpperCase()).toBe('5TH AVENUE');
});

it('should be imutable', () => {
it('should be immutable', () => {
const address = new Address({
city: new City('A'),
number: 123,
Expand Down Expand Up @@ -764,7 +764,7 @@ Primitive {
expect(copy.get('value')).toEqual([1, 2, 3]);
expect(copy.toObject()).toEqual([1, 2, 3]);
expect(copy.isEqual(arr)).toBeTruthy();
expect(copy.isEqual( ArrayVo.init([4, 5, 6]))).toBeFalsy();
expect(copy.isEqual(ArrayVo.init([4, 5, 6]))).toBeFalsy();
});

// Test for SymbolVo
Expand Down Expand Up @@ -873,4 +873,63 @@ Object {
expect(copy.isEqual(Complex.init({ ...props, index: NumberVo.init(2) }))).toBeFalsy();
});
});

describe('init', () => {

class Name extends ValueObject<string> {
constructor(name: string) {
super(name);
}
};

it('should throw error if init is not implemented', () => {
const init = () => Name.init('Jane');
expect(init).toThrowError('method not implemented: init');
});

class Custom extends Name {
private constructor(name: string) {
super(name)
}

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

it('should init with success', () => {

const name = Custom.init('Jane');
expect(name.get('value')).toBe('Jane');

});

it('should adapt using adapter', () => {
class AdaptName implements Adapter<Custom, string> {
adaptOne(item: Custom): string {
return item.get('value') + ' Doe';
}
}
const name = Custom.init('Jane');
expect(name.toObject(new AdaptName())).toBe('Jane Doe');
});

it('should adapt many', () => {

class AdaptName implements Adapter<Custom, string> {
adaptOne(item: Custom): string {
return item.get('value') + ' Doe';
}

adaptMany(itens: Custom[]): string[] {
return itens.map(this.adaptOne);
}
}

const adapter = new AdaptName();
const names = ['Jane', 'John'].map(Custom.init)
const values = adapter.adaptMany(names);
expect(values).toEqual(['Jane Doe', 'John Doe']);
});
});
});

0 comments on commit 53dace9

Please sign in to comment.