@zudojs/cqrs 0.1.0 → 1.0.1
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 +101 -8
- package/dist/cqrsMiddleware/cqrsMiddleware.core.js +123 -32
- 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 +26 -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,27 +1,75 @@
|
|
|
1
|
-
import { BaseError
|
|
1
|
+
import { BaseError } from "@zudojs/errors";
|
|
2
|
+
import { CqrsError, CqrsValidationError, InvalidMiddlewareError, MiddlewareExecutionError, } from "../cqrsErrors/cqrsError.base.js";
|
|
2
3
|
/**
|
|
3
4
|
* Middleware that measures command or query execution time.
|
|
5
|
+
*
|
|
6
|
+
* Every measurement is reported through `options.onTiming` (when given)
|
|
7
|
+
* and stored on the returned middleware as `lastTiming`.
|
|
4
8
|
*/
|
|
5
9
|
export function timingMiddleware(options = {}) {
|
|
10
|
+
if (options.onTiming !== undefined && typeof options.onTiming !== "function") {
|
|
11
|
+
throw new InvalidMiddlewareError("timingMiddleware onTiming must be a function.");
|
|
12
|
+
}
|
|
13
|
+
if (options.now !== undefined && typeof options.now !== "function") {
|
|
14
|
+
throw new InvalidMiddlewareError("timingMiddleware now must be a function.");
|
|
15
|
+
}
|
|
6
16
|
const name = options.name ?? "timing";
|
|
7
|
-
|
|
17
|
+
const now = options.now ?? (() => performance.now());
|
|
18
|
+
const onTiming = options.onTiming;
|
|
19
|
+
let lastTiming;
|
|
20
|
+
let count = 0;
|
|
21
|
+
const record = async (timing) => {
|
|
22
|
+
lastTiming = timing;
|
|
23
|
+
count += 1;
|
|
24
|
+
if (onTiming) {
|
|
25
|
+
await onTiming(timing);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const middleware = async (request, context, next) => {
|
|
8
29
|
if (options.enabled === false) {
|
|
9
30
|
return next(request, context);
|
|
10
31
|
}
|
|
11
|
-
const startedAt =
|
|
32
|
+
const startedAt = now();
|
|
33
|
+
let result;
|
|
12
34
|
try {
|
|
13
|
-
|
|
35
|
+
result = await next(request, context);
|
|
14
36
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
37
|
+
catch (error) {
|
|
38
|
+
await record({
|
|
39
|
+
name,
|
|
40
|
+
request,
|
|
41
|
+
context,
|
|
42
|
+
durationMs: now() - startedAt,
|
|
43
|
+
succeeded: false,
|
|
44
|
+
error,
|
|
45
|
+
});
|
|
46
|
+
throw error;
|
|
19
47
|
}
|
|
48
|
+
await record({
|
|
49
|
+
name,
|
|
50
|
+
request,
|
|
51
|
+
context,
|
|
52
|
+
durationMs: now() - startedAt,
|
|
53
|
+
succeeded: true,
|
|
54
|
+
});
|
|
55
|
+
return result;
|
|
20
56
|
};
|
|
57
|
+
Object.defineProperty(middleware, "lastTiming", {
|
|
58
|
+
enumerable: true,
|
|
59
|
+
get: () => lastTiming,
|
|
60
|
+
});
|
|
61
|
+
Object.defineProperty(middleware, "count", {
|
|
62
|
+
enumerable: true,
|
|
63
|
+
get: () => count,
|
|
64
|
+
});
|
|
65
|
+
return middleware;
|
|
21
66
|
}
|
|
22
67
|
/**
|
|
23
68
|
* Middleware that catches unknown exceptions and normalizes them
|
|
24
|
-
* into
|
|
69
|
+
* into `CqrsError` instances.
|
|
70
|
+
*
|
|
71
|
+
* `BaseError` instances (including every CQRS error) pass through
|
|
72
|
+
* unchanged.
|
|
25
73
|
*/
|
|
26
74
|
export function errorMiddleware(options = {}) {
|
|
27
75
|
return async (request, context, next) => {
|
|
@@ -35,23 +83,20 @@ export function errorMiddleware(options = {}) {
|
|
|
35
83
|
if (error instanceof BaseError) {
|
|
36
84
|
throw error;
|
|
37
85
|
}
|
|
38
|
-
throw new
|
|
39
|
-
code: ErrorCode.INTERNAL_ERROR,
|
|
40
|
-
category: ErrorCategory.SYSTEM,
|
|
41
|
-
severity: ErrorSeverity.ERROR,
|
|
42
|
-
statusCode: 500,
|
|
86
|
+
throw new CqrsError(error instanceof Error ? error.message : "CQRS execution failed.", {
|
|
43
87
|
expose: false,
|
|
44
88
|
isOperational: false,
|
|
45
89
|
cause: error,
|
|
46
90
|
metadata: {
|
|
47
|
-
requestType: request
|
|
91
|
+
requestType: getRequestType(request),
|
|
48
92
|
},
|
|
49
93
|
});
|
|
50
94
|
}
|
|
51
95
|
};
|
|
52
96
|
}
|
|
53
97
|
/**
|
|
54
|
-
* Middleware that validates the basic CQRS request structure
|
|
98
|
+
* Middleware that validates the basic CQRS request structure and throws
|
|
99
|
+
* `CqrsValidationError` when it is malformed.
|
|
55
100
|
*/
|
|
56
101
|
export function validationMiddleware(options = {}) {
|
|
57
102
|
return async (request, context, next) => {
|
|
@@ -62,14 +107,7 @@ export function validationMiddleware(options = {}) {
|
|
|
62
107
|
typeof request !== "object" ||
|
|
63
108
|
typeof request.type !== "string" ||
|
|
64
109
|
request.type.trim().length === 0) {
|
|
65
|
-
throw new
|
|
66
|
-
code: ErrorCode.INVALID_INPUT,
|
|
67
|
-
category: ErrorCategory.VALIDATION,
|
|
68
|
-
severity: ErrorSeverity.WARNING,
|
|
69
|
-
statusCode: 400,
|
|
70
|
-
expose: true,
|
|
71
|
-
isOperational: true,
|
|
72
|
-
});
|
|
110
|
+
throw new CqrsValidationError("A valid CQRS request with a type is required.");
|
|
73
111
|
}
|
|
74
112
|
return next(request, context);
|
|
75
113
|
};
|
|
@@ -86,30 +124,65 @@ export function contextMiddleware(options = {}) {
|
|
|
86
124
|
...(context ?? {}),
|
|
87
125
|
metadata: {
|
|
88
126
|
...(context?.metadata ?? {}),
|
|
89
|
-
cqrsRequestType: request
|
|
127
|
+
cqrsRequestType: getRequestType(request),
|
|
90
128
|
},
|
|
91
129
|
};
|
|
92
130
|
return next(request, enrichedContext);
|
|
93
131
|
};
|
|
94
132
|
}
|
|
95
133
|
/**
|
|
96
|
-
* Creates locking middleware
|
|
134
|
+
* Creates locking middleware that prevents concurrent execution of
|
|
135
|
+
* requests sharing the same lock key.
|
|
97
136
|
*/
|
|
98
137
|
export function lockMiddleware(lock, options = {}) {
|
|
99
138
|
if (!lock || typeof lock.acquire !== "function") {
|
|
100
|
-
throw new
|
|
139
|
+
throw new InvalidMiddlewareError("A valid CQRS lock implementation with an acquire() method is required.");
|
|
140
|
+
}
|
|
141
|
+
if (options.key !== undefined && typeof options.key !== "function") {
|
|
142
|
+
throw new InvalidMiddlewareError("lockMiddleware key selector must be a function.");
|
|
101
143
|
}
|
|
102
144
|
return async (request, context, next) => {
|
|
103
145
|
if (options.enabled === false) {
|
|
104
146
|
return next(request, context);
|
|
105
147
|
}
|
|
106
|
-
const
|
|
148
|
+
const key = options.key
|
|
149
|
+
? options.key(request, context)
|
|
150
|
+
: (getRequestType(request) ?? "");
|
|
151
|
+
const release = await lock.acquire(key);
|
|
152
|
+
if (typeof release !== "function") {
|
|
153
|
+
throw new InvalidMiddlewareError(`CQRS lock acquire() must resolve to a release function (key "${key}").`, {
|
|
154
|
+
lockKey: key,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// `release()` may be asynchronous (a Redis DEL, say). It used to be
|
|
158
|
+
// called and dropped, so a rejected release was an unhandled promise
|
|
159
|
+
// rejection — which terminates the process under Node's defaults.
|
|
160
|
+
let result;
|
|
107
161
|
try {
|
|
108
|
-
|
|
162
|
+
result = await next(request, context);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
try {
|
|
166
|
+
await release();
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// The handler's failure is the one the caller needs to see.
|
|
170
|
+
}
|
|
171
|
+
throw error;
|
|
109
172
|
}
|
|
110
|
-
|
|
111
|
-
release();
|
|
173
|
+
try {
|
|
174
|
+
await release();
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
throw new CqrsError(`CQRS lock release failed (key "${key}").`, {
|
|
178
|
+
cause: error,
|
|
179
|
+
metadata: {
|
|
180
|
+
lockKey: key,
|
|
181
|
+
requestType: getRequestType(request),
|
|
182
|
+
},
|
|
183
|
+
});
|
|
112
184
|
}
|
|
185
|
+
return result;
|
|
113
186
|
};
|
|
114
187
|
}
|
|
115
188
|
/**
|
|
@@ -130,6 +203,11 @@ export function queryMiddleware(middleware) {
|
|
|
130
203
|
}
|
|
131
204
|
/**
|
|
132
205
|
* Combines multiple middleware functions into a single middleware.
|
|
206
|
+
*
|
|
207
|
+
* Each middleware may call `next()` at most once per execution; a second
|
|
208
|
+
* call throws `MiddlewareExecutionError`. The command and query buses
|
|
209
|
+
* build their pipelines with this function, so the same rule applies
|
|
210
|
+
* there.
|
|
133
211
|
*/
|
|
134
212
|
export function composeMiddleware(middleware) {
|
|
135
213
|
const stack = [...middleware];
|
|
@@ -137,7 +215,7 @@ export function composeMiddleware(middleware) {
|
|
|
137
215
|
let index = -1;
|
|
138
216
|
const dispatch = async (currentIndex, currentRequest, currentContext) => {
|
|
139
217
|
if (currentIndex <= index) {
|
|
140
|
-
throw new
|
|
218
|
+
throw new MiddlewareExecutionError();
|
|
141
219
|
}
|
|
142
220
|
index = currentIndex;
|
|
143
221
|
const current = stack[currentIndex];
|
|
@@ -182,4 +260,17 @@ export function onErrorMiddleware(callback) {
|
|
|
182
260
|
}
|
|
183
261
|
};
|
|
184
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Reads the request discriminator defensively.
|
|
265
|
+
*
|
|
266
|
+
* Validation now runs inside the pipeline, so middleware may observe a
|
|
267
|
+
* malformed request before the bus rejects it.
|
|
268
|
+
*/
|
|
269
|
+
function getRequestType(request) {
|
|
270
|
+
if (typeof request !== "object" || request === null) {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
const type = request.type;
|
|
274
|
+
return typeof type === "string" ? type : undefined;
|
|
275
|
+
}
|
|
185
276
|
//# sourceMappingURL=cqrsMiddleware.core.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zudojs/cqrs/cqrsMiddleware
|
|
3
3
|
*
|
|
4
|
-
* Built-in CQRS middleware: timing, error handling, validation,
|
|
4
|
+
* Built-in CQRS middleware: timing, error handling, validation, locking,
|
|
5
|
+
* context enrichment, lifecycle callbacks, and composition.
|
|
5
6
|
*/
|
|
6
7
|
export * from "./cqrsMiddleware.core.js";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zudojs/cqrs/cqrsMiddleware
|
|
3
3
|
*
|
|
4
|
-
* Built-in CQRS middleware: timing, error handling, validation,
|
|
4
|
+
* Built-in CQRS middleware: timing, error handling, validation, locking,
|
|
5
|
+
* context enrichment, lifecycle callbacks, and composition.
|
|
5
6
|
*/
|
|
6
7
|
export * from "./cqrsMiddleware.core.js";
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -28,28 +28,20 @@ export interface CqrsContext {
|
|
|
28
28
|
readonly tenantId?: string;
|
|
29
29
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Result returned by a command handler.
|
|
33
|
-
*/
|
|
34
|
-
export interface CommandResult<TResult = void> {
|
|
35
|
-
readonly result: TResult;
|
|
36
|
-
readonly commandType?: string;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Result returned by a query handler.
|
|
40
|
-
*/
|
|
41
|
-
export interface QueryResult<TResult> {
|
|
42
|
-
readonly result: TResult;
|
|
43
|
-
readonly queryType?: string;
|
|
44
|
-
}
|
|
45
31
|
/**
|
|
46
32
|
* Generic command handler contract.
|
|
33
|
+
*
|
|
34
|
+
* Any object exposing an `execute` method satisfies this contract; the
|
|
35
|
+
* abstract `CommandHandler` class in `command/` is one implementation.
|
|
47
36
|
*/
|
|
48
37
|
export interface CommandHandler<TCommand extends Command = Command, TResult = void> {
|
|
49
38
|
execute(command: TCommand, context?: CqrsContext): Promise<TResult> | TResult;
|
|
50
39
|
}
|
|
51
40
|
/**
|
|
52
41
|
* Generic query handler contract.
|
|
42
|
+
*
|
|
43
|
+
* Any object exposing an `execute` method satisfies this contract; the
|
|
44
|
+
* abstract `QueryHandler` class in `query/` is one implementation.
|
|
53
45
|
*/
|
|
54
46
|
export interface QueryHandler<TQuery extends Query = Query, TResult = unknown> {
|
|
55
47
|
execute(query: TQuery, context?: CqrsContext): Promise<TResult> | TResult;
|
|
@@ -121,16 +113,23 @@ export interface QueryBus {
|
|
|
121
113
|
}
|
|
122
114
|
/**
|
|
123
115
|
* Converts a command type into a strongly typed command definition.
|
|
116
|
+
*
|
|
117
|
+
* The payload defaults to an empty object (`Record<never, never>`), so
|
|
118
|
+
* `CommandOf<"Ping">` is satisfied by `{ type: "Ping" }`. A `type` key in
|
|
119
|
+
* the payload is dropped: the discriminator always wins, mirroring
|
|
120
|
+
* `createCommand`.
|
|
124
121
|
*/
|
|
125
|
-
export type CommandOf<TType extends string, TPayload extends Record<string, unknown> = Record<
|
|
122
|
+
export type CommandOf<TType extends string, TPayload extends Record<string, unknown> = Record<never, never>> = Readonly<{
|
|
126
123
|
readonly type: TType;
|
|
127
|
-
} & TPayload
|
|
124
|
+
} & Omit<TPayload, "type">>;
|
|
128
125
|
/**
|
|
129
126
|
* Converts a query type into a strongly typed query definition.
|
|
127
|
+
*
|
|
128
|
+
* See `CommandOf` for the payload defaulting and `type` precedence rules.
|
|
130
129
|
*/
|
|
131
|
-
export type QueryOf<TType extends string, TPayload extends Record<string, unknown> = Record<
|
|
130
|
+
export type QueryOf<TType extends string, TPayload extends Record<string, unknown> = Record<never, never>> = Readonly<{
|
|
132
131
|
readonly type: TType;
|
|
133
|
-
} & TPayload
|
|
132
|
+
} & Omit<TPayload, "type">>;
|
|
134
133
|
/**
|
|
135
134
|
* Extracts the type discriminator from a CQRS request.
|
|
136
135
|
*/
|
|
@@ -139,18 +138,6 @@ export type CqrsRequestType<TRequest extends CqrsRequest> = TRequest["type"];
|
|
|
139
138
|
* Extracts the payload of a command or query without its discriminator.
|
|
140
139
|
*/
|
|
141
140
|
export type CqrsPayload<TRequest extends CqrsRequest> = Omit<TRequest, "type">;
|
|
142
|
-
/**
|
|
143
|
-
* Determines whether a value is a command.
|
|
144
|
-
*/
|
|
145
|
-
export declare function isCommand(value: unknown): value is Command;
|
|
146
|
-
/**
|
|
147
|
-
* Determines whether a value is a query.
|
|
148
|
-
*
|
|
149
|
-
* Since commands and queries share the same runtime shape, this helper
|
|
150
|
-
* should normally be used only when the surrounding code already knows
|
|
151
|
-
* the request belongs to the query pipeline.
|
|
152
|
-
*/
|
|
153
|
-
export declare function isQuery(value: unknown): value is Query;
|
|
154
141
|
/**
|
|
155
142
|
* Determines whether an unknown value satisfies the basic CQRS request shape.
|
|
156
143
|
*/
|
|
@@ -5,22 +5,6 @@
|
|
|
5
5
|
* Commands represent state-changing operations, while queries represent
|
|
6
6
|
* read-only operations.
|
|
7
7
|
*/
|
|
8
|
-
/**
|
|
9
|
-
* Determines whether a value is a command.
|
|
10
|
-
*/
|
|
11
|
-
export function isCommand(value) {
|
|
12
|
-
return isCqrsRequest(value) && "type" in value;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Determines whether a value is a query.
|
|
16
|
-
*
|
|
17
|
-
* Since commands and queries share the same runtime shape, this helper
|
|
18
|
-
* should normally be used only when the surrounding code already knows
|
|
19
|
-
* the request belongs to the query pipeline.
|
|
20
|
-
*/
|
|
21
|
-
export function isQuery(value) {
|
|
22
|
-
return isCqrsRequest(value);
|
|
23
|
-
}
|
|
24
8
|
/**
|
|
25
9
|
* Determines whether an unknown value satisfies the basic CQRS request shape.
|
|
26
10
|
*/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler kinds accepted by the validation helpers.
|
|
3
|
+
*/
|
|
4
|
+
export type ValidatedHandlerKind = "command" | "query";
|
|
5
|
+
/**
|
|
6
|
+
* Determines whether a value is a well-formed handler type discriminator:
|
|
7
|
+
* a non-empty string without leading or trailing whitespace.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isValidHandlerType(type: unknown): type is string;
|
|
10
|
+
/**
|
|
11
|
+
* Asserts that a handler type discriminator is well-formed.
|
|
12
|
+
*
|
|
13
|
+
* Registration keys are stored verbatim, so surrounding whitespace is
|
|
14
|
+
* rejected instead of silently creating an unreachable registration.
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertHandlerType(kind: ValidatedHandlerKind, type: unknown): asserts type is string;
|
|
17
|
+
/**
|
|
18
|
+
* Determines whether a value can be executed as a handler: either a
|
|
19
|
+
* function or an object exposing a callable `execute` method.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isExecutableHandler(value: unknown): value is ((...args: unknown[]) => unknown) | {
|
|
22
|
+
execute: (...args: unknown[]) => unknown;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Asserts that a value can be executed as a handler.
|
|
26
|
+
*/
|
|
27
|
+
export declare function assertExecutableHandler(kind: ValidatedHandlerKind, type: string, handler: unknown): void;
|
|
28
|
+
/**
|
|
29
|
+
* Asserts that a value is a middleware function.
|
|
30
|
+
*/
|
|
31
|
+
export declare function assertMiddleware(kind: ValidatedHandlerKind, middleware: unknown): asserts middleware is (...args: unknown[]) => unknown;
|
|
32
|
+
//# sourceMappingURL=cqrsValidation.core.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { HandlerConfigurationError, InvalidHandlerTypeError, InvalidMiddlewareError, } from "../cqrsErrors/cqrsError.base.js";
|
|
2
|
+
/**
|
|
3
|
+
* Determines whether a value is a well-formed handler type discriminator:
|
|
4
|
+
* a non-empty string without leading or trailing whitespace.
|
|
5
|
+
*/
|
|
6
|
+
export function isValidHandlerType(type) {
|
|
7
|
+
return (typeof type === "string" && type.length > 0 && type.trim() === type);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Asserts that a handler type discriminator is well-formed.
|
|
11
|
+
*
|
|
12
|
+
* Registration keys are stored verbatim, so surrounding whitespace is
|
|
13
|
+
* rejected instead of silently creating an unreachable registration.
|
|
14
|
+
*/
|
|
15
|
+
export function assertHandlerType(kind, type) {
|
|
16
|
+
if (!isValidHandlerType(type)) {
|
|
17
|
+
throw new InvalidHandlerTypeError(kind, type);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Determines whether a value can be executed as a handler: either a
|
|
22
|
+
* function or an object exposing a callable `execute` method.
|
|
23
|
+
*/
|
|
24
|
+
export function isExecutableHandler(value) {
|
|
25
|
+
if (typeof value === "function") {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return (typeof value === "object" &&
|
|
29
|
+
value !== null &&
|
|
30
|
+
typeof value.execute === "function");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Asserts that a value can be executed as a handler.
|
|
34
|
+
*/
|
|
35
|
+
export function assertExecutableHandler(kind, type, handler) {
|
|
36
|
+
if (!isExecutableHandler(handler)) {
|
|
37
|
+
throw new HandlerConfigurationError(`A valid ${kind} handler (a function or an object with an execute() method) is required for "${type}".`, {
|
|
38
|
+
handlerKind: kind,
|
|
39
|
+
handlerType: type,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Asserts that a value is a middleware function.
|
|
45
|
+
*/
|
|
46
|
+
export function assertMiddleware(kind, middleware) {
|
|
47
|
+
if (typeof middleware !== "function") {
|
|
48
|
+
throw new InvalidMiddlewareError(`${kind === "command" ? "Command" : "Query"} middleware must be a function.`, {
|
|
49
|
+
handlerKind: kind,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=cqrsValidation.core.js.map
|
|
@@ -29,6 +29,11 @@ export type HandlerEntry = CommandHandlerEntry | QueryHandlerEntry;
|
|
|
29
29
|
* The registry is intentionally independent from the buses so handlers
|
|
30
30
|
* can be registered during application bootstrap and later consumed by
|
|
31
31
|
* one or more buses.
|
|
32
|
+
*
|
|
33
|
+
* All failures are `CqrsError` instances: `InvalidHandlerTypeError` for
|
|
34
|
+
* malformed types, `HandlerConfigurationError` for non-callable handlers,
|
|
35
|
+
* `DuplicateHandlerError` for repeated registrations and
|
|
36
|
+
* `InvalidHandlerKindError` for kinds other than `"command"`/`"query"`.
|
|
32
37
|
*/
|
|
33
38
|
export declare class HandlerRegistry {
|
|
34
39
|
private readonly commandHandlers;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import { DuplicateHandlerError, InvalidHandlerKindError, } from "../cqrsErrors/cqrsError.base.js";
|
|
2
|
+
import { assertExecutableHandler, assertHandlerType, } from "../cqrsValidation/cqrsValidation.core.js";
|
|
1
3
|
/**
|
|
2
4
|
* Central registry for CQRS command and query handlers.
|
|
3
5
|
*
|
|
4
6
|
* The registry is intentionally independent from the buses so handlers
|
|
5
7
|
* can be registered during application bootstrap and later consumed by
|
|
6
8
|
* one or more buses.
|
|
9
|
+
*
|
|
10
|
+
* All failures are `CqrsError` instances: `InvalidHandlerTypeError` for
|
|
11
|
+
* malformed types, `HandlerConfigurationError` for non-callable handlers,
|
|
12
|
+
* `DuplicateHandlerError` for repeated registrations and
|
|
13
|
+
* `InvalidHandlerKindError` for kinds other than `"command"`/`"query"`.
|
|
7
14
|
*/
|
|
8
15
|
export class HandlerRegistry {
|
|
9
16
|
commandHandlers = new Map();
|
|
@@ -15,7 +22,7 @@ export class HandlerRegistry {
|
|
|
15
22
|
this.validateType(type, "command");
|
|
16
23
|
this.validateHandler(handler, "command", type);
|
|
17
24
|
if (this.commandHandlers.has(type)) {
|
|
18
|
-
throw new
|
|
25
|
+
throw new DuplicateHandlerError("command", type);
|
|
19
26
|
}
|
|
20
27
|
this.commandHandlers.set(type, handler);
|
|
21
28
|
return this;
|
|
@@ -27,7 +34,7 @@ export class HandlerRegistry {
|
|
|
27
34
|
this.validateType(type, "query");
|
|
28
35
|
this.validateHandler(handler, "query", type);
|
|
29
36
|
if (this.queryHandlers.has(type)) {
|
|
30
|
-
throw new
|
|
37
|
+
throw new DuplicateHandlerError("query", type);
|
|
31
38
|
}
|
|
32
39
|
this.queryHandlers.set(type, handler);
|
|
33
40
|
return this;
|
|
@@ -36,10 +43,16 @@ export class HandlerRegistry {
|
|
|
36
43
|
* Registers a generic handler entry.
|
|
37
44
|
*/
|
|
38
45
|
register(entry) {
|
|
46
|
+
if (!entry || typeof entry !== "object") {
|
|
47
|
+
throw new InvalidHandlerKindError(entry);
|
|
48
|
+
}
|
|
39
49
|
if (entry.kind === "command") {
|
|
40
50
|
return this.registerCommand(entry.type, entry.handler);
|
|
41
51
|
}
|
|
42
|
-
|
|
52
|
+
if (entry.kind === "query") {
|
|
53
|
+
return this.registerQuery(entry.type, entry.handler);
|
|
54
|
+
}
|
|
55
|
+
throw new InvalidHandlerKindError(entry.kind);
|
|
43
56
|
}
|
|
44
57
|
/**
|
|
45
58
|
* Registers multiple handlers.
|
|
@@ -84,6 +97,7 @@ export class HandlerRegistry {
|
|
|
84
97
|
* Removes either a command or query handler.
|
|
85
98
|
*/
|
|
86
99
|
unregister(kind, type) {
|
|
100
|
+
assertHandlerKind(kind);
|
|
87
101
|
return kind === "command"
|
|
88
102
|
? this.unregisterCommand(type)
|
|
89
103
|
: this.unregisterQuery(type);
|
|
@@ -116,6 +130,7 @@ export class HandlerRegistry {
|
|
|
116
130
|
* Returns whether either kind of handler exists.
|
|
117
131
|
*/
|
|
118
132
|
has(kind, type) {
|
|
133
|
+
assertHandlerKind(kind);
|
|
119
134
|
return kind === "command" ? this.hasCommand(type) : this.hasQuery(type);
|
|
120
135
|
}
|
|
121
136
|
/**
|
|
@@ -189,15 +204,18 @@ export class HandlerRegistry {
|
|
|
189
204
|
this.queryHandlers.clear();
|
|
190
205
|
}
|
|
191
206
|
validateType(type, kind) {
|
|
192
|
-
|
|
193
|
-
throw new TypeError(`${kind} type cannot be empty.`);
|
|
194
|
-
}
|
|
207
|
+
assertHandlerType(kind, type);
|
|
195
208
|
}
|
|
196
209
|
validateHandler(handler, kind, type) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
210
|
+
assertExecutableHandler(kind, type, handler);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Asserts that a handler kind is one of the supported values.
|
|
215
|
+
*/
|
|
216
|
+
function assertHandlerKind(kind) {
|
|
217
|
+
if (kind !== "command" && kind !== "query") {
|
|
218
|
+
throw new InvalidHandlerKindError(kind);
|
|
201
219
|
}
|
|
202
220
|
}
|
|
203
221
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Command Query Responsibility Segregation primitives for the Zudojs framework.
|
|
5
5
|
* Provides buses, handlers, middleware, and execution infrastructure.
|
|
6
6
|
*/
|
|
7
|
-
export { type CqrsContext, type CommandHandlerFunction, type QueryHandlerFunction, type CommandHandlerLike, type QueryHandlerLike, type CommandMiddleware, type QueryMiddleware, type CqrsMiddleware, type CqrsRequest, type CommandHandlerRegistration, type QueryHandlerRegistration, type CqrsBusOptions, type CommandOf, type QueryOf, type CqrsRequestType, type CqrsPayload, isCqrsRequest, } from "./cqrsTypes/index.js";
|
|
7
|
+
export { type CqrsContext, type CommandHandler as CommandHandlerContract, type QueryHandler as QueryHandlerContract, type CommandHandlerFunction, type QueryHandlerFunction, type CommandHandlerLike, type QueryHandlerLike, type CommandMiddleware, type QueryMiddleware, type CqrsMiddleware, type CqrsRequest, type CommandHandlerRegistration, type QueryHandlerRegistration, type CqrsBusOptions, type CommandOf, type QueryOf, type CqrsRequestType, type CqrsPayload, isCqrsRequest, } from "./cqrsTypes/index.js";
|
|
8
8
|
export { Command, type CommandOptions, MetadataCommand, createCommand, getCommandType, isCommand, commandType, CommandHandler, FunctionCommandHandler, createCommandHandler, isCommandHandler, isCommandHandlerLike, executeCommandHandler, CommandBus, type CommandBusOptions, type CommandRegistration, createCommandBus, type CommandResultStatus, type CommandResult, type CreateCommandResultOptions, createCommandResult, createFailedCommandResult, isSuccessfulCommandResult, isFailedCommandResult, unwrapCommandResult, withCommandResultMetadata, } from "./command/index.js";
|
|
9
9
|
export { Query, type QueryOptions, MetadataQuery, createQuery, getQueryType, isQuery, queryType, QueryHandler, FunctionQueryHandler, createQueryHandler, isQueryHandler, isQueryHandlerLike, executeQueryHandler, QueryBus, type QueryBusOptions, type QueryRegistration, createQueryBus, type QueryResultStatus, type QueryResult, type CreateQueryResultOptions, createQueryResult, createFailedQueryResult, isSuccessfulQueryResult, isFailedQueryResult, unwrapQueryResult, withQueryResultMetadata, } from "./query/index.js";
|
|
10
10
|
export { type Event, type EventInput, type EventDefinition, type EventType, type EventPayload, isEvent, createEvent as createBaseEvent, createEventId as createBaseEventId, } from "@zudojs/events";
|
|
11
11
|
export * from "./cqrsEvents/index.js";
|
|
12
12
|
export * from "./cqrsMiddleware/index.js";
|
|
13
13
|
export * from "./handlerRegistry/index.js";
|
|
14
|
-
export
|
|
14
|
+
export { type CqrsHandlerKind, type CqrsHandlerMetadata, type CommandHandlerMetadata, type QueryHandlerMetadata, type CqrsClass, type DecoratedCqrsClass, CqrsHandler, CommandHandlerFor, QueryHandlerFor, getCqrsHandlerMetadata, getCommandHandlerMetadata, getQueryHandlerMetadata, getCqrsType, isCqrsHandler, isDecoratedCommandHandler, isDecoratedQueryHandler, createCommandHandlerDecorator, createQueryHandlerDecorator, } from "./cqrsDecorators/index.js";
|
|
15
15
|
export * from "./cqrsContext/index.js";
|
|
16
16
|
export * from "./cqrsErrors/index.js";
|
|
17
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* Provides buses, handlers, middleware, and execution infrastructure.
|
|
6
6
|
*/
|
|
7
7
|
// Core types (interfaces, type aliases, type guards)
|
|
8
|
+
//
|
|
9
|
+
// The handler *interfaces* are exported as `CommandHandlerContract` /
|
|
10
|
+
// `QueryHandlerContract` because the abstract classes below own the bare
|
|
11
|
+
// `CommandHandler` / `QueryHandler` names.
|
|
8
12
|
export { isCqrsRequest, } from "./cqrsTypes/index.js";
|
|
9
13
|
// Command (classes, factories, type guards)
|
|
10
14
|
export { Command, MetadataCommand, createCommand, getCommandType, isCommand, commandType, CommandHandler, FunctionCommandHandler, createCommandHandler, isCommandHandler, isCommandHandlerLike, executeCommandHandler, CommandBus, createCommandBus, createCommandResult, createFailedCommandResult, isSuccessfulCommandResult, isFailedCommandResult, unwrapCommandResult, withCommandResultMetadata, } from "./command/index.js";
|
|
@@ -17,8 +21,8 @@ export * from "./cqrsEvents/index.js";
|
|
|
17
21
|
export * from "./cqrsMiddleware/index.js";
|
|
18
22
|
// Handler Registry
|
|
19
23
|
export * from "./handlerRegistry/index.js";
|
|
20
|
-
// Decorators
|
|
21
|
-
export
|
|
24
|
+
// Decorators (explicit list: nothing here may shadow the class exports above)
|
|
25
|
+
export { CqrsHandler, CommandHandlerFor, QueryHandlerFor, getCqrsHandlerMetadata, getCommandHandlerMetadata, getQueryHandlerMetadata, getCqrsType, isCqrsHandler, isDecoratedCommandHandler, isDecoratedQueryHandler, createCommandHandlerDecorator, createQueryHandlerDecorator, } from "./cqrsDecorators/index.js";
|
|
22
26
|
// Execution Context
|
|
23
27
|
export * from "./cqrsContext/index.js";
|
|
24
28
|
// Errors
|
|
@@ -24,10 +24,13 @@ export declare abstract class MetadataQuery<TType extends string = string> exten
|
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Creates a simple immutable query object.
|
|
27
|
+
*
|
|
28
|
+
* The `type` argument always wins over any `type` key present in the
|
|
29
|
+
* payload, so untrusted payloads cannot reroute the query.
|
|
27
30
|
*/
|
|
28
|
-
export declare function createQuery<TType extends string, TPayload extends Record<string, unknown> = Record<
|
|
31
|
+
export declare function createQuery<TType extends string, TPayload extends Record<string, unknown> = Record<never, never>>(type: TType, payload?: TPayload): Readonly<{
|
|
29
32
|
readonly type: TType;
|
|
30
|
-
} & TPayload
|
|
33
|
+
} & Omit<TPayload, "type">>;
|
|
31
34
|
/**
|
|
32
35
|
* Returns the query type discriminator.
|
|
33
36
|
*/
|