All notable changes to this project will be documented in this file.
- Fix: Improved return typings in the
get
method of the value object. - Fix: Bug fix #152 when cloning an instance of a value object.
- Fix: Ensure that properties of an entity or aggregate will always be an object.
- Fix: Improved validations performed in the
isEqual
method of the value object.
- Removed the
set
method from value object instances. - Changed the way the
toObject
method works. Path shortcutting of property access has been removed when props have only one attribute. - Implemented some improvements in how value objects handle primitive values.
- Break Change
If you are using the toObject
method in production to create a model from Aggregate, Entity, or Value Object domain instances, it is important to note that the property access path is no longer shortened.
Now the model object follows exactly the contract defined in props
.
For example:
If an object is defined in props
, even if props contains only one property, if it is an object, the toObject
method will generate a model according to props
.
type Props = { value: number };
class Price extends ValueObject<Props>{};
const price = new Price({ value: 200 });
console.log(price.toObject());
// > 200
type Props = { value: number };
class Price extends ValueObject<Props>{};
const price = new Price({ value: 200 });
console.log(price.toObject());
// > { value: 200 }
If you want to maintain the return with primitive value without it being an object, use props
of primitive type.
class Price extends ValueObject<number>{};
const price = new Price(200);
console.log(price.toObject());
// > 200
Another alternative is to use an adapter.
class Adapter implements IAdapter<Domain, Model> {
adapt(domain: Domain): Result<Model> {
//...
}
}
price.toObject(new Adapter());
- Added types inference to method "toObject" on entities and value objects
- Added method
getRaw
to getters-and-setters instance to expose props object
#146 Special thanks and credits to @hikinine
- Added feature to segregate events by contexts.
Implementation Details:
- Implemented a mechanism to subscribe to events under different contexts.
- Implemented the ability to dispatch events to specific contexts based on the event name pattern.
- Introduced validation to ensure that event names follow the pattern "contextName:EventName".
// Example Usage
const context = Context.events();
const handler = (event) => console.log(event);
// Subscribing to events under different contexts
context.subscribe('Context-A:SIGNUP', handler);
context.subscribe('Context-B:SIGNUP', handler);
context.subscribe('Context-C:SIGNUP', handler);
context.subscribe('Context-B:NOTIFY', handler);
context.subscribe('Context-B:SEND-EMAIL', handler);
// Dispatching events to specific contexts
// Dispatches the SIGNUP event to Context-B
context.dispatchEvent('Context-B:SIGNUP');
// Dispatches the SIGNUP event to all contexts
context.dispatchEvent('*:SIGNUP');
// Dispatches all events to all contexts. Not recommended
context.dispatchEvent('*:*');
// Dispatches all events under Context-B
context.dispatchEvent('Context-B:*');
-
Added Context Communication: Implemented a feature for inter-context communication within the library, enabling seamless interaction between different contexts within a project.
-
Introduced Custom Events: Custom events can now be dispatched both globally and within specific aggregates, providing flexibility in event handling and propagation.
-
Compatibility with Browser and Node.js: Maintained compatibility between browser and server environments by utilizing CustomEvents in the DOM for browser compatibility and standard event handling for Node.js.
-
Aggregate Event Management: Aggregates retain event management methods, allowing for the encapsulation of business rules within specific contexts.
- Investigating potential chain reactions of lambda functions in certain scenarios.
-
Strong Typing: Consideration for enhancing typing support for event parameters to provide better developer guidance and error checking.
-
Further Investigation: Continue investigating potential implications of fanning-out from a single lambda function to ensure robustness in complex scenarios.
// implement extending to EventHandler
class Handler extends EventHandler<Product> {
constructor() { super({ eventName: 'sample' }) };
dispatch(product: Product, args_1: [DEvent<Product>, any[]]): void | Promise<void> {
const model = product.toObject();
const [event, args] = args_1;
console.log(model);
console.log(event);
console.log(event.eventName);
console.log(event.options);
// custom params provided on call dispatchEvent
console.log(args);
}
}
// Listening global events
contextX.subscribe('ACCOUNT:USER_REGISTERED', (arg) => {
console.log(arg);
});
// ------------------
// User Account as Context Y
type Props = { name: string };
class User extends Aggregate<Props>{
private constructor(props: Props) {
super(props);
}
public static signUp(name: string): User {
const user = new User({ name });
// add handler according to business rule
user.addEvent(new SignUpEvent());
return user;
}
public static create(props: Props): Result<User> {
return Ok(new User(props));
}
}
const contextY = Context.events();
class SignUpEvent extends EventHandler<User> {
constructor() {
super({ eventName: 'SIGNUP' })
}
dispatch(user: User): void {
// dispatch to global context event manager
user.context().dispatchEvent("ACCOUNT:USER_REGISTERED", user.toObject());
};
}
const user = User.signUp('John Doe');
// dispatch to call handler
user.dispatchEvent('SIGNUP');
Fixed an issue with the event dispatching mechanism. Notes This version introduces significant changes to the event handling system, enhancing the flexibility and usability of the Aggregate class.
-
Added Context Communication: Implemented a feature for inter-context communication within the library, enabling seamless interaction between different contexts within a project.
-
Introduced Custom Events: Custom events can now be dispatched both globally and within specific aggregates, providing flexibility in event handling and propagation.
-
Compatibility with Browser and Node.js: Maintained compatibility between browser and server environments by utilizing CustomEvents in the DOM for browser compatibility and standard event handling for Node.js.
-
Aggregate Event Management: Aggregates retain event management methods, allowing for the encapsulation of business rules within specific contexts.
- Investigating potential chain reactions of lambda functions in certain scenarios.
-
Strong Typing: Consideration for enhancing typing support for event parameters to provide better developer guidance and error checking.
-
Further Investigation: Continue investigating potential implications of fanning-out from a single lambda function to ensure robustness in complex scenarios.
import { Aggregate, Ok, Result, Context, EventHandler } from 'rich-domain';
// ------------------
// Some Context X
const contextX = Context.events();
// Listening global events
contextX.subscribe('USER_REGISTERED', (arg) => {
console.log(arg);
});
// ------------------
// User Account as Context Y
type Props = { name: string };
class User extends Aggregate<Props>{
private constructor(props: Props) {
super(props);
}
public static signUp(name: string): User {
const user = new User({ name });
// add handler according to business rule
user.addEvent(new SignUpEvent());
return user;
}
public static create(props: Props): Result<User> {
return Ok(new User(props));
}
}
const contextY = Context.events();
class SignUpEvent extends EventHandler<User> {
constructor() {
super({ eventName: 'SIGNUP' })
}
dispatch(user: User): void {
// dispatch to global context event manager
user.context().dispatchEvent("USER_REGISTERED", user.toObject());
};
}
const user = User.signUp('John Doe');
// dispatch to call handler
user.dispatchEvent('SIGNUP');
thanks to @mmmoli for contributions and inspirations
- fix bind window.crypto to browser context
- Improved UUID generation function to ensure better atomicity and performance.
- Enhanced environment detection for browser and Node.js environments.
- Refactored UUID generation logic to eliminate potential race conditions and ensure thread safety.
- Updated UUID generation algorithm to utilize native environment resources more efficiently.
- Added support for generating UUIDs in both browser and Node.js environments, leveraging appropriate platform-specific methods.
- Enhanced UUID generation function to follow standard UUID format (8-4-4-4-12) using hexadecimal characters.
- Ensured backward compatibility with existing usage patterns while delivering significant improvements in reliability and efficiency.
- Added context binding (this.bind(this)) to the EventHandler class constructor to ensure proper access to class properties and methods within event handlers.
- change: modify the to add and dispatch event on aggregate
- added: Implemented a new instance of the event management class, allowing for better event handling and organization.
Details: Introduced a new method to create an event management instance for the aggregate. Improved event handling and organization, enhancing the overall performance and efficiency of event management. Enabled easier integration and usage of event management features in various applications.
import { TsEvents } from 'rich-domain';
/**
* Create a new instance of the event management class
* Pass the aggregate as a parameter
* Return an event management instance for the aggregate
*/
const events = new TsEvents(aggregate);
events.addEvent('eventName', (...args) => {
console.log(args);
});
// dispatch all events
await events.dispatchEvents();
// OR dispatch a specific event
events.dispatchEvent('eventName');
Implemented a new event handling mechanism using the Handler class. Example:
// implement extending to EventHandler
class Handler extends EventHandler<Product> {
constructor() {
super({ eventName: 'sample' });
};
dispatch(product: Product, params: [DEvent<Product>, any[]]): void | Promise<void> {
const model = product.toObject();
const [event, args] = params;
console.log(model);
console.log(event);
// custom params provided on call dispatchEvent
console.log(args);
}
}
const event = new Handler();
aggregate.addEvent(event);
aggregate.dispatchEvent('sample', { custom: 'params' });
await aggregate.dispatchAll();
Fixed an issue with the event dispatching mechanism. Notes This version introduces significant changes to the event handling system, enhancing the flexibility and usability of the Aggregate class.
Change event handler implementation
// use extends to EventHandler
class Handler extends EventHandler<Product> {
constructor() {
super({ eventName: 'sample' });
};
// aggregate as first param
dispatch(product: Product): void | Promise<void> {
// your implementation
}
}
Remove imports DomainEvents
and use events from aggregate instance
aggregate.addEvent(event);
aggregate.dispatchEvent('eventName');
- change: added support to nodejs v21
- change: update deps
- changed: remove history (
snapshot
) deprecated method. removed to improve performance and save memory usage. - change: update deps
- fixed: ensure compare null or undefined using
isEqual
method on value-object, entity and aggregate instance. - fixed: ensure create props copy on clone domain entities.
- added: create a shortcut to
isEqual
method on id instance to compare values. - changed: mark history (
snapshot
) method as deprecated. Will be removed on next version to improve performance. - change: update deps
- fixed: ensure custom payload error to adapter
- fixed: ensure provide null value when call toObject function on entity, aggregate and value objects
- fixed: ensure custom payload type on create method
- fixed: check if exists some id before transform to a simple value on execute toObject method.
- changed: move event manager to instance of aggregate #50 by Paulo Santana
The user will still have the option of using the global event handler using DomainEvents
class, however when adding an event using an instance of an aggregate, the event can only handle from the instance of the aggregate
// add event to instance of aggregate - OK
user.addEvent(event);
// dispatch from aggregate instance - OK
user.dispatchEvent('EventName', handler);
/**
* if you try dispatch the event globally It will not works, because the events was added to aggregate * instance specifically
* The Event does not exists globally.
*/
DomainEvents.dispatch({ eventName: 'EventName', id: user.id });
// to dispatch global
// add the event globally
DomainEvents.addEvent({ /* ... */ });
// so dispatch it globally. Works!
DomainEvents.dispatch({ /* ... */});
- changed: Result properties to private using # to do not serialize private keys
- changed: Types for create method: Entity, Aggregate and ValueObject
- changed: Clone method in Entity and Aggregate instance. Now It accepts optional props.
// create a copy of user instance and also copy domain events from original aggregate user.
const userCopy = user.clone({ copyEvents: true });
// create a copy and apply a new name value
const userCopy = user.clone({ name });
- update build
- added: event handler callback
user.dispatchEvent('EventName', handler);
- utils.number: fix precision on calculation and infinity case
// Example using fractionDigits
// Default fractionDigits is 5
util.number(0.03).divideBy(0.09, { fractionDigits: 4 });
> 0.3333
// Example Infinity case now returns 0
// 0 / 1 = Infinity
util.number(0).divideBy(1);
> 0
- Rename methods
- Change payload
- ValueObject, Entity, Aggregate:
clone
method now returns an instance insteadResult
- ValueObject, Entity, Aggregate:
set
andchange
method now returnstrue
if the value has changed and returnsfalse
if the value has not changed.
// Example using set now
const changed = user.set("name").to(age);
console.log(changed);
> true
// Example using clone now
const copy = user.clone();
console.log(copy.get("name").get("value"))
> "Jane Doe"
- Util: added method date. pull request 32
- Aggregate, Entity, ValueObject: added
util
method to instance. - Util: added class to domain with some utils functions. pull request 31
- Aggregate: added method
dispatchEvent
to handle domain events from aggregate instance. - Validator: added method
isSpecialChar
andhasSpecialChar
to check special character.
- Entity: added method
isEqual
to compare current instance with another one. - ValueObject: added method
isEqual
to compare current instance with another one. Issue 27
- toObject method on entity: fix error on process simple object on entity issue #25
- node version: update requirements. node version required >=16 and < 19
- value-object: mark set function as deprecated
- value-object: mark change function as deprecated
- validator: change methods for string (hasLengthBetween - now validate only interval)
- validator: rename method isPair to isEven
- validator - string added method hasLengthBetweenOrEqual
- validator - number isBetweenOrEqual
The function still works, but it is marked as deprecated. show warning if using.
- AutoMapper: fix conversion for aggregate
- Now its possible to convert entity on aggregate
- Domain Events: fix iteration to domain id
- Combine: fix iterator not defined
- dispatchAll: added fn to dispatch all event by aggregate id
- Types: update types for Result
- Result: change to type any result arg on combine function
- Result: added Combine function as single import
- Types: added Payload type as Result type
- validator: added method to validate if all chars in string is number
- result: ensure result props to be read only
- refactor: Fail
- refactor: Ok
- refactor: Result.Ok
- refactor: Result.fail
Change generic types order for Result.fail
and Result.Ok
Now each method has your own order Example:
// for implementation:
IResult<Payload, Error, MetaData>;
// before the generic order was the same for both method.
// now for
Result.Ok
// the generic order is
Result.Ok<Payload, MetaData, Error>(payload metaData);
// for
Result.fail
//the generic order is
Result.fail<Error, MetaData, Payload>(error, metaData);
Changes made on Ok
import { Ok } from 'rich-domain';
// simple use case for success. no arg required
return Ok();
// arg required
return Ok<string>('my payload');
// arg and metaData required
interface MetaData {
arg: string;
}
return Ok<string, MetaData>('payload', { arg: 'sample' })
Changes made on Fail
import { Fail } from 'rich-domain';
// simple use case for success. no arg required
return Fail();
// arg required
return Fail<string>('my payload');
// arg and metaData required
interface MetaData {
arg: string;
}
return Fail<string, MetaData>('payload', { arg: 'sample' })
- feat: implement function Fail
- feat: implement function Ok
fix: implement support for native randomUUID
chore: updated dependencies
- typescript to version 4.8.2
- @types/jest to 28.1.8
- jest 28.1.3
fix: update some types error on update typescript
chore: added license ci: added dependa bot ci: added build and test step on merge docs: added full documentation to readme
- changed validation method args position
Now second arg is optional. The key is not required
// from
validation<Key extends keyof Props>(key: Key, value: Props[Key]): boolean {};
// to
validation<Key extends keyof Props>(value: Props[Key], key: Key): boolean {};
- createMany method on domain entity and value objects
const { result, data } = ValueObject.createMany([
Class<AgeProps>(Age, props),
Class<PriceProps>(Price, props),
Class<NameProps>(Name, props)
]);
result.isOk() // true
const age = data.next() as IResult<Age>;
const price = data.next() as IResult<Price>;
const name = data.next() as IResult<Name>;
age.value().get('value') // 21
- from
isOK()
toisOk()
- from
OK()
toOk()
- Result: now you may return Result
const result: Result<void> = Result.Ok();
- Rename methods:
- from
isFailure()
toisFail()
- from
isSuccess()
toisOK()
- from
isShortID()
toisShort()
- from
success()
toOK()
- from
createShort()
toshort()