@zudojs/cqrs 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  Command Query Responsibility Segregation (CQRS) primitives for separating read and write operations.
4
4
 
5
+ <!-- zudo-docs:start -->
6
+
7
+ **Documentation:** [zudojs.oyinlola.site/docs/packages-cqrs](https://zudojs.oyinlola.site/docs/packages-cqrs) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-cqrs.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
8
+
9
+ <!-- zudo-docs:end -->
10
+
5
11
  ## Installation
6
12
 
7
13
  ```bash
@@ -103,6 +109,10 @@ Validation and handler resolution run at the end of the middleware pipeline,
103
109
  so middleware observes `InvalidCommandError` / `CommandHandlerNotFoundError`
104
110
  like any other failure. Each middleware may call `next()` at most once.
105
111
 
112
+ `onTiming` is an observer: if it throws, the command or query still
113
+ succeeds (or fails with its own error). Pass `onTimingError` to see those
114
+ observer failures.
115
+
106
116
  ## Features
107
117
 
108
118
  - Command bus for write operations
@@ -1,6 +1,6 @@
1
1
  import { executeCommandHandler } from "../command/commandHandler.core.js";
2
2
  import { CommandHandlerNotFoundError, DuplicateHandlerError, InvalidCommandError, } from "../cqrsErrors/cqrsError.base.js";
3
- import { composeMiddleware } from "../cqrsMiddleware/cqrsMiddleware.core.js";
3
+ import { composeMiddleware } from "../cqrsMiddleware/cqrsMiddleware.compose.js";
4
4
  import { assertExecutableHandler, assertHandlerType, assertMiddleware, } from "../cqrsValidation/cqrsValidation.core.js";
5
5
  /**
6
6
  * Command bus implementation.
@@ -1,10 +1,11 @@
1
- import { BaseError, ErrorCode, type BaseErrorOptions, type ErrorMetadata } from "@zudojs/errors";
1
+ import { CqrsError, ErrorCode, type ErrorMetadata } from "@zudojs/errors";
2
2
  /**
3
3
  * Base error for failures originating from the CQRS package.
4
+ *
5
+ * Owned by `@zudojs/errors` and re-exported here, so
6
+ * `instanceof CqrsError` matches across both packages.
4
7
  */
5
- export declare class CqrsError extends BaseError {
6
- constructor(message: string, options?: BaseErrorOptions);
7
- }
8
+ export { CqrsError };
8
9
  /**
9
10
  * Thrown when a CQRS request is invalid.
10
11
  *
@@ -1,20 +1,11 @@
1
- import { BaseError, ErrorCategory, ErrorCode, ErrorSeverity, } from "@zudojs/errors";
1
+ import { BaseError, CqrsError, ErrorCategory, ErrorCode, ErrorSeverity, } from "@zudojs/errors";
2
2
  /**
3
3
  * Base error for failures originating from the CQRS package.
4
+ *
5
+ * Owned by `@zudojs/errors` and re-exported here, so
6
+ * `instanceof CqrsError` matches across both packages.
4
7
  */
5
- export class CqrsError extends BaseError {
6
- constructor(message, options = {}) {
7
- super(message, {
8
- ...options,
9
- code: options.code ?? ErrorCode.INTERNAL_ERROR,
10
- category: options.category ?? ErrorCategory.SYSTEM,
11
- severity: options.severity ?? ErrorSeverity.ERROR,
12
- statusCode: options.statusCode ?? 500,
13
- expose: options.expose ?? false,
14
- isOperational: options.isOperational ?? true,
15
- });
16
- }
17
- }
8
+ export { CqrsError };
18
9
  /**
19
10
  * Thrown when a CQRS request is invalid.
20
11
  *
@@ -0,0 +1,14 @@
1
+ /**
2
+ * CQRS middleware composition, built on `compose` from `@zudojs/middleware`.
3
+ */
4
+ import type { CqrsMiddleware } from "../cqrsTypes/cqrsTypes.type.js";
5
+ /**
6
+ * Combines multiple middleware functions into a single middleware.
7
+ *
8
+ * Each middleware may call `next()` at most once per execution; a second
9
+ * call throws `MiddlewareExecutionError`. The command and query buses
10
+ * build their pipelines with this function, so the same rule applies
11
+ * there.
12
+ */
13
+ export declare function composeMiddleware(middleware: readonly CqrsMiddleware[]): CqrsMiddleware;
14
+ //# sourceMappingURL=cqrsMiddleware.compose.d.ts.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * CQRS middleware composition, built on `compose` from `@zudojs/middleware`.
3
+ */
4
+ import { compose } from "@zudojs/middleware";
5
+ import { MiddlewareExecutionError } from "../cqrsErrors/cqrsError.base.js";
6
+ /** Adapts one CQRS middleware to the shared `(context, next)` shape. */
7
+ function adapt(middleware) {
8
+ return async (state, next) => {
9
+ let called = false;
10
+ return middleware(state.request, state.context, (request, context) => {
11
+ if (called) {
12
+ return Promise.reject(new MiddlewareExecutionError());
13
+ }
14
+ called = true;
15
+ state.request = request;
16
+ state.context = context;
17
+ return next();
18
+ });
19
+ };
20
+ }
21
+ /**
22
+ * Combines multiple middleware functions into a single middleware.
23
+ *
24
+ * Each middleware may call `next()` at most once per execution; a second
25
+ * call throws `MiddlewareExecutionError`. The command and query buses
26
+ * build their pipelines with this function, so the same rule applies
27
+ * there.
28
+ */
29
+ export function composeMiddleware(middleware) {
30
+ const run = compose(middleware.map(adapt), async (state) => state.terminal(state.request, state.context), { maxDepth: Number.POSITIVE_INFINITY });
31
+ return async (request, context, terminal) => run({ request, context, terminal });
32
+ }
33
+ //# sourceMappingURL=cqrsMiddleware.compose.js.map
@@ -45,12 +45,19 @@ export interface CqrsTiming {
45
45
  export interface TimingMiddlewareOptions extends MiddlewareOptions {
46
46
  /**
47
47
  * Receives the measurement after each execution (success or failure).
48
- * Errors thrown by the callback propagate to the caller.
48
+ * The callback is an observer: if it throws or rejects, the error is
49
+ * passed to `onTimingError` (when given) and otherwise discarded. It
50
+ * never changes the outcome of the command or query.
49
51
  *
50
52
  * Optional: measurements are always exposed through
51
53
  * `TimingMiddleware.lastTiming` as well.
52
54
  */
53
55
  readonly onTiming?: (timing: CqrsTiming) => void | Promise<void>;
56
+ /**
57
+ * Receives any error thrown by `onTiming`. Errors thrown here are
58
+ * discarded as well.
59
+ */
60
+ readonly onTimingError?: (error: unknown, timing: CqrsTiming) => void;
54
61
  /**
55
62
  * Monotonic clock in milliseconds. Defaults to `performance.now()`.
56
63
  */
@@ -134,15 +141,6 @@ export declare function commandMiddleware(middleware: CommandMiddleware): CqrsMi
134
141
  * Adapts query-specific middleware to generic CQRS middleware.
135
142
  */
136
143
  export declare function queryMiddleware(middleware: QueryMiddleware): CqrsMiddleware;
137
- /**
138
- * Combines multiple middleware functions into a single middleware.
139
- *
140
- * Each middleware may call `next()` at most once per execution; a second
141
- * call throws `MiddlewareExecutionError`. The command and query buses
142
- * build their pipelines with this function, so the same rule applies
143
- * there.
144
- */
145
- export declare function composeMiddleware(middleware: readonly CqrsMiddleware[]): CqrsMiddleware;
146
144
  /**
147
145
  * Creates a middleware that runs a callback before execution.
148
146
  */
@@ -1,5 +1,5 @@
1
1
  import { BaseError } from "@zudojs/errors";
2
- import { CqrsError, CqrsValidationError, InvalidMiddlewareError, MiddlewareExecutionError, } from "../cqrsErrors/cqrsError.base.js";
2
+ import { CqrsError, CqrsValidationError, InvalidMiddlewareError, } from "../cqrsErrors/cqrsError.base.js";
3
3
  /**
4
4
  * Middleware that measures command or query execution time.
5
5
  *
@@ -10,6 +10,10 @@ export function timingMiddleware(options = {}) {
10
10
  if (options.onTiming !== undefined && typeof options.onTiming !== "function") {
11
11
  throw new InvalidMiddlewareError("timingMiddleware onTiming must be a function.");
12
12
  }
13
+ if (options.onTimingError !== undefined &&
14
+ typeof options.onTimingError !== "function") {
15
+ throw new InvalidMiddlewareError("timingMiddleware onTimingError must be a function.");
16
+ }
13
17
  if (options.now !== undefined && typeof options.now !== "function") {
14
18
  throw new InvalidMiddlewareError("timingMiddleware now must be a function.");
15
19
  }
@@ -21,9 +25,20 @@ export function timingMiddleware(options = {}) {
21
25
  const record = async (timing) => {
22
26
  lastTiming = timing;
23
27
  count += 1;
24
- if (onTiming) {
28
+ if (!onTiming) {
29
+ return;
30
+ }
31
+ try {
25
32
  await onTiming(timing);
26
33
  }
34
+ catch (error) {
35
+ try {
36
+ options.onTimingError?.(error, timing);
37
+ }
38
+ catch {
39
+ return;
40
+ }
41
+ }
27
42
  };
28
43
  const middleware = async (request, context, next) => {
29
44
  if (options.enabled === false) {
@@ -201,32 +216,6 @@ export function queryMiddleware(middleware) {
201
216
  return middleware(request, context, async (query, nextContext) => next(query, nextContext));
202
217
  };
203
218
  }
204
- /**
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.
211
- */
212
- export function composeMiddleware(middleware) {
213
- const stack = [...middleware];
214
- return async (request, context, terminal) => {
215
- let index = -1;
216
- const dispatch = async (currentIndex, currentRequest, currentContext) => {
217
- if (currentIndex <= index) {
218
- throw new MiddlewareExecutionError();
219
- }
220
- index = currentIndex;
221
- const current = stack[currentIndex];
222
- if (!current) {
223
- return terminal(currentRequest, currentContext);
224
- }
225
- return current(currentRequest, currentContext, (nextRequest, nextContext) => dispatch(currentIndex + 1, nextRequest, nextContext));
226
- };
227
- return dispatch(0, request, context);
228
- };
229
- }
230
219
  /**
231
220
  * Creates a middleware that runs a callback before execution.
232
221
  */
@@ -5,4 +5,5 @@
5
5
  * context enrichment, lifecycle callbacks, and composition.
6
6
  */
7
7
  export * from "./cqrsMiddleware.core.js";
8
+ export * from "./cqrsMiddleware.compose.js";
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -5,4 +5,5 @@
5
5
  * context enrichment, lifecycle callbacks, and composition.
6
6
  */
7
7
  export * from "./cqrsMiddleware.core.js";
8
+ export * from "./cqrsMiddleware.compose.js";
8
9
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,6 @@
1
1
  import { executeQueryHandler } from "../query/queryHandler.core.js";
2
2
  import { QueryHandlerNotFoundError, DuplicateHandlerError, InvalidQueryError, } from "../cqrsErrors/cqrsError.base.js";
3
- import { composeMiddleware } from "../cqrsMiddleware/cqrsMiddleware.core.js";
3
+ import { composeMiddleware } from "../cqrsMiddleware/cqrsMiddleware.compose.js";
4
4
  import { assertExecutableHandler, assertHandlerType, assertMiddleware, } from "../cqrsValidation/cqrsValidation.core.js";
5
5
  /**
6
6
  * Query bus implementation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/cqrs",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Command Query Responsibility Segregation (CQRS) primitives for separating read and write operations.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -25,8 +25,9 @@
25
25
  "!dist/.tsbuildinfo"
26
26
  ],
27
27
  "dependencies": {
28
- "@zudojs/errors": "1.0.1",
29
- "@zudojs/events": "1.0.1"
28
+ "@zudojs/errors": "1.1.0",
29
+ "@zudojs/events": "1.1.0",
30
+ "@zudojs/middleware": "1.0.2"
30
31
  },
31
32
  "devDependencies": {
32
33
  "typescript": "7.0.2",