drimion 0.1.0

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.
Files changed (51) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +955 -0
  3. package/dist/cli/index.js +1045 -0
  4. package/dist/cli/templates/aggregate.ts.hbs +22 -0
  5. package/dist/cli/templates/entity.ts.hbs +16 -0
  6. package/dist/cli/templates/repository.ts.hbs +24 -0
  7. package/dist/cli/templates/use-case.ts.hbs +20 -0
  8. package/dist/cli/templates/value-object.ts.hbs +16 -0
  9. package/dist/kernel/core/aggregate.ts +234 -0
  10. package/dist/kernel/core/entity.ts +348 -0
  11. package/dist/kernel/core/id.ts +207 -0
  12. package/dist/kernel/core/index.ts +5 -0
  13. package/dist/kernel/core/repository.ts +81 -0
  14. package/dist/kernel/core/value-object.ts +309 -0
  15. package/dist/kernel/events/browser-event-manager.ts +241 -0
  16. package/dist/kernel/events/domain-event.ts +76 -0
  17. package/dist/kernel/events/event-bus.ts +158 -0
  18. package/dist/kernel/events/event-context.ts +95 -0
  19. package/dist/kernel/events/event-manager.ts +20 -0
  20. package/dist/kernel/events/event-utils.ts +19 -0
  21. package/dist/kernel/events/index.ts +7 -0
  22. package/dist/kernel/events/server-event-manager.ts +169 -0
  23. package/dist/kernel/helpers/auto-mapper.ts +222 -0
  24. package/dist/kernel/helpers/domain-classes.ts +162 -0
  25. package/dist/kernel/helpers/domain-error.ts +52 -0
  26. package/dist/kernel/helpers/getters-setters.ts +385 -0
  27. package/dist/kernel/helpers/index.ts +7 -0
  28. package/dist/kernel/index.ts +73 -0
  29. package/dist/kernel/libs/crypto.ts +33 -0
  30. package/dist/kernel/libs/index.ts +5 -0
  31. package/dist/kernel/libs/iterator.ts +298 -0
  32. package/dist/kernel/libs/result.ts +252 -0
  33. package/dist/kernel/libs/utils.ts +260 -0
  34. package/dist/kernel/libs/validator.ts +353 -0
  35. package/dist/kernel/types/adapter.types.ts +26 -0
  36. package/dist/kernel/types/command.types.ts +37 -0
  37. package/dist/kernel/types/entity.types.ts +60 -0
  38. package/dist/kernel/types/event.types.ts +129 -0
  39. package/dist/kernel/types/index.ts +26 -0
  40. package/dist/kernel/types/iterator.types.ts +39 -0
  41. package/dist/kernel/types/result.types.ts +122 -0
  42. package/dist/kernel/types/uid.types.ts +18 -0
  43. package/dist/kernel/types/utils.types.ts +120 -0
  44. package/dist/kernel/types/value-object.types.ts +20 -0
  45. package/dist/kernel/utils/date.utils.ts +111 -0
  46. package/dist/kernel/utils/index.ts +32 -0
  47. package/dist/kernel/utils/number.utils.ts +341 -0
  48. package/dist/kernel/utils/object.utils.ts +61 -0
  49. package/dist/kernel/utils/string.utils.ts +128 -0
  50. package/dist/kernel/utils/type.utils.ts +33 -0
  51. package/package.json +59 -0
