@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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -7
  3. package/dist/command/command.core.d.ts +5 -2
  4. package/dist/command/command.core.js +11 -7
  5. package/dist/command/commandBus.core.d.ts +20 -6
  6. package/dist/command/commandBus.core.js +47 -62
  7. package/dist/command/commandHandler.core.d.ts +9 -3
  8. package/dist/command/commandHandler.core.js +23 -6
  9. package/dist/cqrsContext/cqrsContext.core.d.ts +11 -0
  10. package/dist/cqrsContext/cqrsContext.core.js +25 -7
  11. package/dist/cqrsDecorators/cqrsDecorators.core.d.ts +14 -8
  12. package/dist/cqrsDecorators/cqrsDecorators.core.js +79 -60
  13. package/dist/cqrsErrors/cqrsError.base.d.ts +34 -2
  14. package/dist/cqrsErrors/cqrsError.base.js +58 -5
  15. package/dist/cqrsEvents/cqrsEvents.type.d.ts +13 -6
  16. package/dist/cqrsEvents/cqrsEvents.type.js +11 -1
  17. package/dist/cqrsMiddleware/cqrsMiddleware.core.d.ts +101 -8
  18. package/dist/cqrsMiddleware/cqrsMiddleware.core.js +123 -32
  19. package/dist/cqrsMiddleware/index.d.ts +2 -1
  20. package/dist/cqrsMiddleware/index.js +2 -1
  21. package/dist/cqrsTypes/cqrsTypes.type.d.ts +17 -30
  22. package/dist/cqrsTypes/cqrsTypes.type.js +0 -16
  23. package/dist/cqrsValidation/cqrsValidation.core.d.ts +32 -0
  24. package/dist/cqrsValidation/cqrsValidation.core.js +53 -0
  25. package/dist/cqrsValidation/index.d.ts +7 -0
  26. package/dist/cqrsValidation/index.js +7 -0
  27. package/dist/handlerRegistry/handlerRegistry.core.d.ts +5 -0
  28. package/dist/handlerRegistry/handlerRegistry.core.js +28 -10
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +6 -2
  31. package/dist/query/query.core.d.ts +5 -2
  32. package/dist/query/query.core.js +11 -7
  33. package/dist/query/queryBus.core.d.ts +20 -6
  34. package/dist/query/queryBus.core.js +47 -62
  35. package/dist/query/queryHandler.core.d.ts +9 -3
  36. package/dist/query/queryHandler.core.js +23 -6
  37. package/package.json +26 -14
  38. package/dist/command/command.core.d.ts.map +0 -1
  39. package/dist/command/command.core.js.map +0 -1
  40. package/dist/command/commandBus.core.d.ts.map +0 -1
  41. package/dist/command/commandBus.core.js.map +0 -1
  42. package/dist/command/commandHandler.core.d.ts.map +0 -1
  43. package/dist/command/commandHandler.core.js.map +0 -1
  44. package/dist/command/commandResult.type.d.ts.map +0 -1
  45. package/dist/command/commandResult.type.js.map +0 -1
  46. package/dist/command/index.d.ts.map +0 -1
  47. package/dist/command/index.js.map +0 -1
  48. package/dist/cqrsContext/cqrsContext.core.d.ts.map +0 -1
  49. package/dist/cqrsContext/cqrsContext.core.js.map +0 -1
  50. package/dist/cqrsContext/index.d.ts.map +0 -1
  51. package/dist/cqrsContext/index.js.map +0 -1
  52. package/dist/cqrsDecorators/cqrsDecorators.core.d.ts.map +0 -1
  53. package/dist/cqrsDecorators/cqrsDecorators.core.js.map +0 -1
  54. package/dist/cqrsDecorators/index.d.ts.map +0 -1
  55. package/dist/cqrsDecorators/index.js.map +0 -1
  56. package/dist/cqrsErrors/cqrsError.base.d.ts.map +0 -1
  57. package/dist/cqrsErrors/cqrsError.base.js.map +0 -1
  58. package/dist/cqrsErrors/index.d.ts.map +0 -1
  59. package/dist/cqrsErrors/index.js.map +0 -1
  60. package/dist/cqrsEvents/cqrsEventBus.core.d.ts.map +0 -1
  61. package/dist/cqrsEvents/cqrsEventBus.core.js.map +0 -1
  62. package/dist/cqrsEvents/cqrsEventResult.type.d.ts.map +0 -1
  63. package/dist/cqrsEvents/cqrsEventResult.type.js.map +0 -1
  64. package/dist/cqrsEvents/cqrsEvents.type.d.ts.map +0 -1
  65. package/dist/cqrsEvents/cqrsEvents.type.js.map +0 -1
  66. package/dist/cqrsEvents/index.d.ts.map +0 -1
  67. package/dist/cqrsEvents/index.js.map +0 -1
  68. package/dist/cqrsMiddleware/cqrsMiddleware.core.d.ts.map +0 -1
  69. package/dist/cqrsMiddleware/cqrsMiddleware.core.js.map +0 -1
  70. package/dist/cqrsMiddleware/index.d.ts.map +0 -1
  71. package/dist/cqrsMiddleware/index.js.map +0 -1
  72. package/dist/cqrsTypes/cqrsTypes.type.d.ts.map +0 -1
  73. package/dist/cqrsTypes/cqrsTypes.type.js.map +0 -1
  74. package/dist/cqrsTypes/index.d.ts.map +0 -1
  75. package/dist/cqrsTypes/index.js.map +0 -1
  76. package/dist/handlerRegistry/handlerRegistry.core.d.ts.map +0 -1
  77. package/dist/handlerRegistry/handlerRegistry.core.js.map +0 -1
  78. package/dist/handlerRegistry/index.d.ts.map +0 -1
  79. package/dist/handlerRegistry/index.js.map +0 -1
  80. package/dist/index.d.ts.map +0 -1
  81. package/dist/index.js.map +0 -1
  82. package/dist/query/index.d.ts.map +0 -1
  83. package/dist/query/index.js.map +0 -1
  84. package/dist/query/query.core.d.ts.map +0 -1
  85. package/dist/query/query.core.js.map +0 -1
  86. package/dist/query/queryBus.core.d.ts.map +0 -1
  87. package/dist/query/queryBus.core.js.map +0 -1
  88. package/dist/query/queryHandler.core.d.ts.map +0 -1
  89. package/dist/query/queryHandler.core.js.map +0 -1
  90. package/dist/query/queryResult.type.d.ts.map +0 -1
  91. package/dist/query/queryResult.type.js.map +0 -1
