@zudojs/messaging 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 +85 -7
- package/dist/dispatcher/dispatcherCore.d.ts +38 -3
- package/dist/dispatcher/dispatcherCore.js +173 -30
- package/dist/dispatcher/dispatcherType.type.d.ts +16 -5
- package/dist/handlerRegistry/handlerRegistryStore.d.ts +10 -0
- package/dist/handlerRegistry/handlerRegistryStore.js +38 -2
- package/dist/handlerRegistry/handlerRegistryType.type.d.ts +6 -3
- package/dist/message/index.d.ts +1 -1
- package/dist/message/index.js +1 -1
- package/dist/message/messageFactory.d.ts +17 -1
- package/dist/message/messageFactory.js +33 -0
- package/dist/messageBus/messageBusCore.d.ts +4 -3
- package/dist/messageBus/messageBusCore.js +20 -25
- package/dist/messageBus/messageBusType.type.d.ts +12 -4
- package/dist/messageMiddleware/messageMiddlewarePipeline.js +18 -7
- package/dist/messageMiddleware/messageMiddlewareType.type.d.ts +11 -0
- package/package.json +25 -13
- package/dist/.tsbuildinfo +0 -1
- package/dist/dispatcher/dispatcherCore.d.ts.map +0 -1
- package/dist/dispatcher/dispatcherCore.js.map +0 -1
- package/dist/dispatcher/dispatcherType.type.d.ts.map +0 -1
- package/dist/dispatcher/dispatcherType.type.js.map +0 -1
- package/dist/dispatcher/index.d.ts.map +0 -1
- package/dist/dispatcher/index.js.map +0 -1
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js.map +0 -1
- package/dist/handlerRegistry/handlerRegistryStore.d.ts.map +0 -1
- package/dist/handlerRegistry/handlerRegistryStore.js.map +0 -1
- package/dist/handlerRegistry/handlerRegistryType.type.d.ts.map +0 -1
- package/dist/handlerRegistry/handlerRegistryType.type.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/message/index.d.ts.map +0 -1
- package/dist/message/index.js.map +0 -1
- package/dist/message/messageFactory.d.ts.map +0 -1
- package/dist/message/messageFactory.js.map +0 -1
- package/dist/message/messageType.type.d.ts.map +0 -1
- package/dist/message/messageType.type.js.map +0 -1
- package/dist/messageBus/index.d.ts.map +0 -1
- package/dist/messageBus/index.js.map +0 -1
- package/dist/messageBus/messageBusCore.d.ts.map +0 -1
- package/dist/messageBus/messageBusCore.js.map +0 -1
- package/dist/messageBus/messageBusType.type.d.ts.map +0 -1
- package/dist/messageBus/messageBusType.type.js.map +0 -1
- package/dist/messageContext/index.d.ts.map +0 -1
- package/dist/messageContext/index.js.map +0 -1
- package/dist/messageContext/messageContextType.type.d.ts.map +0 -1
- package/dist/messageContext/messageContextType.type.js.map +0 -1
- package/dist/messageHandler/index.d.ts.map +0 -1
- package/dist/messageHandler/index.js.map +0 -1
- package/dist/messageHandler/messageHandlerType.type.d.ts.map +0 -1
- package/dist/messageHandler/messageHandlerType.type.js.map +0 -1
- package/dist/messageMiddleware/index.d.ts.map +0 -1
- package/dist/messageMiddleware/index.js.map +0 -1
- package/dist/messageMiddleware/messageMiddlewarePipeline.d.ts.map +0 -1
- package/dist/messageMiddleware/messageMiddlewarePipeline.js.map +0 -1
- package/dist/messageMiddleware/messageMiddlewareType.type.d.ts.map +0 -1
- package/dist/messageMiddleware/messageMiddlewareType.type.js.map +0 -1
|
@@ -10,6 +10,39 @@
|
|
|
10
10
|
export function createMessageId() {
|
|
11
11
|
return `msg:${crypto.randomUUID()}`;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Rejects an empty or blank identifier.
|
|
15
|
+
*/
|
|
16
|
+
function assertNonEmptyId(value, kind) {
|
|
17
|
+
if (value.trim().length === 0) {
|
|
18
|
+
throw new TypeError(`A ${kind} must be a non-empty string.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Brands an existing string as a {@link MessageId}.
|
|
23
|
+
*
|
|
24
|
+
* Identifiers arriving from outside the process — a transport frame, a
|
|
25
|
+
* database row, a log record — are plain strings. This is the supported way
|
|
26
|
+
* to restore the branded type without an unchecked cast.
|
|
27
|
+
*/
|
|
28
|
+
export function toMessageId(value) {
|
|
29
|
+
assertNonEmptyId(value, "MessageId");
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Brands an existing string as a {@link MessageCorrelationId}.
|
|
34
|
+
*/
|
|
35
|
+
export function toCorrelationId(value) {
|
|
36
|
+
assertNonEmptyId(value, "CorrelationId");
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Brands an existing string as a {@link MessageCausationId}.
|
|
41
|
+
*/
|
|
42
|
+
export function toCausationId(value) {
|
|
43
|
+
assertNonEmptyId(value, "CausationId");
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
13
46
|
/**
|
|
14
47
|
* Normalizes a message timestamp.
|
|
15
48
|
*/
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { Message, MessageInput } from "../message/messageType.type.js";
|
|
7
7
|
import type { MessageHandler, NamedMessageHandler } from "../messageHandler/messageHandlerType.type.js";
|
|
8
|
-
import type { MessageMiddlewareLike } from "../messageMiddleware/messageMiddlewareType.type.js";
|
|
8
|
+
import type { MessageMiddlewareLike, MessageMiddlewareOptions } from "../messageMiddleware/messageMiddlewareType.type.js";
|
|
9
9
|
import type { MessageBus, MessageBusOptions } from "./messageBusType.type.js";
|
|
10
10
|
import type { DispatchResult, DispatchOptions } from "../dispatcher/dispatcherType.type.js";
|
|
11
11
|
/** Default in-memory message bus. */
|
|
@@ -13,12 +13,12 @@ export declare class InMemoryMessageBus implements MessageBus {
|
|
|
13
13
|
private readonly dispatcher;
|
|
14
14
|
private readonly registry;
|
|
15
15
|
private readonly defaultTimeout;
|
|
16
|
+
private handlerSequence;
|
|
16
17
|
private _disposed;
|
|
17
18
|
constructor(options?: MessageBusOptions);
|
|
18
19
|
private registerGlobalMiddleware;
|
|
19
20
|
dispatch<TPayload, TResult>(message: Message<TPayload>, options?: DispatchOptions<TResult>): Promise<DispatchResult<TResult>>;
|
|
20
21
|
private validateNotDisposed;
|
|
21
|
-
private dispatchWithTimeout;
|
|
22
22
|
send<TPayload, TResult>(input: MessageInput<TPayload>, options?: DispatchOptions<TResult>): Promise<DispatchResult<TResult>>;
|
|
23
23
|
on<TPayload, TResult>(messageType: string, handler: MessageHandler<Message<TPayload>, TResult>, options?: {
|
|
24
24
|
id?: string;
|
|
@@ -26,7 +26,8 @@ export declare class InMemoryMessageBus implements MessageBus {
|
|
|
26
26
|
}): void;
|
|
27
27
|
addHandler<TMessage extends Message, TResult>(handler: NamedMessageHandler<TMessage, TResult>): void;
|
|
28
28
|
off(handlerId: string): boolean;
|
|
29
|
-
use<TMessage extends Message = Message, TResult = unknown>(middleware: MessageMiddlewareLike<TMessage, TResult
|
|
29
|
+
use<TMessage extends Message = Message, TResult = unknown>(middleware: MessageMiddlewareLike<TMessage, TResult>, options?: MessageMiddlewareOptions): string;
|
|
30
|
+
removeMiddleware(middlewareId: string): boolean;
|
|
30
31
|
hasHandlers(messageType: string): boolean;
|
|
31
32
|
get handlerCount(): number;
|
|
32
33
|
dispose(): void;
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
import { createMessage } from "../message/messageFactory.js";
|
|
7
7
|
import { HandlerRegistryStore } from "../handlerRegistry/handlerRegistryStore.js";
|
|
8
8
|
import { DefaultDispatcher } from "../dispatcher/dispatcherCore.js";
|
|
9
|
-
import {
|
|
9
|
+
import { MessageBusDisposedError } from "@zudojs/errors";
|
|
10
10
|
/** Default in-memory message bus. */
|
|
11
11
|
export class InMemoryMessageBus {
|
|
12
12
|
dispatcher;
|
|
13
13
|
registry;
|
|
14
14
|
defaultTimeout;
|
|
15
|
+
handlerSequence = 0;
|
|
15
16
|
_disposed = false;
|
|
16
17
|
constructor(options = {}) {
|
|
17
18
|
this.registry = new HandlerRegistryStore({
|
|
@@ -31,36 +32,27 @@ export class InMemoryMessageBus {
|
|
|
31
32
|
async dispatch(message, options = {}) {
|
|
32
33
|
this.validateNotDisposed(message);
|
|
33
34
|
const timeout = options.timeout ?? this.defaultTimeout;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
// The timeout is the dispatcher's job. The bus used to race the dispatch
|
|
36
|
+
// itself and abort on expiry, which produced a generic abort error and
|
|
37
|
+
// meant the dispatcher's own documented `timeout` option stayed dead.
|
|
38
|
+
return this.dispatcher.dispatch(message, {
|
|
39
|
+
...options,
|
|
40
|
+
timeout,
|
|
41
|
+
});
|
|
39
42
|
}
|
|
40
|
-
validateNotDisposed(
|
|
43
|
+
validateNotDisposed(_message) {
|
|
41
44
|
if (this._disposed) {
|
|
42
|
-
throw new
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
async dispatchWithTimeout(message, options) {
|
|
46
|
-
const controller = new AbortController();
|
|
47
|
-
const timeoutId = setTimeout(() => controller.abort(), options.timeout ?? 0);
|
|
48
|
-
try {
|
|
49
|
-
const result = await this.dispatcher.dispatch(message, {
|
|
50
|
-
...options,
|
|
51
|
-
signal: controller.signal,
|
|
52
|
-
});
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
clearTimeout(timeoutId);
|
|
45
|
+
throw new MessageBusDisposedError();
|
|
57
46
|
}
|
|
58
47
|
}
|
|
59
48
|
async send(input, options = {}) {
|
|
60
49
|
return this.dispatch(createMessage(input), options);
|
|
61
50
|
}
|
|
62
51
|
on(messageType, handler, options = {}) {
|
|
63
|
-
|
|
52
|
+
// A clock-derived id collided whenever two handlers for the same type
|
|
53
|
+
// were registered inside one millisecond, which then threw
|
|
54
|
+
// DuplicateMessageHandlerError from what looked like ordinary setup.
|
|
55
|
+
const id = options.id ?? `handler:${messageType}:${++this.handlerSequence}`;
|
|
64
56
|
const namedHandler = {
|
|
65
57
|
id,
|
|
66
58
|
name: id,
|
|
@@ -77,8 +69,11 @@ export class InMemoryMessageBus {
|
|
|
77
69
|
off(handlerId) {
|
|
78
70
|
return this.registry.unregister(handlerId);
|
|
79
71
|
}
|
|
80
|
-
use(middleware) {
|
|
81
|
-
this.dispatcher.use(middleware);
|
|
72
|
+
use(middleware, options) {
|
|
73
|
+
return this.dispatcher.use(middleware, options);
|
|
74
|
+
}
|
|
75
|
+
removeMiddleware(middlewareId) {
|
|
76
|
+
return this.dispatcher.removeMiddleware(middlewareId);
|
|
82
77
|
}
|
|
83
78
|
hasHandlers(messageType) {
|
|
84
79
|
return this.registry.resolve(messageType).length > 0;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { Message, MessageInput } from "../message/messageType.type.js";
|
|
10
10
|
import type { MessageHandler, NamedMessageHandler } from "../messageHandler/messageHandlerType.type.js";
|
|
11
|
-
import type { MessageMiddlewareLike } from "../messageMiddleware/messageMiddlewareType.type.js";
|
|
11
|
+
import type { MessageMiddlewareLike, MessageMiddlewareOptions } from "../messageMiddleware/messageMiddlewareType.type.js";
|
|
12
12
|
import type { DispatchResult, DispatchOptions } from "../dispatcher/dispatcherType.type.js";
|
|
13
13
|
/**
|
|
14
14
|
* Options for creating a message bus.
|
|
@@ -59,10 +59,18 @@ export interface MessageBus {
|
|
|
59
59
|
off(handlerId: string): boolean;
|
|
60
60
|
/**
|
|
61
61
|
* Adds middleware to the bus.
|
|
62
|
+
*
|
|
63
|
+
* Middleware runs in ascending `priority` order (default 100).
|
|
64
|
+
*
|
|
65
|
+
* @returns The identifier {@link MessageBus.removeMiddleware} takes.
|
|
62
66
|
*/
|
|
63
|
-
use<TMessage extends Message = Message, TResult = unknown>(middleware: MessageMiddlewareLike<TMessage, TResult>, options?:
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
use<TMessage extends Message = Message, TResult = unknown>(middleware: MessageMiddlewareLike<TMessage, TResult>, options?: MessageMiddlewareOptions): string;
|
|
68
|
+
/**
|
|
69
|
+
* Removes middleware by the id `use` returned.
|
|
70
|
+
*
|
|
71
|
+
* @returns Whether a registration was removed.
|
|
72
|
+
*/
|
|
73
|
+
removeMiddleware(middlewareId: string): boolean;
|
|
66
74
|
/**
|
|
67
75
|
* Checks if handlers are registered for a message type.
|
|
68
76
|
*/
|
|
@@ -49,20 +49,31 @@ export async function runMessagePipeline(middlewareList, handler, message, optio
|
|
|
49
49
|
const metadata = options.metadata ?? {};
|
|
50
50
|
const state = options.state ?? new Map();
|
|
51
51
|
const executions = [];
|
|
52
|
-
const resolvedMiddleware = middlewareList.map((mw) => {
|
|
52
|
+
const resolvedMiddleware = middlewareList.map((mw, index) => {
|
|
53
53
|
const resolved = resolveMiddlewareLike(mw);
|
|
54
|
+
// Each record now names the middleware it belongs to and carries what
|
|
55
|
+
// that middleware returned. Both used to be wrong: every record was
|
|
56
|
+
// labelled with the pipeline's own execution id, and `result` was pushed
|
|
57
|
+
// from a `finally` block that could only ever see `undefined`.
|
|
58
|
+
const middlewareId = options.middlewareIds?.[index] ?? `${executionId}#${index}`;
|
|
54
59
|
return async (ctx, next) => {
|
|
55
60
|
const mwStart = performance.now();
|
|
56
61
|
try {
|
|
57
|
-
|
|
62
|
+
const result = await resolved(ctx, next);
|
|
63
|
+
executions.push({
|
|
64
|
+
middlewareId,
|
|
65
|
+
result,
|
|
66
|
+
duration: performance.now() - mwStart,
|
|
67
|
+
});
|
|
68
|
+
return result;
|
|
58
69
|
}
|
|
59
|
-
|
|
60
|
-
const duration = performance.now() - mwStart;
|
|
70
|
+
catch (error) {
|
|
61
71
|
executions.push({
|
|
62
|
-
middlewareId
|
|
72
|
+
middlewareId,
|
|
63
73
|
result: undefined,
|
|
64
|
-
duration,
|
|
74
|
+
duration: performance.now() - mwStart,
|
|
65
75
|
});
|
|
76
|
+
throw error;
|
|
66
77
|
}
|
|
67
78
|
};
|
|
68
79
|
});
|
|
@@ -70,7 +81,7 @@ export async function runMessagePipeline(middlewareList, handler, message, optio
|
|
|
70
81
|
const composed = compose(resolvedMiddleware, async (ctx) => handler(message, ctx));
|
|
71
82
|
const context = {
|
|
72
83
|
message,
|
|
73
|
-
context: {
|
|
84
|
+
context: options.context ?? {
|
|
74
85
|
message,
|
|
75
86
|
correlationId: message.correlationId ??
|
|
76
87
|
message.id,
|
|
@@ -73,6 +73,7 @@ export interface RegisteredMessageMiddleware<TMessage extends Message = Message,
|
|
|
73
73
|
* Execution record for a single middleware.
|
|
74
74
|
*/
|
|
75
75
|
export interface MessageMiddlewareExecution<TResult = unknown> {
|
|
76
|
+
/** The id of the middleware this record describes. */
|
|
76
77
|
readonly middlewareId: string;
|
|
77
78
|
readonly result: TResult;
|
|
78
79
|
readonly duration: number;
|
|
@@ -92,5 +93,15 @@ export interface MessageMiddlewarePipelineOptions {
|
|
|
92
93
|
readonly signal?: AbortSignal;
|
|
93
94
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
94
95
|
readonly state?: Map<string, unknown>;
|
|
96
|
+
/**
|
|
97
|
+
* Identifiers parallel to the middleware list, used to label the execution
|
|
98
|
+
* records. Positions without an id fall back to a synthesised one.
|
|
99
|
+
*/
|
|
100
|
+
readonly middlewareIds?: readonly string[];
|
|
101
|
+
/**
|
|
102
|
+
* Message context to expose as `MessageMiddlewareContext.context`.
|
|
103
|
+
* When omitted one is derived from the message and the options above.
|
|
104
|
+
*/
|
|
105
|
+
readonly context?: MessageContext;
|
|
95
106
|
}
|
|
96
107
|
//# sourceMappingURL=messageMiddlewareType.type.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/messaging",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "In-process message bus infrastructure with handlers, middleware, and publish/subscribe patterns.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,26 +13,27 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"dist"
|
|
16
|
+
"dist",
|
|
17
|
+
"!dist/**/*.map",
|
|
18
|
+
"!dist/**/*.tsbuildinfo",
|
|
19
|
+
"!dist/.tsbuildinfo"
|
|
17
20
|
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "tsc -p tsconfig.json",
|
|
20
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
21
|
-
"test": "vitest run",
|
|
22
|
-
"test:watch": "vitest",
|
|
23
|
-
"clean": "rm -rf dist"
|
|
24
|
-
},
|
|
25
21
|
"dependencies": {
|
|
26
|
-
"@zudojs/errors": "0.1
|
|
27
|
-
"@zudojs/constants": "0.1
|
|
22
|
+
"@zudojs/errors": "1.0.1",
|
|
23
|
+
"@zudojs/constants": "1.0.1"
|
|
28
24
|
},
|
|
29
25
|
"engines": {
|
|
30
26
|
"node": ">=24.0.0"
|
|
31
27
|
},
|
|
32
28
|
"devDependencies": {
|
|
29
|
+
"typescript": "7.0.2",
|
|
33
30
|
"vitest": "^4.1.11"
|
|
34
31
|
},
|
|
35
32
|
"license": "MIT",
|
|
33
|
+
"author": {
|
|
34
|
+
"name": "Oluwayemi Oyinlola",
|
|
35
|
+
"url": "https://github.com/oyinlola-tech"
|
|
36
|
+
},
|
|
36
37
|
"publishConfig": {
|
|
37
38
|
"access": "public"
|
|
38
39
|
},
|
|
@@ -43,8 +44,19 @@
|
|
|
43
44
|
"pubsub"
|
|
44
45
|
],
|
|
45
46
|
"homepage": "https://github.com/oyinlola-tech/zudo#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/oyinlola-tech/zudo/issues"
|
|
49
|
+
},
|
|
46
50
|
"repository": {
|
|
47
51
|
"type": "git",
|
|
48
|
-
"url": "https://github.com/oyinlola-tech/zudo"
|
|
52
|
+
"url": "https://github.com/oyinlola-tech/zudo",
|
|
53
|
+
"directory": "packages/messaging"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc -p tsconfig.json",
|
|
57
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"clean": "rm -rf dist"
|
|
49
61
|
}
|
|
50
|
-
}
|
|
62
|
+
}
|