Skip to content

Commit

Permalink
added Message and updated event topics
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-jonathan committed Jul 31, 2024
1 parent 7beede3 commit c358a1e
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 27 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cosmicmind/domainjs",
"version": "0.0.1-rc-072224-4",
"version": "0.0.1-rc-073024-4",
"description": "A domain-driven design framework for scalable systems.",
"keywords": [],
"author": {
Expand Down
8 changes: 4 additions & 4 deletions src/Aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ import {
} from '@/Entity'

import {
EventTopics,
EventObservable,
} from '@/Event'
Topics,
ObservableTopics,
} from '@/Topic'

export abstract class Aggregate<E extends Entity, T extends EventTopics = EventTopics> extends EventObservable<T> {
export abstract class Aggregate<E extends Entity, T extends Topics = Topics> extends ObservableTopics<T> {
protected root: E

constructor(root: E) {
Expand Down
189 changes: 189 additions & 0 deletions src/Message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* BSD 3-Clause License
*
* Copyright © 2023, Daniel Jonathan <daniel at cosmicmind dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES LOSS OF USE, DATA, OR PROFITS OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/**
* @module Message
*/

import {
guard,
FoundationError,
} from '@cosmicmind/foundationjs'

import {
Observable,
ObservableTopics,
} from '@cosmicmind/patternjs'

/**
* Represents an Message.
*
* @example
* const message: Message = {
* name: 'John Doe',
* age: 25
* }
*/
export type Message = Record<string, unknown>

/**
* Represents a collection of message topics.
*
* @extends {ObservableTopics}
*
* @property {Message} [K] - The message topic.
*/
export type MessageTopics = ObservableTopics & {
readonly [K: string]: Message
}

/**
* An observable class for handling messages of specific types.
*
* @template T The message topic type.
*/
export class MessageObservable<T extends MessageTopics> extends Observable<T> {}

/**
* Represents the lifecycle hooks for an message property.
*
* @template E - The type of the message.
* @template V - The type of the property value.
*/
export type MessagePropertyLifecycle<E extends Message, V> = {
required?: boolean
validator?(value: V, message: E): boolean | never
}

/**
* Represents a map that defines the lifecycle of message properties.
*
* @template E - The type of the message.
*/
export type MessagePropertyLifecycleMap<E extends Message> = {
[K in keyof E]?: MessagePropertyLifecycle<E, E[K]>
}

export class MessageError extends FoundationError {}

/**
* Represents the lifecycle methods for an message.
*
* @template E - The type of message.
*/
export type MessageLifecycle<E extends Message> = {
created?(message: E): void
error?(error: MessageError): void
properties?: MessagePropertyLifecycleMap<E>
}

/**
* Defines an message with an optional message lifecycle handler.
*
* @template E The type of the message.
* @param {MessageLifecycle<E>} [handler={}] The optional message lifecycle handler.
* @returns {(message: E) => E} A function that creates an message with the given lifecycle handler.
*/
export const defineMessage = <E extends Message>(handler: MessageLifecycle<E> = {}): (message: E) => E =>
(message: E): E => makeMessage(message, handler)

/**
* Creates a proxy message handler that prmessages the modification of message properties.
*
* @template E - The message type.
* @param {MessageLifecycle<E>} handler - The message lifecycle handler.
* @returns {ProxyHandler<E>} - The proxy message handler.
*/
function makeMessageHandler<E extends Message>(handler: MessageLifecycle<E>): ProxyHandler<E> {
return {
set(): never {
throwError('cannot modify message properties', handler)
},
}
}

/**
* Throws an MessageError with a specified message and invokes the error handler.
*
* @template E - The type of Message.
* @param {string} message - The error message.
* @param {MessageLifecycle<E>} handler - The message lifecycle handler.
* @throws {MessageError} - The MessageError instance.
* @return {never} - This method never returns.
*/
function throwError<E extends Message>(message: string, handler: MessageLifecycle<E>): never {
const error = new MessageError(message)
handler.error?.(error)
throw error
}

/**
* Creates an message of type `E`.
*
* @template E - The type of the message to create.
* @param {E} target - The target object to create the message from.
* @param {MessageLifecycle<E>} [handler={}] - The lifecycle handler for the message.
* @returns {E} - The created message object.
* @throws {MessageError} - If the target object is invalid.
*/
function makeMessage<E extends Message>(target: E, handler: MessageLifecycle<E> = {}): E | never {
if (guard<E>(target)) {
const properties = handler.properties

if (guard<MessagePropertyLifecycleMap<E>>(properties)) {
const message = new Proxy(target, makeMessageHandler(handler))

for (const [ key, property ] of Object.entries(properties) as [string, MessagePropertyLifecycle<E, unknown>][]) {
if (property.required) {
if (!(key in target)) {
throwError(`${JSON.stringify(target)} ${key} is required`, handler)
}

if (false === property.validator?.(target[key], message)) {
throwError(`${JSON.stringify(target)} ${key} is invalid`, handler)
}
}
else if (key in target && 'undefined' !== typeof target[key]) {
if (guard(property, 'validator') && false === property.validator?.(target[key], message)) {
throwError(`${JSON.stringify(target)} ${key} is invalid`, handler)
}
}
}

handler.created?.(message)

return message
}
}

throw new MessageError(`${String(target)} is invalid`)
}
26 changes: 4 additions & 22 deletions src/Topic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,10 @@
*/

import {
Observable,
ObservableTopics,
Observable as O,
ObservableTopics as OT,
} from '@cosmicmind/patternjs'

import {
Event,
} from '@/Event'
export type Topics = OT

/**
* Represents a collection of Event topics.
*
* @extends {ObservableTopics}
*
* @property {Event} [K] - A event topic.
*/
export type EventTopics = ObservableTopics & {
readonly [K: string]: Event
}

/**
* An observable class for handling topics of specific types.
*
* @template T The event topic type.
*/
export class TopicObservable<T extends EventTopics> extends Observable<T> {}
export class ObservableTopics<T extends Topics> extends O<T> {}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@
export * from '@/Aggregate'
export * from '@/Event'
export * from '@/Entity'
export * from '@/Message'
export * from '@/Topic'
export * from '@/Value'

0 comments on commit c358a1e

Please sign in to comment.