@zudojs/cqrs 0.1.0 → 1.0.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/LICENSE +21 -0
- package/README.md +88 -7
- package/dist/command/command.core.d.ts +5 -2
- package/dist/command/command.core.js +11 -7
- package/dist/command/commandBus.core.d.ts +20 -6
- package/dist/command/commandBus.core.js +47 -62
- package/dist/command/commandHandler.core.d.ts +9 -3
- package/dist/command/commandHandler.core.js +23 -6
- package/dist/cqrsContext/cqrsContext.core.d.ts +11 -0
- package/dist/cqrsContext/cqrsContext.core.js +25 -7
- package/dist/cqrsDecorators/cqrsDecorators.core.d.ts +14 -8
- package/dist/cqrsDecorators/cqrsDecorators.core.js +79 -60
- package/dist/cqrsErrors/cqrsError.base.d.ts +34 -2
- package/dist/cqrsErrors/cqrsError.base.js +58 -5
- package/dist/cqrsEvents/cqrsEvents.type.d.ts +13 -6
- package/dist/cqrsEvents/cqrsEvents.type.js +11 -1
- package/dist/cqrsMiddleware/cqrsMiddleware.core.d.ts +95 -7
- package/dist/cqrsMiddleware/cqrsMiddleware.core.js +97 -29
- package/dist/cqrsMiddleware/index.d.ts +2 -1
- package/dist/cqrsMiddleware/index.js +2 -1
- package/dist/cqrsTypes/cqrsTypes.type.d.ts +17 -30
- package/dist/cqrsTypes/cqrsTypes.type.js +0 -16
- package/dist/cqrsValidation/cqrsValidation.core.d.ts +32 -0
- package/dist/cqrsValidation/cqrsValidation.core.js +53 -0
- package/dist/cqrsValidation/index.d.ts +7 -0
- package/dist/cqrsValidation/index.js +7 -0
- package/dist/handlerRegistry/handlerRegistry.core.d.ts +5 -0
- package/dist/handlerRegistry/handlerRegistry.core.js +28 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -2
- package/dist/query/query.core.d.ts +5 -2
- package/dist/query/query.core.js +11 -7
- package/dist/query/queryBus.core.d.ts +20 -6
- package/dist/query/queryBus.core.js +47 -62
- package/dist/query/queryHandler.core.d.ts +9 -3
- package/dist/query/queryHandler.core.js +23 -6
- package/package.json +22 -14
- package/dist/command/command.core.d.ts.map +0 -1
- package/dist/command/command.core.js.map +0 -1
- package/dist/command/commandBus.core.d.ts.map +0 -1
- package/dist/command/commandBus.core.js.map +0 -1
- package/dist/command/commandHandler.core.d.ts.map +0 -1
- package/dist/command/commandHandler.core.js.map +0 -1
- package/dist/command/commandResult.type.d.ts.map +0 -1
- package/dist/command/commandResult.type.js.map +0 -1
- package/dist/command/index.d.ts.map +0 -1
- package/dist/command/index.js.map +0 -1
- package/dist/cqrsContext/cqrsContext.core.d.ts.map +0 -1
- package/dist/cqrsContext/cqrsContext.core.js.map +0 -1
- package/dist/cqrsContext/index.d.ts.map +0 -1
- package/dist/cqrsContext/index.js.map +0 -1
- package/dist/cqrsDecorators/cqrsDecorators.core.d.ts.map +0 -1
- package/dist/cqrsDecorators/cqrsDecorators.core.js.map +0 -1
- package/dist/cqrsDecorators/index.d.ts.map +0 -1
- package/dist/cqrsDecorators/index.js.map +0 -1
- package/dist/cqrsErrors/cqrsError.base.d.ts.map +0 -1
- package/dist/cqrsErrors/cqrsError.base.js.map +0 -1
- package/dist/cqrsErrors/index.d.ts.map +0 -1
- package/dist/cqrsErrors/index.js.map +0 -1
- package/dist/cqrsEvents/cqrsEventBus.core.d.ts.map +0 -1
- package/dist/cqrsEvents/cqrsEventBus.core.js.map +0 -1
- package/dist/cqrsEvents/cqrsEventResult.type.d.ts.map +0 -1
- package/dist/cqrsEvents/cqrsEventResult.type.js.map +0 -1
- package/dist/cqrsEvents/cqrsEvents.type.d.ts.map +0 -1
- package/dist/cqrsEvents/cqrsEvents.type.js.map +0 -1
- package/dist/cqrsEvents/index.d.ts.map +0 -1
- package/dist/cqrsEvents/index.js.map +0 -1
- package/dist/cqrsMiddleware/cqrsMiddleware.core.d.ts.map +0 -1
- package/dist/cqrsMiddleware/cqrsMiddleware.core.js.map +0 -1
- package/dist/cqrsMiddleware/index.d.ts.map +0 -1
- package/dist/cqrsMiddleware/index.js.map +0 -1
- package/dist/cqrsTypes/cqrsTypes.type.d.ts.map +0 -1
- package/dist/cqrsTypes/cqrsTypes.type.js.map +0 -1
- package/dist/cqrsTypes/index.d.ts.map +0 -1
- package/dist/cqrsTypes/index.js.map +0 -1
- package/dist/handlerRegistry/handlerRegistry.core.d.ts.map +0 -1
- package/dist/handlerRegistry/handlerRegistry.core.js.map +0 -1
- package/dist/handlerRegistry/index.d.ts.map +0 -1
- package/dist/handlerRegistry/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/query/index.d.ts.map +0 -1
- package/dist/query/index.js.map +0 -1
- package/dist/query/query.core.d.ts.map +0 -1
- package/dist/query/query.core.js.map +0 -1
- package/dist/query/queryBus.core.d.ts.map +0 -1
- package/dist/query/queryBus.core.js.map +0 -1
- package/dist/query/queryHandler.core.d.ts.map +0 -1
- package/dist/query/queryHandler.core.js.map +0 -1
- package/dist/query/queryResult.type.d.ts.map +0 -1
- package/dist/query/queryResult.type.js.map +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { HandlerConfigurationError } from "../cqrsErrors/cqrsError.base.js";
|
|
2
|
+
import { assertHandlerType } from "../cqrsValidation/cqrsValidation.core.js";
|
|
1
3
|
/**
|
|
2
4
|
* Metadata key used to identify CQRS handler configuration.
|
|
3
5
|
*/
|
|
@@ -28,24 +30,17 @@ export function CqrsHandler(kind, type) {
|
|
|
28
30
|
kind,
|
|
29
31
|
type,
|
|
30
32
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
enumerable: false,
|
|
34
|
-
writable: false,
|
|
35
|
-
value: metadata,
|
|
36
|
-
});
|
|
37
|
-
Object.defineProperty(constructor, CQRS_TYPE_METADATA, {
|
|
38
|
-
configurable: false,
|
|
39
|
-
enumerable: false,
|
|
40
|
-
writable: false,
|
|
41
|
-
value: type,
|
|
42
|
-
});
|
|
33
|
+
defineMetadata(constructor, CQRS_HANDLER_METADATA, metadata);
|
|
34
|
+
defineMetadata(constructor, CQRS_TYPE_METADATA, type);
|
|
43
35
|
};
|
|
44
36
|
}
|
|
45
37
|
/**
|
|
46
|
-
* Marks a class as
|
|
38
|
+
* Marks a class as the command handler for `type`.
|
|
39
|
+
*
|
|
40
|
+
* Named `CommandHandlerFor` so it does not collide with the abstract
|
|
41
|
+
* `CommandHandler` class exported from the same package.
|
|
47
42
|
*/
|
|
48
|
-
export function
|
|
43
|
+
export function CommandHandlerFor(type) {
|
|
49
44
|
validateHandlerMetadata("command", type);
|
|
50
45
|
return (target) => {
|
|
51
46
|
const constructor = target;
|
|
@@ -53,30 +48,18 @@ export function CommandHandler(type) {
|
|
|
53
48
|
kind: "command",
|
|
54
49
|
type,
|
|
55
50
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
writable: false,
|
|
60
|
-
value: metadata,
|
|
61
|
-
});
|
|
62
|
-
Object.defineProperty(constructor, CQRS_HANDLER_METADATA, {
|
|
63
|
-
configurable: false,
|
|
64
|
-
enumerable: false,
|
|
65
|
-
writable: false,
|
|
66
|
-
value: metadata,
|
|
67
|
-
});
|
|
68
|
-
Object.defineProperty(constructor, CQRS_TYPE_METADATA, {
|
|
69
|
-
configurable: false,
|
|
70
|
-
enumerable: false,
|
|
71
|
-
writable: false,
|
|
72
|
-
value: type,
|
|
73
|
-
});
|
|
51
|
+
defineMetadata(constructor, COMMAND_HANDLER_METADATA, metadata);
|
|
52
|
+
defineMetadata(constructor, CQRS_HANDLER_METADATA, metadata);
|
|
53
|
+
defineMetadata(constructor, CQRS_TYPE_METADATA, type);
|
|
74
54
|
};
|
|
75
55
|
}
|
|
76
56
|
/**
|
|
77
|
-
* Marks a class as
|
|
57
|
+
* Marks a class as the query handler for `type`.
|
|
58
|
+
*
|
|
59
|
+
* Named `QueryHandlerFor` so it does not collide with the abstract
|
|
60
|
+
* `QueryHandler` class exported from the same package.
|
|
78
61
|
*/
|
|
79
|
-
export function
|
|
62
|
+
export function QueryHandlerFor(type) {
|
|
80
63
|
validateHandlerMetadata("query", type);
|
|
81
64
|
return (target) => {
|
|
82
65
|
const constructor = target;
|
|
@@ -84,24 +67,9 @@ export function QueryHandler(type) {
|
|
|
84
67
|
kind: "query",
|
|
85
68
|
type,
|
|
86
69
|
});
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
writable: false,
|
|
91
|
-
value: metadata,
|
|
92
|
-
});
|
|
93
|
-
Object.defineProperty(constructor, CQRS_HANDLER_METADATA, {
|
|
94
|
-
configurable: false,
|
|
95
|
-
enumerable: false,
|
|
96
|
-
writable: false,
|
|
97
|
-
value: metadata,
|
|
98
|
-
});
|
|
99
|
-
Object.defineProperty(constructor, CQRS_TYPE_METADATA, {
|
|
100
|
-
configurable: false,
|
|
101
|
-
enumerable: false,
|
|
102
|
-
writable: false,
|
|
103
|
-
value: type,
|
|
104
|
-
});
|
|
70
|
+
defineMetadata(constructor, QUERY_HANDLER_METADATA, metadata);
|
|
71
|
+
defineMetadata(constructor, CQRS_HANDLER_METADATA, metadata);
|
|
72
|
+
defineMetadata(constructor, CQRS_TYPE_METADATA, type);
|
|
105
73
|
};
|
|
106
74
|
}
|
|
107
75
|
/**
|
|
@@ -147,35 +115,86 @@ export function isCqrsHandler(target) {
|
|
|
147
115
|
return getCqrsHandlerMetadata(target) !== undefined;
|
|
148
116
|
}
|
|
149
117
|
/**
|
|
150
|
-
* Determines whether a class is decorated
|
|
118
|
+
* Determines whether a class is decorated with `CommandHandlerFor`.
|
|
151
119
|
*/
|
|
152
|
-
export function
|
|
120
|
+
export function isDecoratedCommandHandler(target) {
|
|
153
121
|
return getCommandHandlerMetadata(target) !== undefined;
|
|
154
122
|
}
|
|
155
123
|
/**
|
|
156
|
-
* Determines whether a class is decorated
|
|
124
|
+
* Determines whether a class is decorated with `QueryHandlerFor`.
|
|
157
125
|
*/
|
|
158
|
-
export function
|
|
126
|
+
export function isDecoratedQueryHandler(target) {
|
|
159
127
|
return getQueryHandlerMetadata(target) !== undefined;
|
|
160
128
|
}
|
|
161
129
|
/**
|
|
162
130
|
* Creates a reusable command handler decorator.
|
|
163
131
|
*/
|
|
164
132
|
export function createCommandHandlerDecorator(type) {
|
|
165
|
-
return
|
|
133
|
+
return CommandHandlerFor(type);
|
|
166
134
|
}
|
|
167
135
|
/**
|
|
168
136
|
* Creates a reusable query handler decorator.
|
|
169
137
|
*/
|
|
170
138
|
export function createQueryHandlerDecorator(type) {
|
|
171
|
-
return
|
|
139
|
+
return QueryHandlerFor(type);
|
|
172
140
|
}
|
|
173
141
|
/**
|
|
174
142
|
* Validates decorator arguments.
|
|
175
143
|
*/
|
|
176
144
|
function validateHandlerMetadata(kind, type) {
|
|
177
|
-
|
|
178
|
-
|
|
145
|
+
assertHandlerType(kind, type);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Defines immutable metadata on a constructor.
|
|
149
|
+
*
|
|
150
|
+
* Re-applying a decorator that carries identical metadata is a no-op, so
|
|
151
|
+
* stacking `CqrsHandler("command", "A")` with `CommandHandlerFor("A")`
|
|
152
|
+
* works. Conflicting metadata throws `HandlerConfigurationError`.
|
|
153
|
+
*/
|
|
154
|
+
function defineMetadata(constructor, key, value) {
|
|
155
|
+
if (Object.prototype.hasOwnProperty.call(constructor, key)) {
|
|
156
|
+
const existing = constructor[key];
|
|
157
|
+
if (isSameMetadata(existing, value)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
throw new HandlerConfigurationError(`Class "${constructor.name || "<anonymous>"}" already carries conflicting CQRS handler metadata (${describeMetadata(existing)} vs ${describeMetadata(value)}).`, {
|
|
161
|
+
className: constructor.name,
|
|
162
|
+
existing: describeMetadata(existing),
|
|
163
|
+
incoming: describeMetadata(value),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
Object.defineProperty(constructor, key, {
|
|
167
|
+
configurable: false,
|
|
168
|
+
enumerable: false,
|
|
169
|
+
writable: false,
|
|
170
|
+
value,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Compares two metadata values structurally.
|
|
175
|
+
*/
|
|
176
|
+
function isSameMetadata(existing, incoming) {
|
|
177
|
+
if (typeof incoming === "string") {
|
|
178
|
+
return existing === incoming;
|
|
179
|
+
}
|
|
180
|
+
if (typeof existing !== "object" || existing === null) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const current = existing;
|
|
184
|
+
return (current.kind === incoming.kind &&
|
|
185
|
+
current.type === incoming.type);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Renders metadata for error messages.
|
|
189
|
+
*/
|
|
190
|
+
function describeMetadata(value) {
|
|
191
|
+
if (typeof value === "string") {
|
|
192
|
+
return `"${value}"`;
|
|
193
|
+
}
|
|
194
|
+
if (typeof value === "object" && value !== null) {
|
|
195
|
+
const metadata = value;
|
|
196
|
+
return `${String(metadata.kind)}:"${String(metadata.type)}"`;
|
|
179
197
|
}
|
|
198
|
+
return String(value);
|
|
180
199
|
}
|
|
181
200
|
//# sourceMappingURL=cqrsDecorators.core.js.map
|
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
import { BaseError, type ErrorMetadata } from "@zudojs/errors";
|
|
1
|
+
import { BaseError, ErrorCode, type BaseErrorOptions, type ErrorMetadata } from "@zudojs/errors";
|
|
2
2
|
/**
|
|
3
3
|
* Base error for failures originating from the CQRS package.
|
|
4
4
|
*/
|
|
5
5
|
export declare class CqrsError extends BaseError {
|
|
6
|
-
constructor(message: string, options?:
|
|
6
|
+
constructor(message: string, options?: BaseErrorOptions);
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Thrown when a CQRS request is invalid.
|
|
10
|
+
*
|
|
11
|
+
* The error code defaults to `ERR_INVALID_INPUT`; request-specific
|
|
12
|
+
* subclasses (`InvalidCommandError`, `InvalidQueryError`) narrow it.
|
|
10
13
|
*/
|
|
11
14
|
export declare class CqrsValidationError extends CqrsError {
|
|
15
|
+
constructor(message?: string, metadata?: ErrorMetadata, code?: ErrorCode | string);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Thrown by the command bus when a command is malformed
|
|
19
|
+
* (not an object, or missing a non-empty `type`).
|
|
20
|
+
*/
|
|
21
|
+
export declare class InvalidCommandError extends CqrsValidationError {
|
|
22
|
+
constructor(message?: string, metadata?: ErrorMetadata);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Thrown by the query bus when a query is malformed
|
|
26
|
+
* (not an object, or missing a non-empty `type`).
|
|
27
|
+
*/
|
|
28
|
+
export declare class InvalidQueryError extends CqrsValidationError {
|
|
12
29
|
constructor(message?: string, metadata?: ErrorMetadata);
|
|
13
30
|
}
|
|
14
31
|
/**
|
|
@@ -56,6 +73,13 @@ export declare class InvalidHandlerTypeError extends CqrsValidationError {
|
|
|
56
73
|
readonly handlerKind: "command" | "query" | "event";
|
|
57
74
|
constructor(handlerKind: "command" | "query" | "event", handlerType: unknown);
|
|
58
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Thrown when a registry operation receives an unsupported handler kind.
|
|
78
|
+
*/
|
|
79
|
+
export declare class InvalidHandlerKindError extends CqrsValidationError {
|
|
80
|
+
readonly handlerKind: string;
|
|
81
|
+
constructor(handlerKind: unknown);
|
|
82
|
+
}
|
|
59
83
|
/**
|
|
60
84
|
* Thrown when CQRS middleware is invalid.
|
|
61
85
|
*/
|
|
@@ -80,6 +104,14 @@ export declare class HandlerConfigurationError extends CqrsError {
|
|
|
80
104
|
export declare function isCqrsError(error: unknown): error is CqrsError;
|
|
81
105
|
/**
|
|
82
106
|
* Converts an unknown error into a CQRS error.
|
|
107
|
+
*
|
|
108
|
+
* - `CqrsError` instances are returned unchanged.
|
|
109
|
+
* - Other `BaseError` instances keep their code, status, category,
|
|
110
|
+
* severity, exposure and operational flags (and metadata).
|
|
111
|
+
* - Plain `Error` instances become a non-operational 500 `CqrsError`
|
|
112
|
+
* using the original message.
|
|
113
|
+
* - Anything else becomes a non-operational 500 `CqrsError` using the
|
|
114
|
+
* supplied `message`; the original value is attached as `cause`.
|
|
83
115
|
*/
|
|
84
116
|
export declare function toCqrsError(error: unknown, message?: string): CqrsError;
|
|
85
117
|
//# sourceMappingURL=cqrsError.base.d.ts.map
|
|
@@ -5,32 +5,53 @@ import { BaseError, ErrorCategory, ErrorCode, ErrorSeverity, } from "@zudojs/err
|
|
|
5
5
|
export class CqrsError extends BaseError {
|
|
6
6
|
constructor(message, options = {}) {
|
|
7
7
|
super(message, {
|
|
8
|
+
...options,
|
|
8
9
|
code: options.code ?? ErrorCode.INTERNAL_ERROR,
|
|
9
10
|
category: options.category ?? ErrorCategory.SYSTEM,
|
|
10
11
|
severity: options.severity ?? ErrorSeverity.ERROR,
|
|
11
12
|
statusCode: options.statusCode ?? 500,
|
|
12
13
|
expose: options.expose ?? false,
|
|
13
14
|
isOperational: options.isOperational ?? true,
|
|
14
|
-
...options,
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Thrown when a CQRS request is invalid.
|
|
20
|
+
*
|
|
21
|
+
* The error code defaults to `ERR_INVALID_INPUT`; request-specific
|
|
22
|
+
* subclasses (`InvalidCommandError`, `InvalidQueryError`) narrow it.
|
|
20
23
|
*/
|
|
21
24
|
export class CqrsValidationError extends CqrsError {
|
|
22
|
-
constructor(message = "The CQRS request is invalid.", metadata) {
|
|
25
|
+
constructor(message = "The CQRS request is invalid.", metadata, code = ErrorCode.INVALID_INPUT) {
|
|
23
26
|
super(message, {
|
|
24
|
-
code
|
|
27
|
+
code,
|
|
25
28
|
category: ErrorCategory.VALIDATION,
|
|
26
29
|
severity: ErrorSeverity.WARNING,
|
|
27
30
|
statusCode: 400,
|
|
28
31
|
expose: true,
|
|
29
32
|
isOperational: true,
|
|
30
|
-
metadata
|
|
33
|
+
metadata,
|
|
31
34
|
});
|
|
32
35
|
}
|
|
33
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Thrown by the command bus when a command is malformed
|
|
39
|
+
* (not an object, or missing a non-empty `type`).
|
|
40
|
+
*/
|
|
41
|
+
export class InvalidCommandError extends CqrsValidationError {
|
|
42
|
+
constructor(message = "The command is invalid.", metadata) {
|
|
43
|
+
super(message, metadata, ErrorCode.INVALID_COMMAND);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Thrown by the query bus when a query is malformed
|
|
48
|
+
* (not an object, or missing a non-empty `type`).
|
|
49
|
+
*/
|
|
50
|
+
export class InvalidQueryError extends CqrsValidationError {
|
|
51
|
+
constructor(message = "The query is invalid.", metadata) {
|
|
52
|
+
super(message, metadata, ErrorCode.INVALID_QUERY);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
34
55
|
/**
|
|
35
56
|
* Thrown when a command handler cannot be resolved.
|
|
36
57
|
*/
|
|
@@ -155,6 +176,18 @@ export class InvalidHandlerTypeError extends CqrsValidationError {
|
|
|
155
176
|
this.handlerKind = handlerKind;
|
|
156
177
|
}
|
|
157
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Thrown when a registry operation receives an unsupported handler kind.
|
|
181
|
+
*/
|
|
182
|
+
export class InvalidHandlerKindError extends CqrsValidationError {
|
|
183
|
+
handlerKind;
|
|
184
|
+
constructor(handlerKind) {
|
|
185
|
+
super(`Unsupported handler kind "${String(handlerKind)}". Expected "command" or "query".`, {
|
|
186
|
+
handlerKind: String(handlerKind),
|
|
187
|
+
});
|
|
188
|
+
this.handlerKind = String(handlerKind);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
158
191
|
/**
|
|
159
192
|
* Thrown when CQRS middleware is invalid.
|
|
160
193
|
*/
|
|
@@ -191,7 +224,7 @@ export class HandlerConfigurationError extends CqrsError {
|
|
|
191
224
|
statusCode: 500,
|
|
192
225
|
expose: false,
|
|
193
226
|
isOperational: true,
|
|
194
|
-
metadata
|
|
227
|
+
metadata,
|
|
195
228
|
});
|
|
196
229
|
}
|
|
197
230
|
}
|
|
@@ -203,11 +236,31 @@ export function isCqrsError(error) {
|
|
|
203
236
|
}
|
|
204
237
|
/**
|
|
205
238
|
* Converts an unknown error into a CQRS error.
|
|
239
|
+
*
|
|
240
|
+
* - `CqrsError` instances are returned unchanged.
|
|
241
|
+
* - Other `BaseError` instances keep their code, status, category,
|
|
242
|
+
* severity, exposure and operational flags (and metadata).
|
|
243
|
+
* - Plain `Error` instances become a non-operational 500 `CqrsError`
|
|
244
|
+
* using the original message.
|
|
245
|
+
* - Anything else becomes a non-operational 500 `CqrsError` using the
|
|
246
|
+
* supplied `message`; the original value is attached as `cause`.
|
|
206
247
|
*/
|
|
207
248
|
export function toCqrsError(error, message = "CQRS execution failed.") {
|
|
208
249
|
if (error instanceof CqrsError) {
|
|
209
250
|
return error;
|
|
210
251
|
}
|
|
252
|
+
if (error instanceof BaseError) {
|
|
253
|
+
return new CqrsError(error.message, {
|
|
254
|
+
code: error.code,
|
|
255
|
+
category: error.category,
|
|
256
|
+
severity: error.severity,
|
|
257
|
+
statusCode: error.statusCode,
|
|
258
|
+
expose: error.expose,
|
|
259
|
+
isOperational: error.isOperational,
|
|
260
|
+
metadata: error.metadata,
|
|
261
|
+
cause: error,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
211
264
|
return new CqrsError(error instanceof Error ? error.message : message, {
|
|
212
265
|
code: ErrorCode.INTERNAL_ERROR,
|
|
213
266
|
category: ErrorCategory.SYSTEM,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Event, EventId, EventType
|
|
1
|
+
import type { Event, EventId, EventType } from "@zudojs/events";
|
|
2
2
|
/**
|
|
3
3
|
* CQRS-specific event extensions.
|
|
4
4
|
*
|
|
@@ -19,15 +19,21 @@ export interface CqrsEventExtensions {
|
|
|
19
19
|
export type CqrsEvent<TPayload extends Record<string, unknown> = Record<string, unknown>> = Event<TPayload> & CqrsEventExtensions;
|
|
20
20
|
/**
|
|
21
21
|
* Input used to create a CQRS event.
|
|
22
|
+
*
|
|
23
|
+
* Identifiers are accepted as plain strings so values coming from a
|
|
24
|
+
* `CqrsContext` (`requestId`, `correlationId`, `causationId`) and from
|
|
25
|
+
* `createEventId()` can be passed without casts; the created event still
|
|
26
|
+
* carries the branded `EventId`/`EventCorrelationId` types of the base
|
|
27
|
+
* `Event` contract.
|
|
22
28
|
*/
|
|
23
29
|
export interface CreateCqrsEventInput<TPayload extends Record<string, unknown> = Record<string, unknown>> {
|
|
24
30
|
readonly type: EventType;
|
|
25
31
|
readonly payload: TPayload;
|
|
26
|
-
readonly id?:
|
|
32
|
+
readonly id?: string;
|
|
27
33
|
readonly timestamp?: Date | number;
|
|
28
34
|
readonly source?: string;
|
|
29
|
-
readonly correlationId?:
|
|
30
|
-
readonly causationId?:
|
|
35
|
+
readonly correlationId?: string;
|
|
36
|
+
readonly causationId?: string;
|
|
31
37
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
32
38
|
readonly aggregateId?: string;
|
|
33
39
|
readonly aggregateType?: string;
|
|
@@ -51,9 +57,10 @@ export interface CqrsEventHandlerRegistration<TEvent extends CqrsEvent = CqrsEve
|
|
|
51
57
|
/**
|
|
52
58
|
* Creates a unique CQRS event identifier.
|
|
53
59
|
*
|
|
54
|
-
* Delegates to the base Zudojs event ID generator
|
|
60
|
+
* Delegates to the base Zudojs event ID generator and returns its
|
|
61
|
+
* branded `EventId` (assignable to `string`).
|
|
55
62
|
*/
|
|
56
|
-
export declare function createEventId():
|
|
63
|
+
export declare function createEventId(): EventId;
|
|
57
64
|
/**
|
|
58
65
|
* Creates an immutable CQRS event.
|
|
59
66
|
*
|
|
@@ -2,7 +2,8 @@ import { createEvent as baseCreateEvent, createEventId as baseCreateEventId, isE
|
|
|
2
2
|
/**
|
|
3
3
|
* Creates a unique CQRS event identifier.
|
|
4
4
|
*
|
|
5
|
-
* Delegates to the base Zudojs event ID generator
|
|
5
|
+
* Delegates to the base Zudojs event ID generator and returns its
|
|
6
|
+
* branded `EventId` (assignable to `string`).
|
|
6
7
|
*/
|
|
7
8
|
export function createEventId() {
|
|
8
9
|
return baseCreateEventId();
|
|
@@ -31,8 +32,17 @@ export function createCqrsEvent(input) {
|
|
|
31
32
|
causationId: input.causationId,
|
|
32
33
|
metadata: input.metadata,
|
|
33
34
|
});
|
|
35
|
+
// A CQRS domain event type is a durable identifier: it is written
|
|
36
|
+
// into event streams and matched by aggregate reducers, so
|
|
37
|
+
// "UserCreated" must survive round-tripping verbatim. The base
|
|
38
|
+
// event factory lower-cases and collapses separators for bus
|
|
39
|
+
// routing, which is right for pub/sub topics but destructive for
|
|
40
|
+
// domain names (it also collides "UserCreated" with "usercreated").
|
|
41
|
+
// Routing still works: the registry normalizes the lookup key on
|
|
42
|
+
// both registration and dispatch.
|
|
34
43
|
const event = {
|
|
35
44
|
...base,
|
|
45
|
+
type: input.type.trim(),
|
|
36
46
|
aggregateId: input.aggregateId,
|
|
37
47
|
aggregateType: input.aggregateType,
|
|
38
48
|
version: input.version,
|
|
@@ -10,17 +10,88 @@ export interface MiddlewareOptions {
|
|
|
10
10
|
readonly name?: string;
|
|
11
11
|
readonly enabled?: boolean;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Measurement reported by `timingMiddleware` after every execution.
|
|
15
|
+
*/
|
|
16
|
+
export interface CqrsTiming {
|
|
17
|
+
/**
|
|
18
|
+
* Name given to the middleware (`options.name`, default `"timing"`).
|
|
19
|
+
*/
|
|
20
|
+
readonly name: string;
|
|
21
|
+
/**
|
|
22
|
+
* The request that was executed.
|
|
23
|
+
*/
|
|
24
|
+
readonly request: Command | Query;
|
|
25
|
+
/**
|
|
26
|
+
* The execution context the middleware received.
|
|
27
|
+
*/
|
|
28
|
+
readonly context: CqrsContext | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Wall-clock duration of everything downstream of the middleware.
|
|
31
|
+
*/
|
|
32
|
+
readonly durationMs: number;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the downstream execution resolved (`true`) or threw.
|
|
35
|
+
*/
|
|
36
|
+
readonly succeeded: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* The error thrown downstream, when `succeeded` is `false`.
|
|
39
|
+
*/
|
|
40
|
+
readonly error?: unknown;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Options for `timingMiddleware`.
|
|
44
|
+
*/
|
|
45
|
+
export interface TimingMiddlewareOptions extends MiddlewareOptions {
|
|
46
|
+
/**
|
|
47
|
+
* Receives the measurement after each execution (success or failure).
|
|
48
|
+
* Errors thrown by the callback propagate to the caller.
|
|
49
|
+
*
|
|
50
|
+
* Optional: measurements are always exposed through
|
|
51
|
+
* `TimingMiddleware.lastTiming` as well.
|
|
52
|
+
*/
|
|
53
|
+
readonly onTiming?: (timing: CqrsTiming) => void | Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Monotonic clock in milliseconds. Defaults to `performance.now()`.
|
|
56
|
+
*/
|
|
57
|
+
readonly now?: () => number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Middleware returned by `timingMiddleware`.
|
|
61
|
+
*
|
|
62
|
+
* Besides acting as ordinary middleware it exposes the most recent
|
|
63
|
+
* measurement, so timings are observable even without an `onTiming`
|
|
64
|
+
* callback.
|
|
65
|
+
*/
|
|
66
|
+
export interface TimingMiddleware extends CqrsMiddleware {
|
|
67
|
+
/**
|
|
68
|
+
* Measurement of the most recent execution, or `undefined` before the
|
|
69
|
+
* first execution completes.
|
|
70
|
+
*/
|
|
71
|
+
readonly lastTiming: CqrsTiming | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Number of executions measured so far.
|
|
74
|
+
*/
|
|
75
|
+
readonly count: number;
|
|
76
|
+
}
|
|
13
77
|
/**
|
|
14
78
|
* Middleware that measures command or query execution time.
|
|
79
|
+
*
|
|
80
|
+
* Every measurement is reported through `options.onTiming` (when given)
|
|
81
|
+
* and stored on the returned middleware as `lastTiming`.
|
|
15
82
|
*/
|
|
16
|
-
export declare function timingMiddleware(options?:
|
|
83
|
+
export declare function timingMiddleware(options?: TimingMiddlewareOptions): TimingMiddleware;
|
|
17
84
|
/**
|
|
18
85
|
* Middleware that catches unknown exceptions and normalizes them
|
|
19
|
-
* into
|
|
86
|
+
* into `CqrsError` instances.
|
|
87
|
+
*
|
|
88
|
+
* `BaseError` instances (including every CQRS error) pass through
|
|
89
|
+
* unchanged.
|
|
20
90
|
*/
|
|
21
91
|
export declare function errorMiddleware(options?: MiddlewareOptions): CqrsMiddleware;
|
|
22
92
|
/**
|
|
23
|
-
* Middleware that validates the basic CQRS request structure
|
|
93
|
+
* Middleware that validates the basic CQRS request structure and throws
|
|
94
|
+
* `CqrsValidationError` when it is malformed.
|
|
24
95
|
*/
|
|
25
96
|
export declare function validationMiddleware(options?: MiddlewareOptions): CqrsMiddleware;
|
|
26
97
|
/**
|
|
@@ -28,16 +99,28 @@ export declare function validationMiddleware(options?: MiddlewareOptions): CqrsM
|
|
|
28
99
|
*/
|
|
29
100
|
export declare function contextMiddleware(options?: MiddlewareOptions): CqrsMiddleware;
|
|
30
101
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
102
|
+
* Lock implementation used by `lockMiddleware`.
|
|
103
|
+
*
|
|
104
|
+
* `acquire` must resolve to a release function.
|
|
33
105
|
*/
|
|
34
106
|
export interface CqrsLock {
|
|
35
107
|
acquire(key: string): (() => void) | Promise<() => void>;
|
|
36
108
|
}
|
|
37
109
|
/**
|
|
38
|
-
*
|
|
110
|
+
* Options for `lockMiddleware`.
|
|
111
|
+
*/
|
|
112
|
+
export interface LockMiddlewareOptions extends MiddlewareOptions {
|
|
113
|
+
/**
|
|
114
|
+
* Derives the lock key from the request. Defaults to `request.type`,
|
|
115
|
+
* which serialises every request of that type.
|
|
116
|
+
*/
|
|
117
|
+
readonly key?: (request: Command | Query, context?: CqrsContext) => string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Creates locking middleware that prevents concurrent execution of
|
|
121
|
+
* requests sharing the same lock key.
|
|
39
122
|
*/
|
|
40
|
-
export declare function lockMiddleware(lock: CqrsLock, options?:
|
|
123
|
+
export declare function lockMiddleware(lock: CqrsLock, options?: LockMiddlewareOptions): CqrsMiddleware;
|
|
41
124
|
/**
|
|
42
125
|
* Adapts command-specific middleware to generic CQRS middleware.
|
|
43
126
|
*/
|
|
@@ -48,6 +131,11 @@ export declare function commandMiddleware(middleware: CommandMiddleware): CqrsMi
|
|
|
48
131
|
export declare function queryMiddleware(middleware: QueryMiddleware): CqrsMiddleware;
|
|
49
132
|
/**
|
|
50
133
|
* Combines multiple middleware functions into a single middleware.
|
|
134
|
+
*
|
|
135
|
+
* Each middleware may call `next()` at most once per execution; a second
|
|
136
|
+
* call throws `MiddlewareExecutionError`. The command and query buses
|
|
137
|
+
* build their pipelines with this function, so the same rule applies
|
|
138
|
+
* there.
|
|
51
139
|
*/
|
|
52
140
|
export declare function composeMiddleware(middleware: readonly CqrsMiddleware[]): CqrsMiddleware;
|
|
53
141
|
/**
|