@@ -0,0 +1,241 @@
1
+ import type { DomainEventPayload, EventEntry } from "../types/event.types";
2
+ import { BaseEventManager } from "./event-manager";
3
+ import { ValidateEventName } from "./event-utils";
4
+
5
+ /**
6
+ * @description
7
+ * Duck-typed interface for the browser `window` object.
8
+ *
9
+ * Using a structural type instead of `Window & typeof globalThis` keeps this
10
+ * module free of DOM lib dependencies, making the library usable in projects
11
+ * that do not include `"lib": ["DOM"]` in their tsconfig.
12
+ */
13
+ export interface WindowLike {
14
+ dispatchEvent(event: Event): boolean;
15
+ addEventListener(
16
+ type: string,
17
+ listener: (event: Event) => void,
18
+ options?: boolean | AddEventListenerOptions,
19
+ ): void;
20
+ removeEventListener(
21
+ type: string,
22
+ listener: (event: Event) => void,
23
+ options?: boolean | EventListenerOptions,
24
+ ): void;
25
+ sessionStorage: {
26
+ getItem(key: string): string | null;
27
+ setItem(key: string, value: string): void;
28
+ removeItem(key: string): void;
29
+ };
30
+ CustomEvent: new <T>(
31
+ type: string,
32
+ eventInitDict?: { bubbles?: boolean; detail?: T },
33
+ ) => Event;
34
+ }
35
+
36
+ /**
37
+ * @description
38
+ * Browser-side singleton event manager backed by the native `window` CustomEvent API.
39
+ *
40
+ * Manages application-level event subscriptions and dispatches for browser environments.
41
+ * Subscriptions are persisted in `sessionStorage` so their existence can be detected
42
+ * across re-renders. Listeners are automatically cleaned up on page unload.
43
+ *
44
+ * Supports wildcard event dispatch using `*` as a glob-style pattern.
45
+ *
46
+ * Use `EventContext.resolve()` to obtain this instance rather than constructing it directly.
47
+ * For most use cases, prefer `EventBus` — use this only when you need to tap into
48
+ * the native browser CustomEvent system.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const manager = BrowserEventManager.instance(window);
53
+ *
54
+ * manager.subscribe('order:placed', (event) => {
55
+ * console.log(event.detail);
56
+ * });
57
+ *
58
+ * manager.dispatchEvent('order:placed', { orderId: '123' });
59
+ * ```
60
+ */
61
+ export class BrowserEventManager extends BaseEventManager {
62
+ private static _instance: BrowserEventManager;
63
+ private readonly entries: EventEntry[] = [];
64
+ private readonly storagePrefix = "browser:event:";
65
+ private initialized = false;
66
+
67
+ private constructor(private readonly win: WindowLike) {
68
+ super();
69
+ if (typeof win === "undefined" || win === null) {
70
+ throw new Error("BrowserEventManager requires a valid window object.");
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @description
76
+ * Returns the singleton `BrowserEventManager` instance, creating it if necessary.
77
+ *
78
+ * @param win The browser `window` object (or any `WindowLike` implementation).
79
+ * @returns The singleton `BrowserEventManager`.
80
+ * @throws {Error} If called outside a browser environment.
81
+ */
82
+ public static instance(win: WindowLike): BrowserEventManager {
83
+ if (!BrowserEventManager._instance) {
84
+ BrowserEventManager._instance = new BrowserEventManager(win);
85
+ }
86
+ return BrowserEventManager._instance;
87
+ }
88
+
89
+ /**
90
+ * @description
91
+ * Checks whether an event with the given name is currently registered.
92
+ *
93
+ * Checks both the internal entry list and `sessionStorage` for persistence
94
+ * across re-renders.
95
+ *
96
+ * @param eventName The event name to check.
97
+ * @returns `true` if the event is registered.
98
+ */
99
+ public exists(eventName: string): boolean {
100
+ const inStorage = !!this.win.sessionStorage.getItem(
101
+ this.storagePrefix + eventName,
102
+ );
103
+ const inEntries = this.entries.some((e) => e.eventName === eventName);
104
+ return inStorage || inEntries;
105
+ }
106
+
107
+ /**
108
+ * @description
109
+ * Dispatches a custom event by name, forwarding optional arguments as the `detail` payload.
110
+ *
111
+ * Supports wildcard patterns — if `eventName` contains `*`, all registered events
112
+ * whose names match the resulting regex are dispatched.
113
+ *
114
+ * @param eventName The event name or wildcard pattern to dispatch.
115
+ * @param args Additional arguments forwarded as `detail` to handlers.
116
+ */
117
+ public dispatchEvent(eventName: string, ...args: unknown[]): void {
118
+ ValidateEventName(eventName);
119
+
120
+ if (eventName.includes("*")) {
121
+ const regex = new RegExp(eventName.replace("*", ".*"));
122
+ for (const entry of this.entries) {
123
+ if (regex.test(entry.eventName)) {
124
+ this.win.dispatchEvent(
125
+ new this.win.CustomEvent(entry.eventName, {
126
+ bubbles: true,
127
+ detail: args,
128
+ }),
129
+ );
130
+ }
131
+ }
132
+ return;
133
+ }
134
+
135
+ this.win.dispatchEvent(
136
+ new this.win.CustomEvent(eventName, {
137
+ bubbles: true,
138
+ detail: args,
139
+ }),
140
+ );
141
+ }
142
+
143
+ /**
144
+ * @description
145
+ * Removes a registered event, cleaning up its listener and `sessionStorage` entry.
146
+ *
147
+ * @param eventName The name of the event to remove.
148
+ * @returns `true` if the event was found and removed; `false` otherwise.
149
+ */
150
+ public removeEvent(eventName: string): boolean {
151
+ // this.win.sessionStorage.removeItem(this.storagePrefix + eventName);
152
+ // const entry = this.entries.find((e) => e.eventName === eventName);
153
+ // if (!entry) return false;
154
+ // this.entries.splice(this.entries.indexOf(entry), 1);
155
+ // // Cast through unknown — the callback is structurally compatible at runtime
156
+ // // (CustomEvent extends Event), but TypeScript cannot verify this statically.
157
+ // this.win.removeEventListener(
158
+ // eventName,
159
+ // entry.callback as unknown as (event: Event) => void,
160
+ // );
161
+ // return true;
162
+ const matching = this.entries.filter((e) => e.eventName === eventName);
163
+
164
+ if (matching.length === 0) return false;
165
+
166
+ // Remove from entries
167
+ this.entries.splice(
168
+ 0,
169
+ this.entries.length,
170
+ ...this.entries.filter((e) => e.eventName !== eventName),
171
+ );
172
+
173
+ // Remove all listeners
174
+ for (const entry of matching) {
175
+ this.win.removeEventListener(
176
+ eventName,
177
+ entry.callback as unknown as (event: Event) => void,
178
+ );
179
+ }
180
+
181
+ // Remove storage
182
+ this.win.sessionStorage.removeItem(this.storagePrefix + eventName);
183
+
184
+ return true;
185
+ }
186
+
187
+ /**
188
+ * @description
189
+ * Subscribes a callback to the specified event name using the native `window` event system.
190
+ *
191
+ * Persists the subscription in `sessionStorage` and registers a `beforeunload`
192
+ * cleanup listener. If the event is already registered, this call is a no-op.
193
+ *
194
+ * Event names must follow the `context:EventName` format.
195
+ *
196
+ * @param eventName The event name to subscribe to.
197
+ * @param fn The callback to invoke when the event is dispatched.
198
+ *
199
+ * @throws {DomainError} If the event name does not follow the required format.
200
+ */
201
+ public subscribe(
202
+ eventName: string,
203
+ fn: (event: DomainEventPayload) => void | Promise<void>,
204
+ ): void {
205
+ ValidateEventName(eventName);
206
+
207
+ this.entries.push({ eventName, callback: fn });
208
+ this.win.sessionStorage.setItem(
209
+ this.storagePrefix + eventName,
210
+ Date.now().toString(),
211
+ );
212
+ this.win.addEventListener(
213
+ eventName,
214
+ fn as unknown as (event: Event) => void,
215
+ );
216
+ if (!this.initialized) {
217
+ this.win.addEventListener("beforeunload", () => {
218
+ for (const entry of this.entries) {
219
+ this.win.sessionStorage.removeItem(
220
+ this.storagePrefix + entry.eventName,
221
+ );
222
+ }
223
+ });
224
+
225
+ this.initialized = true;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * @description
231
+ * Resets the cached event manager instance.
232
+ *
233
+ * Intended for use in tests where environment switching or clean state
234
+ * between test cases is required. Not recommended in production code.
235
+ *
236
+ * @internal
237
+ */
238
+ public static __reset(): void {
239
+ BrowserEventManager._instance = undefined as unknown as BrowserEventManager;
240
+ }
241
+ }
@@ -0,0 +1,76 @@
1
+ import type { DomainEvent } from "../types/event.types";
2
+
3
+ /**
4
+ * @description
5
+ * Abstract base class for defining strongly-typed domain events.
6
+ *
7
+ * Extend this class when you want an OOP-style event definition with a fixed
8
+ * `type` string baked into the class. The `aggregateId`, `aggregateName`, and
9
+ * `occurredAt` fields are set automatically by `Aggregate.emit()` — subclasses
10
+ * only need to declare the `payload` shape.
11
+ *
12
+ * Using this base class is **optional**. `Aggregate.emit()` accepts any object
13
+ * conforming to `DomainEvent<TPayload>` — a plain object literal works equally
14
+ * well. Prefer this class when you want a reusable, self-describing event type
15
+ * that can be imported and checked with `instanceof`.
16
+ *
17
+ * @template TPayload The shape of the event-specific data.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // Define the event
22
+ * interface OrderPlacedPayload { total: number; customerId: string }
23
+ *
24
+ * class OrderPlacedEvent extends BaseDomainEvent<OrderPlacedPayload> {
25
+ * static readonly type = 'order:placed' as const;
26
+ *
27
+ * constructor(aggregateId: string, payload: OrderPlacedPayload) {
28
+ * super(OrderPlacedEvent.type, aggregateId, 'Order', payload);
29
+ * }
30
+ * }
31
+ *
32
+ * // Inside an Aggregate subclass
33
+ * class Order extends Aggregate<OrderProps> {
34
+ * public place(): void {
35
+ * this.change('status', 'placed');
36
+ * this.emit(new OrderPlacedEvent(this.id.value(), {
37
+ * total: this.get('total'),
38
+ * customerId: this.get('customerId'),
39
+ * }));
40
+ * }
41
+ * }
42
+ *
43
+ * // Application layer
44
+ * order.place();
45
+ * await repo.save(order);
46
+ * await eventBus.publishAll(order.pullEvents());
47
+ * ```
48
+ */
49
+ export abstract class BaseDomainEvent<TPayload = unknown>
50
+ implements DomainEvent<TPayload>
51
+ {
52
+ readonly type: string;
53
+ readonly aggregateId: string;
54
+ readonly aggregateName: string;
55
+ readonly occurredAt: Date;
56
+ readonly payload: TPayload;
57
+
58
+ /**
59
+ * @param type The event type identifier, e.g. `'order:placed'`.
60
+ * @param aggregateId The `id.value()` of the aggregate that emitted this event.
61
+ * @param aggregateName The class name of the aggregate, e.g. `'Order'`.
62
+ * @param payload Event-specific data. Keep flat and serializable.
63
+ */
64
+ constructor(
65
+ type: string,
66
+ aggregateId: string,
67
+ aggregateName: string,
68
+ payload: TPayload,
69
+ ) {
70
+ this.type = type;
71
+ this.aggregateId = aggregateId;
72
+ this.aggregateName = aggregateName;
73
+ this.occurredAt = new Date();
74
+ this.payload = payload;
75
+ }
76
+ }
@@ -0,0 +1,158 @@
1
+ import type {
2
+ DomainEvent,
3
+ EventSubscriber,
4
+ IEventBus,
5
+ SubscriberEntry,
6
+ } from "../types/event.types";
7
+
8
+ /**
9
+ * @description
10
+ * A simple, in-process event bus that ships with the library as a zero-config
11
+ * default. Suitable for monolithic applications, unit tests, and any context
12
+ * where events do not need to cross process or network boundaries.
13
+ *
14
+ * `EventBus` implements `IEventBus` so it can be swapped for any external
15
+ * transport (Redis, NATS, BullMQ, etc.) without touching domain code.
16
+ *
17
+ * Subscribers are registered per `type` string and invoked in registration
18
+ * order. Each subscriber runs independently — a failure in one does not
19
+ * prevent the others from running.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const bus = new EventBus();
24
+ *
25
+ * // Register subscribers (application layer)
26
+ * bus.subscribe('order:placed', async (event) => {
27
+ * await sendConfirmationEmail(event.payload.customerId);
28
+ * });
29
+ *
30
+ * // Publish after repository.save() (application layer)
31
+ * await repo.save(order);
32
+ * await bus.publishAll(order.pullEvents());
33
+ * ```
34
+ */
35
+ export class EventBus implements IEventBus {
36
+ private readonly entries: SubscriberEntry[] = [];
37
+
38
+ /**
39
+ * @description
40
+ * Registers a subscriber for a specific event type.
41
+ *
42
+ * Multiple subscribers for the same `type` are all invoked when that event
43
+ * is published. Subscribing the same function twice for the same type will
44
+ * register it twice — callers are responsible for deduplication if needed.
45
+ *
46
+ * @param type The event type to listen for, e.g. `'order:placed'`.
47
+ * @param subscriber The callback to invoke when a matching event is published.
48
+ */
49
+ public subscribe<TPayload = unknown>(
50
+ type: string,
51
+ subscriber: EventSubscriber<TPayload>,
52
+ ): void {
53
+ this.entries.push({
54
+ type,
55
+ subscriber: subscriber as EventSubscriber,
56
+ });
57
+ }
58
+
59
+ /**
60
+ * @description
61
+ * Removes all subscribers for a given event type.
62
+ *
63
+ * @param type The event type whose subscribers should be removed.
64
+ * @returns The number of subscribers that were removed.
65
+ */
66
+ public unsubscribe(type: string): number {
67
+ const before = this.entries.length;
68
+ const remaining = this.entries.filter((e) => e.type !== type);
69
+ this.entries.splice(0, this.entries.length, ...remaining);
70
+ return before - this.entries.length;
71
+ }
72
+
73
+ /**
74
+ * @description
75
+ * Publishes a single domain event, invoking all matching subscribers in
76
+ * registration order.
77
+ *
78
+ * Subscriber errors are caught and re-thrown after all subscribers have
79
+ * had a chance to run, collected as an `AggregateError`.
80
+ *
81
+ * @param event The domain event to publish.
82
+ * @throws {AggregateError} If one or more subscribers throw.
83
+ */
84
+ public async publish(event: DomainEvent): Promise<void> {
85
+ const matching = this.entries.filter((e) => e.type === event.type);
86
+ const errors: unknown[] = [];
87
+
88
+ for (const entry of matching) {
89
+ try {
90
+ await entry.subscriber(event);
91
+ } catch (err) {
92
+ errors.push(err);
93
+ }
94
+ }
95
+
96
+ if (errors.length > 0) {
97
+ throw new AggregateError(
98
+ errors,
99
+ `EventBus: ${errors.length} subscriber(s) failed for event "${event.type}".`,
100
+ );
101
+ }
102
+ }
103
+
104
+ /**
105
+ * @description
106
+ * Publishes all domain events in the provided array, in order.
107
+ * Each event is published independently — a failure in one event's
108
+ * subscribers does not prevent subsequent events from being published.
109
+ *
110
+ * Errors from all events are collected and thrown together as an
111
+ * `AggregateError` after all events have been processed.
112
+ *
113
+ * @param events The array of domain events to publish.
114
+ * @throws {AggregateError} If any subscriber across any event throws.
115
+ */
116
+ public async publishAll(events: ReadonlyArray<DomainEvent>): Promise<void> {
117
+ const errors: unknown[] = [];
118
+
119
+ for (const event of events) {
120
+ try {
121
+ await this.publish(event);
122
+ } catch (err) {
123
+ // AggregateError from publish() — unwrap its inner errors
124
+ if (err instanceof AggregateError) {
125
+ errors.push(...err.errors);
126
+ } else {
127
+ errors.push(err);
128
+ }
129
+ }
130
+ }
131
+
132
+ if (errors.length > 0) {
133
+ throw new AggregateError(
134
+ errors,
135
+ `EventBus: ${errors.length} error(s) occurred while publishing ${events.length} event(s).`,
136
+ );
137
+ }
138
+ }
139
+
140
+ /**
141
+ * @description
142
+ * Returns the number of subscribers currently registered for a given type.
143
+ * Useful for testing and diagnostics.
144
+ *
145
+ * @param type The event type to count subscribers for.
146
+ */
147
+ public subscriberCount(type: string): number {
148
+ return this.entries.filter((e) => e.type === type).length;
149
+ }
150
+
151
+ /**
152
+ * @description
153
+ * Removes all registered subscribers. Useful for test teardown.
154
+ */
155
+ public clear(): void {
156
+ this.entries.splice(0, this.entries.length);
157
+ }
158
+ }
@@ -0,0 +1,95 @@
1
+ import { BrowserEventManager } from "./browser-event-manager";
2
+ import type { BaseEventManager } from "./event-manager";
3
+ import { ServerEventManager } from "./server-event-manager";
4
+
5
+ /**
6
+ * @description
7
+ * Internal cache for the resolved event manager instance.
8
+ */
9
+ let managerCache: BaseEventManager | null = null;
10
+
11
+ /**
12
+ * @description
13
+ * Provides a platform-aware mechanism for resolving the appropriate built-in
14
+ * event manager at runtime.
15
+ *
16
+ * `EventContext` detects whether the current environment is Node.js or a browser,
17
+ * then initialises and returns the corresponding singleton `BaseEventManager`:
18
+ * - Node.js / Bun / Deno → `ServerEventManager`
19
+ * - Browser → `BrowserEventManager`
20
+ *
21
+ * Detection uses `process.versions?.node` rather than just `process` to avoid
22
+ * false positives in bundled browser environments (Webpack/Vite) where `process`
23
+ * may be polyfilled.
24
+ *
25
+ * @remarks
26
+ * `EventContext` is an **optional, convenience utility** for the built-in event
27
+ * managers. Application code that uses `EventBus` or a custom `IEventBus`
28
+ * implementation does not need this at all — it exists only to simplify access
29
+ * to `BrowserEventManager` / `ServerEventManager` when you choose to use them.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * // Resolve the platform-appropriate manager once at startup
34
+ * const manager = EventContext.resolve();
35
+ * manager.subscribe('order:placed', handler);
36
+ *
37
+ * // After aggregate mutations and repository.save():
38
+ * for (const event of order.pullEvents()) {
39
+ * manager.dispatchEvent(event.type, event);
40
+ * }
41
+ * ```
42
+ */
43
+ export const EventContext = {
44
+ /**
45
+ * @description
46
+ * Resolves and returns the platform-appropriate `BaseEventManager` singleton.
47
+ *
48
+ * Detection order:
49
+ * 1. Node.js / Bun — detected via `process.versions?.node`
50
+ * 2. Browser — detected via `globalThis.window`
51
+ *
52
+ * The resolved instance is cached after first resolution. Call `reset()`
53
+ * in tests if you need a clean state between environment switches.
54
+ *
55
+ * @returns The resolved `BaseEventManager` instance.
56
+ * @throws {Error} If the runtime environment cannot be determined.
57
+ */
58
+ resolve(): BaseEventManager {
59
+ if (managerCache) return managerCache;
60
+
61
+ if (
62
+ typeof process !== "undefined" &&
63
+ process.versions?.node !== undefined
64
+ ) {
65
+ managerCache = ServerEventManager.instance();
66
+ return managerCache;
67
+ }
68
+
69
+ if (
70
+ typeof globalThis !== "undefined" &&
71
+ typeof globalThis.window !== "undefined"
72
+ ) {
73
+ managerCache = BrowserEventManager.instance(
74
+ globalThis.window as Window & typeof globalThis,
75
+ );
76
+ return managerCache;
77
+ }
78
+
79
+ throw new Error(
80
+ "EventContext: unable to determine the runtime environment. " +
81
+ "Neither a Node.js-compatible runtime nor a browser window was detected.",
82
+ );
83
+ },
84
+
85
+ /**
86
+ * @description
87
+ * Resets the cached event manager instance.
88
+ *
89
+ * Intended for use in tests where environment switching or clean state
90
+ * between test cases is required. Not recommended in production code.
91
+ */
92
+ __reset(): void {
93
+ managerCache = null;
94
+ },
95
+ };
@@ -0,0 +1,20 @@
1
+ import type { DomainEventPayload } from "../types/event.types";
2
+
3
+ /**
4
+ * @description
5
+ * Abstract base class for the built-in event manager implementations
6
+ * (`BrowserEventManager`, `ServerEventManager`).
7
+ *
8
+ * Application code that depends only on `IEventBus` does not need this class.
9
+ * It is exposed for library consumers who want to extend or replace the
10
+ * built-in transport layer.
11
+ */
12
+ export abstract class BaseEventManager {
13
+ abstract subscribe(
14
+ eventName: string,
15
+ fn: (event: DomainEventPayload) => void | Promise<void>,
16
+ ): void;
17
+ abstract exists(eventName: string): boolean;
18
+ abstract removeEvent(eventName: string): boolean;
19
+ abstract dispatchEvent(eventName: string, ...args: unknown[]): void;
20
+ }
@@ -0,0 +1,19 @@
1
+ import { DomainError } from "../helpers";
2
+
3
+ /**
4
+ * @description
5
+ * Validates that an event name follows the required `context:EventName` format.
6
+ *
7
+ * @param eventName The event name to validate.
8
+ * @throws {DomainError} If the event name does not contain a colon separator.
9
+ */
10
+ export const ValidateEventName = (eventName: string): void => {
11
+ if (!eventName.includes(":")) {
12
+ throw new DomainError(
13
+ `Invalid event name "${eventName}". ` +
14
+ 'Event names must follow the pattern "context:EventName" ' +
15
+ '(e.g., "order:placed", "user:registered").',
16
+ { context: "DomainEvent" },
17
+ );
18
+ }
19
+ };
@@ -0,0 +1,7 @@
1
+ export { BrowserEventManager, type WindowLike } from "./browser-event-manager";
2
+ export { BaseDomainEvent } from "./domain-event";
3
+ export { EventBus } from "./event-bus";
4
+ export { EventContext } from "./event-context";
5
+ export { BaseEventManager } from "./event-manager";
6
+ export { ValidateEventName } from "./event-utils";
7
+ export { ServerEventManager } from "./server-event-manager";