@zudojs/cqrs 0.0.1 → 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.
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 +95 -7
  18. package/dist/cqrsMiddleware/cqrsMiddleware.core.js +97 -29
  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 +22 -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,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
- Object.defineProperty(constructor, CQRS_HANDLER_METADATA, {
32
- configurable: false,
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 a command handler.
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 CommandHandler(type) {
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
- Object.defineProperty(constructor, COMMAND_HANDLER_METADATA, {
57
- configurable: false,
58
- enumerable: false,
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 a query handler.
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 QueryHandler(type) {
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
- Object.defineProperty(constructor, QUERY_HANDLER_METADATA, {
88
- configurable: false,
89
- enumerable: false,
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 as a command handler.
118
+ * Determines whether a class is decorated with `CommandHandlerFor`.
151
119
  */
152
- export function isCommandHandler(target) {
120
+ export function isDecoratedCommandHandler(target) {
153
121
  return getCommandHandlerMetadata(target) !== undefined;
154
122
  }
155
123
  /**
156
- * Determines whether a class is decorated as a query handler.
124
+ * Determines whether a class is decorated with `QueryHandlerFor`.
157
125
  */
158
- export function isQueryHandler(target) {
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 CommandHandler(type);
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 QueryHandler(type);
139
+ return QueryHandlerFor(type);
172
140
  }
173
141
  /**
174
142
  * Validates decorator arguments.
175
143
  */
176
144
  function validateHandlerMetadata(kind, type) {
177
- if (typeof type !== "string" || type.trim().length === 0) {
178
- throw new TypeError(`${kind} handler type cannot be empty.`);
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?: ConstructorParameters<typeof BaseError>[1]);
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: ErrorCode.INVALID_INPUT,
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: 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: 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, EventCorrelationId } from "@zudojs/events";
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?: EventId;
32
+ readonly id?: string;
27
33
  readonly timestamp?: Date | number;
28
34
  readonly source?: string;
29
- readonly correlationId?: EventCorrelationId;
30
- readonly causationId?: EventCorrelationId;
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(): string;
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?: MiddlewareOptions): CqrsMiddleware;
83
+ export declare function timingMiddleware(options?: TimingMiddlewareOptions): TimingMiddleware;
17
84
  /**
18
85
  * Middleware that catches unknown exceptions and normalizes them
19
- * into BaseError instances.
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
- * Middleware that prevents concurrent execution of the same request type
32
- * when used with a shared lock implementation.
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
- * Creates locking middleware.
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?: MiddlewareOptions): CqrsMiddleware;
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
  /**