@cqrs-ddd/cqrs 0.5.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.
- package/COMMERCIAL_LICENSE.txt +34 -0
- package/LICENSE +661 -0
- package/README.md +39 -0
- package/dist/command.bus.d.ts +35 -0
- package/dist/command.bus.d.ts.map +1 -0
- package/dist/command.bus.js +43 -0
- package/dist/command.bus.js.map +1 -0
- package/dist/create-cqrs.d.ts +62 -0
- package/dist/create-cqrs.d.ts.map +1 -0
- package/dist/create-cqrs.js +54 -0
- package/dist/create-cqrs.js.map +1 -0
- package/dist/decorators.d.ts +73 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +104 -0
- package/dist/decorators.js.map +1 -0
- package/dist/dispatch.d.ts +31 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +67 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -0
- package/dist/event.bus.d.ts +79 -0
- package/dist/event.bus.d.ts.map +1 -0
- package/dist/event.bus.js +86 -0
- package/dist/event.bus.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces.d.ts +68 -0
- package/dist/interfaces.d.ts.map +1 -0
- package/dist/interfaces.js +3 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/query.bus.d.ts +35 -0
- package/dist/query.bus.d.ts.map +1 -0
- package/dist/query.bus.js +43 -0
- package/dist/query.bus.js.map +1 -0
- package/dist/registry.d.ts +38 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +92 -0
- package/dist/registry.js.map +1 -0
- package/dist/unhandled-exception.bus.d.ts +43 -0
- package/dist/unhandled-exception.bus.d.ts.map +1 -0
- package/dist/unhandled-exception.bus.js +56 -0
- package/dist/unhandled-exception.bus.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { PipelineLogger } from '@cqrs-ddd/pipeline';
|
|
2
|
+
import type { RequestType } from './decorators.js';
|
|
3
|
+
import { type Dispatch } from './dispatch.js';
|
|
4
|
+
import type { IEvent } from './interfaces.js';
|
|
5
|
+
import { type UnhandledExceptionBus } from './unhandled-exception.bus.js';
|
|
6
|
+
/** One handler of an event class: its name, for logs, and what runs it. */
|
|
7
|
+
export interface EventSubscriber {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly run: Dispatch;
|
|
10
|
+
}
|
|
11
|
+
/** How the {@link EventBus} treats a failed handler. */
|
|
12
|
+
export interface EventBusOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Throw a failure as an uncaught exception instead of publishing it on the
|
|
15
|
+
* `UnhandledExceptionBus` and logging it.
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
readonly rethrowUnhandled?: boolean;
|
|
19
|
+
/** Logs each failure at `error`. @default console */
|
|
20
|
+
readonly logger?: PipelineLogger;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Starts every handler of an event, each through its own pipeline, and returns without
|
|
24
|
+
* awaiting them. `createCqrs()` builds it; it satisfies `IDomainEventPublisher` of
|
|
25
|
+
* `@cqrs-ddd/core`, so `CommandBaseHandler` takes it as its event publisher.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* cqrs.eventBus.publish(new UserCreatedEvent(user.id));
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class EventBus<E extends IEvent = IEvent> {
|
|
33
|
+
private readonly handlers;
|
|
34
|
+
private readonly unhandled;
|
|
35
|
+
private readonly options;
|
|
36
|
+
private readonly running;
|
|
37
|
+
/**
|
|
38
|
+
* @param handlers - Maps each event class to its handlers. The bus reads it at every
|
|
39
|
+
* call, so it can be filled after the bus is created, as handlers that inject the bus
|
|
40
|
+
* require.
|
|
41
|
+
* @param unhandled - Receives the failures of handlers.
|
|
42
|
+
*/
|
|
43
|
+
constructor(handlers: ReadonlyMap<RequestType, readonly EventSubscriber[]>, unhandled: UnhandledExceptionBus, options?: EventBusOptions);
|
|
44
|
+
/**
|
|
45
|
+
* Starts the handlers of the event's class, or of its nearest parent class that has
|
|
46
|
+
* any; their synchronous part runs before `publish` returns. An event without handlers
|
|
47
|
+
* is dropped. A failure goes to the `UnhandledExceptionBus` and the logger, never to
|
|
48
|
+
* the caller.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* eventBus.publish(new UserCreatedEvent(user.id));
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
publish<T extends E>(event: T): void;
|
|
56
|
+
/**
|
|
57
|
+
* Publishes each event in order, as {@link publish} does.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* eventBus.publishAll(order.getUncommittedEvents());
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
publishAll<T extends E>(events: readonly T[]): void;
|
|
65
|
+
/**
|
|
66
|
+
* Resolves once every handler started so far has settled, including handlers those
|
|
67
|
+
* handlers start meanwhile. `app.close()` calls it, so a shutdown or a test ends
|
|
68
|
+
* after the work it caused.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* eventBus.publish(new UserCreatedEvent(user.id));
|
|
73
|
+
* await eventBus.drain();
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
drain(): Promise<void>;
|
|
77
|
+
private fail;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=event.bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.bus.d.ts","sourceRoot":"","sources":["../src/event.bus.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,KAAK,QAAQ,EAAW,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,8BAA8B,CAAC;AAEtC,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;CACxB;AAED,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC,qDAAqD;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC;AAED;;;;;;;;;GASG;AACH,qBAAa,QAAQ,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAU3C,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAIzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAd1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IAEpD;;;;;OAKG;IACH,YACmB,QAAQ,EAAE,WAAW,CACpC,WAAW,EACX,SAAS,eAAe,EAAE,CAC3B,EACgB,SAAS,EAAE,qBAAqB,EAChC,OAAO,GAAE,eAAoB,EAC5C;IAEJ;;;;;;;;;;OAUG;IACH,OAAO,CAAC,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAWnC;IAED;;;;;;;OAOG;IACH,UAAU,CAAC,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,GAAG,IAAI,CAElD;IAED;;;;;;;;;;OAUG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAED,OAAO,CAAC,IAAI;CAWb"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
import { nearest } from './dispatch.js';
|
|
3
|
+
import { raise, } from './unhandled-exception.bus.js';
|
|
4
|
+
/**
|
|
5
|
+
* Starts every handler of an event, each through its own pipeline, and returns without
|
|
6
|
+
* awaiting them. `createCqrs()` builds it; it satisfies `IDomainEventPublisher` of
|
|
7
|
+
* `@cqrs-ddd/core`, so `CommandBaseHandler` takes it as its event publisher.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* cqrs.eventBus.publish(new UserCreatedEvent(user.id));
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export class EventBus {
|
|
15
|
+
handlers;
|
|
16
|
+
unhandled;
|
|
17
|
+
options;
|
|
18
|
+
running = new Set();
|
|
19
|
+
/**
|
|
20
|
+
* @param handlers - Maps each event class to its handlers. The bus reads it at every
|
|
21
|
+
* call, so it can be filled after the bus is created, as handlers that inject the bus
|
|
22
|
+
* require.
|
|
23
|
+
* @param unhandled - Receives the failures of handlers.
|
|
24
|
+
*/
|
|
25
|
+
constructor(handlers, unhandled, options = {}) {
|
|
26
|
+
this.handlers = handlers;
|
|
27
|
+
this.unhandled = unhandled;
|
|
28
|
+
this.options = options;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Starts the handlers of the event's class, or of its nearest parent class that has
|
|
32
|
+
* any; their synchronous part runs before `publish` returns. An event without handlers
|
|
33
|
+
* is dropped. A failure goes to the `UnhandledExceptionBus` and the logger, never to
|
|
34
|
+
* the caller.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* eventBus.publish(new UserCreatedEvent(user.id));
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
publish(event) {
|
|
42
|
+
for (const { name, run } of nearest(this.handlers, event.constructor) ??
|
|
43
|
+
[]) {
|
|
44
|
+
const settled = run(event)
|
|
45
|
+
.then(() => undefined, (exception) => this.fail(name, event, exception))
|
|
46
|
+
.finally(() => this.running.delete(settled));
|
|
47
|
+
this.running.add(settled);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Publishes each event in order, as {@link publish} does.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* eventBus.publishAll(order.getUncommittedEvents());
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
publishAll(events) {
|
|
59
|
+
for (const event of events)
|
|
60
|
+
this.publish(event);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolves once every handler started so far has settled, including handlers those
|
|
64
|
+
* handlers start meanwhile. `app.close()` calls it, so a shutdown or a test ends
|
|
65
|
+
* after the work it caused.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* eventBus.publish(new UserCreatedEvent(user.id));
|
|
70
|
+
* await eventBus.drain();
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
async drain() {
|
|
74
|
+
while (this.running.size > 0)
|
|
75
|
+
await Promise.all(this.running);
|
|
76
|
+
}
|
|
77
|
+
fail(name, event, exception) {
|
|
78
|
+
if (this.options.rethrowUnhandled) {
|
|
79
|
+
raise(exception);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.unhandled.publish({ cause: event, exception });
|
|
83
|
+
(this.options.logger ?? console).error(`"${name}" has thrown an unhandled exception.`, exception);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=event.bus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.bus.js","sourceRoot":"","sources":["../src/event.bus.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAItE,OAAO,EAAiB,OAAO,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,EACL,KAAK,GAEN,MAAM,8BAA8B,CAAC;AAoBtC;;;;;;;;;GASG;AACH,MAAM,OAAO,QAAQ;IAUA,QAAQ;IAIR,SAAS;IACT,OAAO;IAdT,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IAEpD;;;;;OAKG;IACH,YACmB,QAGhB,EACgB,SAAgC,EAChC,OAAO,GAAoB,EAAE;wBAL7B,QAAQ;yBAIR,SAAS;uBACT,OAAO;IACvB,CAAC;IAEJ;;;;;;;;;;OAUG;IACH,OAAO,CAAc,KAAQ;QAC3B,KAAK,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,WAAW,CAAC;YACnE,EAAE,EAAE,CAAC;YACL,MAAM,OAAO,GAAkB,GAAG,CAAC,KAAK,CAAC;iBACtC,IAAI,CACH,GAAG,EAAE,CAAC,SAAS,EACf,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,CACjD;iBACA,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;YAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,UAAU,CAAc,MAAoB;QAC1C,KAAK,MAAM,KAAK,IAAI,MAAM;YAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC;YAAE,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChE,CAAC;IAEO,IAAI,CAAC,IAAY,EAAE,KAAQ,EAAE,SAAkB;QACrD,IAAI,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAClC,KAAK,CAAC,SAAS,CAAC,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QACpD,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,KAAK,CACpC,IAAI,IAAI,sCAAsC,EAC9C,SAAS,CACV,CAAC;IACJ,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { CommandBus } from './command.bus.js';
|
|
2
|
+
export { type Cqrs, type CqrsOptions, createCqrs, } from './create-cqrs.js';
|
|
3
|
+
export { CommandHandler, EventsHandler, QueryHandler, type RequestType, } from './decorators.js';
|
|
4
|
+
export type { Dispatch } from './dispatch.js';
|
|
5
|
+
export { CommandHandlerNotFoundException, InvalidCommandHandlerException, InvalidEventsHandlerException, InvalidQueryHandlerException, QueryHandlerNotFoundException, } from './errors.js';
|
|
6
|
+
export { EventBus, type EventBusOptions, type EventSubscriber, } from './event.bus.js';
|
|
7
|
+
export type { ICommand, ICommandHandler, IEvent, IEventHandler, IQuery, IQueryHandler, UnhandledExceptionInfo, } from './interfaces.js';
|
|
8
|
+
export { QueryBus } from './query.bus.js';
|
|
9
|
+
export { type Subscription, UnhandledExceptionBus, } from './unhandled-exception.bus.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,KAAK,IAAI,EACT,KAAK,WAAW,EAChB,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,cAAc,EACd,aAAa,EACb,YAAY,EACZ,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,EAC9B,6BAA6B,EAC7B,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,QAAQ,EACR,eAAe,EACf,MAAM,EACN,aAAa,EACb,MAAM,EACN,aAAa,EACb,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EACL,KAAK,YAAY,EACjB,qBAAqB,GACtB,MAAM,8BAA8B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
export { CommandBus } from './command.bus.js';
|
|
3
|
+
export { createCqrs, } from './create-cqrs.js';
|
|
4
|
+
export { CommandHandler, EventsHandler, QueryHandler, } from './decorators.js';
|
|
5
|
+
export { CommandHandlerNotFoundException, InvalidCommandHandlerException, InvalidEventsHandlerException, InvalidQueryHandlerException, QueryHandlerNotFoundException, } from './errors.js';
|
|
6
|
+
export { EventBus, } from './event.bus.js';
|
|
7
|
+
export { QueryBus } from './query.bus.js';
|
|
8
|
+
export { UnhandledExceptionBus, } from './unhandled-exception.bus.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAGL,UAAU,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,cAAc,EACd,aAAa,EACb,YAAY,GAEb,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,EAC9B,6BAA6B,EAC7B,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,QAAQ,GAGT,MAAM,gBAAgB,CAAC;AAUxB,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAEL,qBAAqB,GACtB,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** A command: a request to change state, handled by exactly one handler. */
|
|
2
|
+
export interface ICommand {
|
|
3
|
+
}
|
|
4
|
+
/** A query: a request to read, handled by exactly one handler. */
|
|
5
|
+
export interface IQuery {
|
|
6
|
+
}
|
|
7
|
+
/** An event: something that happened, handled by any number of handlers. */
|
|
8
|
+
export interface IEvent {
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Handles one command class, registered with `@CommandHandler(Command)`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* @CommandHandler(CreateUserCommand)
|
|
16
|
+
* class CreateUserHandler implements ICommandHandler<CreateUserCommand, string> {
|
|
17
|
+
* async execute(command: CreateUserCommand): Promise<string> {
|
|
18
|
+
* return users.create(command.name);
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export interface ICommandHandler<TCommand extends ICommand = ICommand, TResult = unknown> {
|
|
24
|
+
execute(command: TCommand): Promise<TResult>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Handles one query class, registered with `@QueryHandler(Query)`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* @QueryHandler(GetUserQuery)
|
|
32
|
+
* class GetUserHandler implements IQueryHandler<GetUserQuery, User | null> {
|
|
33
|
+
* async execute(query: GetUserQuery) {
|
|
34
|
+
* return users.find(query.id);
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export interface IQueryHandler<TQuery extends IQuery = IQuery, TResult = unknown> {
|
|
40
|
+
execute(query: TQuery): Promise<TResult>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Handles one or more event classes, registered with `@EventsHandler(...Events)`.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* @EventsHandler(UserCreatedEvent)
|
|
48
|
+
* class SendWelcomeEmail implements IEventHandler<UserCreatedEvent> {
|
|
49
|
+
* async handle(event: UserCreatedEvent) {
|
|
50
|
+
* await mailer.welcome(event.email);
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export interface IEventHandler<TEvent extends IEvent = IEvent> {
|
|
56
|
+
handle(event: TEvent): unknown;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* An event handler failure that nothing awaited, as published on the
|
|
60
|
+
* `UnhandledExceptionBus`.
|
|
61
|
+
*/
|
|
62
|
+
export interface UnhandledExceptionInfo<Cause = IEvent, Exception = unknown> {
|
|
63
|
+
/** What the handler threw. */
|
|
64
|
+
exception: Exception;
|
|
65
|
+
/** The event the handler was handling. */
|
|
66
|
+
cause: Cause;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=interfaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAE5E,MAAM,WAAW,QAAQ;CAAG;AAE5B,kEAAkE;AAElE,MAAM,WAAW,MAAM;CAAG;AAE1B,4EAA4E;AAE5E,MAAM,WAAW,MAAM;CAAG;AAE1B;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe,CAC9B,QAAQ,SAAS,QAAQ,GAAG,QAAQ,EACpC,OAAO,GAAG,OAAO;IAEjB,OAAO,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa,CAC5B,MAAM,SAAS,MAAM,GAAG,MAAM,EAC9B,OAAO,GAAG,OAAO;IAEjB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1C;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM;IAC3D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB,CAAC,KAAK,GAAG,MAAM,EAAE,SAAS,GAAG,OAAO;IACzE,8BAA8B;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,0CAA0C;IAC1C,KAAK,EAAE,KAAK,CAAC;CACd"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,sEAAsE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { RequestType } from './decorators.js';
|
|
2
|
+
import { type Dispatch } from './dispatch.js';
|
|
3
|
+
import type { IQuery } from './interfaces.js';
|
|
4
|
+
/**
|
|
5
|
+
* Sends a query to its one handler, through the handler's pipeline. `createCqrs()`
|
|
6
|
+
* builds it.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const { queryBus } = createCqrs();
|
|
11
|
+
* const user = await queryBus.execute<GetUserQuery, User | null>(new GetUserQuery('u-1'));
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare class QueryBus<Q extends IQuery = IQuery> {
|
|
15
|
+
private readonly handlers;
|
|
16
|
+
/**
|
|
17
|
+
* @param handlers - Maps each query class to the function that runs its handler. The
|
|
18
|
+
* bus reads it at every call, so it can be filled after the bus is created, as
|
|
19
|
+
* handlers that inject the bus require.
|
|
20
|
+
*/
|
|
21
|
+
constructor(handlers: ReadonlyMap<RequestType, Dispatch>);
|
|
22
|
+
/**
|
|
23
|
+
* Runs the handler of the query's class, or of its nearest parent class that has
|
|
24
|
+
* one, and resolves with what the handler returns, typed as `R`.
|
|
25
|
+
*
|
|
26
|
+
* @throws QueryHandlerNotFoundException (as a rejection) when no class in the
|
|
27
|
+
* query's chain has a handler.
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* await queryBus.execute(new GetUserQuery('u-1'));
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
execute<T extends Q, R = unknown>(query: T): Promise<R>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=query.bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.bus.d.ts","sourceRoot":"","sources":["../src/query.bus.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,KAAK,QAAQ,EAAW,MAAM,eAAe,CAAC;AAEvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE9C;;;;;;;;;GASG;AACH,qBAAa,QAAQ,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAMjC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IALrC;;;;OAIG;IACH,YAA6B,QAAQ,EAAE,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAI;IAE7E;;;;;;;;;;OAUG;IACG,OAAO,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAK5D;CACF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
import { nearest } from './dispatch.js';
|
|
3
|
+
import { QueryHandlerNotFoundException } from './errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Sends a query to its one handler, through the handler's pipeline. `createCqrs()`
|
|
6
|
+
* builds it.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const { queryBus } = createCqrs();
|
|
11
|
+
* const user = await queryBus.execute<GetUserQuery, User | null>(new GetUserQuery('u-1'));
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export class QueryBus {
|
|
15
|
+
handlers;
|
|
16
|
+
/**
|
|
17
|
+
* @param handlers - Maps each query class to the function that runs its handler. The
|
|
18
|
+
* bus reads it at every call, so it can be filled after the bus is created, as
|
|
19
|
+
* handlers that inject the bus require.
|
|
20
|
+
*/
|
|
21
|
+
constructor(handlers) {
|
|
22
|
+
this.handlers = handlers;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Runs the handler of the query's class, or of its nearest parent class that has
|
|
26
|
+
* one, and resolves with what the handler returns, typed as `R`.
|
|
27
|
+
*
|
|
28
|
+
* @throws QueryHandlerNotFoundException (as a rejection) when no class in the
|
|
29
|
+
* query's chain has a handler.
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* await queryBus.execute(new GetUserQuery('u-1'));
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
async execute(query) {
|
|
36
|
+
const type = query.constructor;
|
|
37
|
+
const run = nearest(this.handlers, type);
|
|
38
|
+
if (!run)
|
|
39
|
+
throw new QueryHandlerNotFoundException(type.name);
|
|
40
|
+
return (await run(query));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=query.bus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.bus.js","sourceRoot":"","sources":["../src/query.bus.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAGtE,OAAO,EAAiB,OAAO,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAG5D;;;;;;;;;GASG;AACH,MAAM,OAAO,QAAQ;IAMU,QAAQ;IALrC;;;;OAIG;IACH,YAA6B,QAA4C;wBAA5C,QAAQ;IAAuC,CAAC;IAE7E;;;;;;;;;;OAUG;IACH,KAAK,CAAC,OAAO,CAA2B,KAAQ;QAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC;QAC/B,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7D,OAAO,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,CAAM,CAAC;IACjC,CAAC;CACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Constructor, type ContextSources, type GlobalBehaviorsOptions, type IPipelineBehavior, type LogLevel, type PipelineLogger } from '@cqrs-ddd/pipeline';
|
|
2
|
+
import { type Dispatch, type PipelineSettings } from './dispatch.js';
|
|
3
|
+
import type { EventSubscriber } from './event.bus.js';
|
|
4
|
+
/** The dispatch tables the buses read, filled as handlers register. */
|
|
5
|
+
export declare class Handlers {
|
|
6
|
+
readonly commands: Map<import("@cqrs-ddd/pipeline").AnyClass, Dispatch>;
|
|
7
|
+
readonly queries: Map<import("@cqrs-ddd/pipeline").AnyClass, Dispatch>;
|
|
8
|
+
readonly events: Map<import("@cqrs-ddd/pipeline").AnyClass, EventSubscriber[]>;
|
|
9
|
+
}
|
|
10
|
+
/** How every handler's pipeline is built. */
|
|
11
|
+
export interface RegistrationOptions {
|
|
12
|
+
readonly globalBehaviors?: GlobalBehaviorsOptions | GlobalBehaviorsOptions[];
|
|
13
|
+
readonly sources?: ContextSources;
|
|
14
|
+
readonly diagnostics?: 'strict' | 'warn' | 'off';
|
|
15
|
+
readonly bootstrapLogLevel?: LogLevel | 'none';
|
|
16
|
+
}
|
|
17
|
+
/** A handler to register: its class, and what gives the instance per request. */
|
|
18
|
+
export interface HandlerEntry {
|
|
19
|
+
readonly type: Constructor;
|
|
20
|
+
readonly resolve: () => object;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The one instance of each behavior class: the instance given for that class, else
|
|
24
|
+
* `LoggingBehavior` on the logger, else one built with no arguments. The global behaviors
|
|
25
|
+
* are built here, so one that cannot be built fails at once.
|
|
26
|
+
*
|
|
27
|
+
* @throws TypeError on two given instances of one behavior class.
|
|
28
|
+
*/
|
|
29
|
+
export declare function behaviorResolver(given: readonly IPipelineBehavior[], globalBehaviors: RegistrationOptions['globalBehaviors'], logger: PipelineLogger): PipelineSettings['behavior'];
|
|
30
|
+
/**
|
|
31
|
+
* Compiles each handler's pipeline once and adds it to the dispatch tables. Contract
|
|
32
|
+
* violations throw a `PipelineConfigurationError` in `'strict'` mode, are logged in
|
|
33
|
+
* `'warn'` mode, and are not checked in `'off'` mode.
|
|
34
|
+
*
|
|
35
|
+
* @throws TypeError on a second command or query handler.
|
|
36
|
+
*/
|
|
37
|
+
export declare function registerHandlers(handlers: Handlers, entries: readonly HandlerEntry[], behavior: PipelineSettings['behavior'], options: RegistrationOptions, logger: PipelineLogger): void;
|
|
38
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAE3B,KAAK,iBAAiB,EAEtB,KAAK,QAAQ,EAGb,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,gBAAgB,EAEtB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,uEAAuE;AACvE,qBAAa,QAAQ;IACnB,QAAQ,CAAC,QAAQ,uDAAoC;IACrD,QAAQ,CAAC,OAAO,uDAAoC;IACpD,QAAQ,CAAC,MAAM,gEAA6C;CAC7D;AAED,6CAA6C;AAC7C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,eAAe,CAAC,EAAE,sBAAsB,GAAG,sBAAsB,EAAE,CAAC;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;IACjD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CAChD;AAED,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC;CAChC;AAID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,eAAe,EAAE,mBAAmB,CAAC,iBAAiB,CAAC,EACvD,MAAM,EAAE,cAAc,GACrB,gBAAgB,CAAC,UAAU,CAAC,CA2B9B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,EACtC,OAAO,EAAE,mBAAmB,EAC5B,MAAM,EAAE,cAAc,GACrB,IAAI,CAkCN"}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
import { behaviorEntryType, getBehaviorId, LoggingBehavior, PipelineConfigurationError, toGlobalConfigs, } from '@cqrs-ddd/pipeline';
|
|
3
|
+
import { commandOf, eventsOf, queryOf, } from './decorators.js';
|
|
4
|
+
import { toDispatch, } from './dispatch.js';
|
|
5
|
+
/** The dispatch tables the buses read, filled as handlers register. */
|
|
6
|
+
export class Handlers {
|
|
7
|
+
commands = new Map();
|
|
8
|
+
queries = new Map();
|
|
9
|
+
events = new Map();
|
|
10
|
+
}
|
|
11
|
+
const LOGGING_ID = getBehaviorId(LoggingBehavior);
|
|
12
|
+
/**
|
|
13
|
+
* The one instance of each behavior class: the instance given for that class, else
|
|
14
|
+
* `LoggingBehavior` on the logger, else one built with no arguments. The global behaviors
|
|
15
|
+
* are built here, so one that cannot be built fails at once.
|
|
16
|
+
*
|
|
17
|
+
* @throws TypeError on two given instances of one behavior class.
|
|
18
|
+
*/
|
|
19
|
+
export function behaviorResolver(given, globalBehaviors, logger) {
|
|
20
|
+
const instances = new Map();
|
|
21
|
+
for (const instance of given) {
|
|
22
|
+
const type = instance.constructor;
|
|
23
|
+
const id = getBehaviorId(type);
|
|
24
|
+
if (instances.has(id)) {
|
|
25
|
+
throw new TypeError(`createCqrs: two instances of ${type.name}; pass one per behavior class.`);
|
|
26
|
+
}
|
|
27
|
+
instances.set(id, instance);
|
|
28
|
+
}
|
|
29
|
+
const behavior = (type) => {
|
|
30
|
+
const id = getBehaviorId(type);
|
|
31
|
+
let instance = instances.get(id);
|
|
32
|
+
if (!instance) {
|
|
33
|
+
instance = id === LOGGING_ID ? new LoggingBehavior(logger) : new type();
|
|
34
|
+
instances.set(id, instance);
|
|
35
|
+
}
|
|
36
|
+
return instance;
|
|
37
|
+
};
|
|
38
|
+
for (const config of toGlobalConfigs(globalBehaviors)) {
|
|
39
|
+
for (const entry of [...(config.before ?? []), ...(config.after ?? [])]) {
|
|
40
|
+
behavior(behaviorEntryType(entry, 'globalBehaviors'));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return behavior;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Compiles each handler's pipeline once and adds it to the dispatch tables. Contract
|
|
47
|
+
* violations throw a `PipelineConfigurationError` in `'strict'` mode, are logged in
|
|
48
|
+
* `'warn'` mode, and are not checked in `'off'` mode.
|
|
49
|
+
*
|
|
50
|
+
* @throws TypeError on a second command or query handler.
|
|
51
|
+
*/
|
|
52
|
+
export function registerHandlers(handlers, entries, behavior, options, logger) {
|
|
53
|
+
const mode = options.diagnostics ?? 'strict';
|
|
54
|
+
const level = options.bootstrapLogLevel ?? 'debug';
|
|
55
|
+
const found = [];
|
|
56
|
+
const settings = {
|
|
57
|
+
globalBehaviors: options.globalBehaviors,
|
|
58
|
+
sources: options.sources,
|
|
59
|
+
behavior,
|
|
60
|
+
diagnostics: mode === 'off' ? undefined : found,
|
|
61
|
+
log: level === 'none' ? undefined : (message) => logger[level]?.(message),
|
|
62
|
+
};
|
|
63
|
+
for (const { type, resolve } of entries) {
|
|
64
|
+
const command = commandOf(type);
|
|
65
|
+
const query = queryOf(type);
|
|
66
|
+
const events = eventsOf(type);
|
|
67
|
+
if (command)
|
|
68
|
+
bind(handlers.commands, 'command', command, type, resolve, settings);
|
|
69
|
+
if (query)
|
|
70
|
+
bind(handlers.queries, 'query', query, type, resolve, settings);
|
|
71
|
+
if (events) {
|
|
72
|
+
const run = toDispatch(type, resolve, 'event', settings);
|
|
73
|
+
for (const event of events) {
|
|
74
|
+
const subscribers = handlers.events.get(event) ?? [];
|
|
75
|
+
handlers.events.set(event, [...subscribers, { name: type.name, run }]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (found.length > 0 && mode === 'strict') {
|
|
80
|
+
throw new PipelineConfigurationError(found);
|
|
81
|
+
}
|
|
82
|
+
for (const d of found) {
|
|
83
|
+
logger.warn(`[Pipeline Diagnostic] Handler '${d.handlerName}' with behavior '${d.behaviorName}': ${d.message}. Fix: ${d.fix}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function bind(table, kind, request, type, resolve, settings) {
|
|
87
|
+
if (table.has(request)) {
|
|
88
|
+
throw new TypeError(`${request.name} has a second ${kind} handler, ${type.name}; a ${kind} has exactly one.`);
|
|
89
|
+
}
|
|
90
|
+
table.set(request, toDispatch(type, resolve, kind, settings));
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { IEvent, UnhandledExceptionInfo } from './interfaces.js';
|
|
2
|
+
/** Ends a subscription to the {@link UnhandledExceptionBus}. */
|
|
3
|
+
export interface Subscription {
|
|
4
|
+
unsubscribe(): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Receives the failures of event handlers, which nothing awaits. `EventBus` publishes
|
|
8
|
+
* each one here before it logs it.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const subscription = cqrs.unhandledExceptionBus.subscribe(({ cause, exception }) =>
|
|
13
|
+
* alerts.notify(cause.constructor.name, exception),
|
|
14
|
+
* );
|
|
15
|
+
* subscription.unsubscribe();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare class UnhandledExceptionBus<Cause = IEvent> {
|
|
19
|
+
private readonly listeners;
|
|
20
|
+
/**
|
|
21
|
+
* Calls `next` with every failure published from now on, until the subscription ends.
|
|
22
|
+
* A listener that throws does not stop the others; its error is thrown on a later
|
|
23
|
+
* tick, as an uncaught exception.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const subscription = unhandledExceptionBus.subscribe((info) => logger.error(info.exception));
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
subscribe(next: (info: UnhandledExceptionInfo<Cause>) => void): Subscription;
|
|
31
|
+
/**
|
|
32
|
+
* Delivers a failure to every current subscriber, synchronously.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* unhandledExceptionBus.publish({ cause: event, exception: error });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
publish(info: UnhandledExceptionInfo<Cause>): void;
|
|
40
|
+
}
|
|
41
|
+
/** Throws `error` on a later tick, where nothing can catch it. */
|
|
42
|
+
export declare function raise(error: unknown): void;
|
|
43
|
+
//# sourceMappingURL=unhandled-exception.bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unhandled-exception.bus.d.ts","sourceRoot":"","sources":["../src/unhandled-exception.bus.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEtE,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,WAAW,IAAI,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,qBAAqB,CAAC,KAAK,GAAG,MAAM;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAEtB;IAEJ;;;;;;;;;OASG;IACH,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,sBAAsB,CAAC,KAAK,CAAC,KAAK,IAAI,GAAG,YAAY,CAI3E;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,IAAI,EAAE,sBAAsB,CAAC,KAAK,CAAC,GAAG,IAAI,CAQjD;CACF;AAED,kEAAkE;AAClE,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAI1C"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
/**
|
|
3
|
+
* Receives the failures of event handlers, which nothing awaits. `EventBus` publishes
|
|
4
|
+
* each one here before it logs it.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const subscription = cqrs.unhandledExceptionBus.subscribe(({ cause, exception }) =>
|
|
9
|
+
* alerts.notify(cause.constructor.name, exception),
|
|
10
|
+
* );
|
|
11
|
+
* subscription.unsubscribe();
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export class UnhandledExceptionBus {
|
|
15
|
+
listeners = new Set();
|
|
16
|
+
/**
|
|
17
|
+
* Calls `next` with every failure published from now on, until the subscription ends.
|
|
18
|
+
* A listener that throws does not stop the others; its error is thrown on a later
|
|
19
|
+
* tick, as an uncaught exception.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const subscription = unhandledExceptionBus.subscribe((info) => logger.error(info.exception));
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
subscribe(next) {
|
|
27
|
+
const listener = (info) => next(info);
|
|
28
|
+
this.listeners.add(listener);
|
|
29
|
+
return { unsubscribe: () => this.listeners.delete(listener) };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Delivers a failure to every current subscriber, synchronously.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* unhandledExceptionBus.publish({ cause: event, exception: error });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
publish(info) {
|
|
40
|
+
for (const listener of [...this.listeners]) {
|
|
41
|
+
try {
|
|
42
|
+
listener(info);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
raise(error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Throws `error` on a later tick, where nothing can catch it. */
|
|
51
|
+
export function raise(error) {
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
throw error;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=unhandled-exception.bus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unhandled-exception.bus.js","sourceRoot":"","sources":["../src/unhandled-exception.bus.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAStE;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,qBAAqB;IACf,SAAS,GAAG,IAAI,GAAG,EAEjC,CAAC;IAEJ;;;;;;;;;OASG;IACH,SAAS,CAAC,IAAmD;QAC3D,MAAM,QAAQ,GAAG,CAAC,IAAmC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;IAChE,CAAC;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,IAAmC;QACzC,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3C,IAAI,CAAC;gBACH,QAAQ,CAAC,IAAI,CAAC,CAAC;YACjB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,KAAK,CAAC,KAAK,CAAC,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED,kEAAkE;AAClE,MAAM,UAAU,KAAK,CAAC,KAAc;IAClC,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC"}
|