@@ -1,27 +1,75 @@
1
- import { BaseError, ErrorCategory, ErrorCode, ErrorSeverity, } from "@zudojs/errors";
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
- return async (request, context, next) => {
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 = Date.now();
32
+ const startedAt = now();
33
+ let result;
12
34
  try {
13
- return await next(request, context);
35
+ result = await next(request, context);
14
36
  }
15
- finally {
16
- const duration = Date.now() - startedAt;
17
- void name;
18
- void duration;
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 BaseError instances.
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 BaseError(error instanceof Error ? error.message : "CQRS execution failed.", {
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.type,
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 BaseError("A valid CQRS request with a type is required.", {
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.type,
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 TypeError("A valid CQRS lock implementation is required.");
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 release = await lock.acquire(request.type);
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
- return await next(request, context);
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
- finally {
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 Error("CQRS middleware called next() more than once.");
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, and composition.
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, and composition.
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<string, never>> = Readonly<{
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<string, never>> = Readonly<{
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
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @zudojs/cqrs/cqrsValidation
3
+ *
4
+ * Shared argument validation used by the buses, registry and decorators.
5
+ */
6
+ export * from "./cqrsValidation.core.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @zudojs/cqrs/cqrsValidation
3
+ *
4
+ * Shared argument validation used by the buses, registry and decorators.
5
+ */
6
+ export * from "./cqrsValidation.core.js";
7
+ //# sourceMappingURL=index.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 Error(`A command handler is already registered for "${type}".`);
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 Error(`A query handler is already registered for "${type}".`);
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
- return this.registerQuery(entry.type, entry.handler);
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
- if (typeof type !== "string" || type.trim().length === 0) {
193
- throw new TypeError(`${kind} type cannot be empty.`);
194
- }
207
+ assertHandlerType(kind, type);
195
208
  }
196
209
  validateHandler(handler, kind, type) {
197
- if (typeof handler !== "function" &&
198
- (typeof handler !== "object" || handler === null)) {
199
- throw new TypeError(`A valid ${kind} handler is required for "${type}".`);
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 * from "./cqrsDecorators/index.js";
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 * from "./cqrsDecorators/index.js";
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<string, never>>(type: TType, payload?: TPayload): Readonly<{
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
  */