@palmetto/pubsub 3.3.2 → 3.4.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/README.md +76 -0
- package/dist/publisher.d.ts +52 -0
- package/dist/publisher.js +39 -5
- package/dist/subscriber.d.ts +49 -7
- package/dist/subscriber.js +26 -9
- package/package.json +15 -1
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ It is up to the pub/sub implementations what to do with rejected messages. Optio
|
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
21
|
+
Install pubsub and the basic peer dependencies. You'll also need to install a pubsub system like RabbitMq or BullMq. See below for links on how to use each supported pubsub system.
|
|
22
|
+
|
|
21
23
|
```sh
|
|
22
24
|
yarn add @palmetto/pubsub zod @palmetto/trace dd-trace
|
|
23
25
|
```
|
|
@@ -189,6 +191,7 @@ The message is checked against the provided schema for every publish and subscri
|
|
|
189
191
|
message: string, // the JSON string of the message
|
|
190
192
|
context: MessageContext, // the context of the message provided by the SubscriptionProvider
|
|
191
193
|
config: PubSubConfiguration, // the configuration for the message (eg: BullMqQueueConfiguration, RabbitQueueExchangeConfiguration)
|
|
194
|
+
error: z.ZodError, // the actual zod error from the schema
|
|
192
195
|
) => {
|
|
193
196
|
// TODO
|
|
194
197
|
},
|
|
@@ -209,3 +212,76 @@ It is up to the provider to obey the `retries` and `retryDelay` configuration pr
|
|
|
209
212
|
### Publisher errors
|
|
210
213
|
|
|
211
214
|
When a message publish fails, it is up to the calling code to handle exceptions.
|
|
215
|
+
|
|
216
|
+
## Events
|
|
217
|
+
|
|
218
|
+
Publisher and Subscribers fire events you can use to inspect messages.
|
|
219
|
+
|
|
220
|
+
### Publisher events
|
|
221
|
+
|
|
222
|
+
- schemaError: fired when the message fails zod validation
|
|
223
|
+
- messagePublishing: fired before the message is published to the pubsub provider
|
|
224
|
+
- messagePublished: fired after the message was published via the pubsub provider
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import {
|
|
228
|
+
PublishingEventContext,
|
|
229
|
+
PublisherSchemaErrorEventContext,
|
|
230
|
+
PublishedEventContext,
|
|
231
|
+
} from "@palmetto/pubsub";
|
|
232
|
+
|
|
233
|
+
import { z } from "zod";
|
|
234
|
+
|
|
235
|
+
// when a schema validation fails none of the messages in a published batch are sent
|
|
236
|
+
publisher.on("schemaError", (ctx: PublisherSchemaErrorEventContext) => {
|
|
237
|
+
console.log(z.prettifyError(ctx.error));
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// just before publishing the message
|
|
241
|
+
publisher.on("messagePublishing", (ctx: PublishingEventContext) => {
|
|
242
|
+
console.log(ctx.json);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// just after publishing the message
|
|
246
|
+
publisher.on("messagePublished", (ctx: PublishedEventContext) => {
|
|
247
|
+
console.log(ctx.json);
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Subscriber events
|
|
252
|
+
|
|
253
|
+
- messageReceived: fired when the plain JSON is received by the pubsub provider. You can modify the JSON at this time
|
|
254
|
+
- schemaError: fired when the message fails zod validation.
|
|
255
|
+
- messageHandling: fired before the message was published via the pubsub provider
|
|
256
|
+
- messageHandled: fired after the message was published via the pubsub provider
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import {
|
|
260
|
+
MessageReceivedEventContext,
|
|
261
|
+
SubscriberSchemaErrorEventContext,
|
|
262
|
+
HandlingEventContext,
|
|
263
|
+
HandledEventContext,
|
|
264
|
+
} from "@palmetto/pubsub";
|
|
265
|
+
|
|
266
|
+
import { z } from "zod";
|
|
267
|
+
|
|
268
|
+
// before any processing of the message
|
|
269
|
+
subscriber.on("messageReceived", (ctx: ReceivedEventContext) => {
|
|
270
|
+
console.log(ctx.json);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// when a schema validation fails
|
|
274
|
+
subscriber.on("schemaError", (ctx: SubscriberSchemaErrorEventContext) => {
|
|
275
|
+
console.log(z.prettifyError(ctx.error));
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// before calling the message handler
|
|
279
|
+
subscriber.on("messageHandling", (ctx: HandlingEventContext) => {
|
|
280
|
+
console.log(ctx.json);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// after calling the message handler
|
|
284
|
+
subscriber.on("messageHandled", (ctx: HandledEventContext) => {
|
|
285
|
+
console.log(ctx.json);
|
|
286
|
+
});
|
|
287
|
+
```
|
package/dist/publisher.d.ts
CHANGED
|
@@ -1,9 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { BaseMessage, Logger, PublisherProvider, PublishMessageOptions, PubSubConfiguration } from "./interfaces.js";
|
|
3
|
+
export interface BasePublisherEventContext {
|
|
4
|
+
/**
|
|
5
|
+
* The original message sent into pubsub before it was parsed by the zod schema.
|
|
6
|
+
*/
|
|
7
|
+
originalMessage: BaseMessage;
|
|
8
|
+
/**
|
|
9
|
+
* The message converted to JSON
|
|
10
|
+
*/
|
|
11
|
+
json: string;
|
|
12
|
+
/**
|
|
13
|
+
* The configuration for the publisher that will publish this message.
|
|
14
|
+
*/
|
|
15
|
+
config: PubSubConfiguration;
|
|
16
|
+
}
|
|
17
|
+
export interface PublishingEventContext extends BasePublisherEventContext {
|
|
18
|
+
/**
|
|
19
|
+
* The message being published. This is the message after it was parsed by the zod schema.
|
|
20
|
+
*/
|
|
21
|
+
message: unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface PublisherSchemaErrorEventContext extends BasePublisherEventContext {
|
|
24
|
+
/**
|
|
25
|
+
* The zod validation error that occurred when validating the message against the schema defined in the PubSubConfiguration.
|
|
26
|
+
*/
|
|
27
|
+
error: z.ZodError;
|
|
28
|
+
}
|
|
29
|
+
export interface PublishedEventContext extends BasePublisherEventContext {
|
|
30
|
+
/**
|
|
31
|
+
* The message that was published. This is the message after it was parsed by the zod schema
|
|
32
|
+
*/
|
|
33
|
+
message: unknown;
|
|
34
|
+
}
|
|
35
|
+
export interface PublisherEventMap {
|
|
36
|
+
/**
|
|
37
|
+
* Fired before a message is published. This can be used to log the published message or to perform additional actions before a message is published.
|
|
38
|
+
* @param context The context of the published message
|
|
39
|
+
*/
|
|
40
|
+
messagePublishing: [context: PublishingEventContext];
|
|
41
|
+
/**
|
|
42
|
+
* Fired when a message fails validation. This can be used to log the validation error or to perform additional actions when a message fails validation.
|
|
43
|
+
* @param context The context of the validation error
|
|
44
|
+
*/
|
|
45
|
+
schemaError: [context: PublisherSchemaErrorEventContext];
|
|
46
|
+
/**
|
|
47
|
+
* Fired after a message is published. This can be used to log the published message or to perform additional actions after a message is published.
|
|
48
|
+
* @param context The context of the published message
|
|
49
|
+
*/
|
|
50
|
+
messagePublished: [context: PublishedEventContext];
|
|
51
|
+
}
|
|
2
52
|
export declare class Publisher {
|
|
3
53
|
private readonly logger;
|
|
4
54
|
private readonly hashes;
|
|
5
55
|
private readonly publisherProviders;
|
|
56
|
+
private readonly events;
|
|
6
57
|
constructor(logger: Logger, providers?: PublisherProvider[]);
|
|
58
|
+
on<TK extends keyof PublisherEventMap>(event: TK, listener: (...args: PublisherEventMap[TK]) => void): this;
|
|
7
59
|
addProvider(provider: PublisherProvider): void;
|
|
8
60
|
removeProvider(providerOrTransport: PublisherProvider | string): boolean;
|
|
9
61
|
init(config: PubSubConfiguration): Promise<void>;
|
package/dist/publisher.js
CHANGED
|
@@ -16,12 +16,14 @@ const crypto_1 = require("crypto");
|
|
|
16
16
|
const trace_1 = require("@palmetto/trace");
|
|
17
17
|
const errors_js_1 = require("./errors.js");
|
|
18
18
|
const message_logger_js_1 = require("./message-logger.js");
|
|
19
|
+
const events_1 = require("events");
|
|
19
20
|
class Publisher {
|
|
20
21
|
constructor(logger, providers) {
|
|
21
22
|
var _a, _b;
|
|
22
23
|
this.logger = logger;
|
|
23
24
|
this.hashes = new Map();
|
|
24
25
|
this.publisherProviders = new Map();
|
|
26
|
+
this.events = new events_1.EventEmitter();
|
|
25
27
|
if (providers) {
|
|
26
28
|
providers.forEach((provider) => this.publisherProviders.set(provider.transport, provider));
|
|
27
29
|
}
|
|
@@ -29,6 +31,14 @@ class Publisher {
|
|
|
29
31
|
...this.publisherProviders.keys(),
|
|
30
32
|
].join(", ")}`);
|
|
31
33
|
}
|
|
34
|
+
on(event, listener) {
|
|
35
|
+
this.events.on("messagePublished", (context) => {
|
|
36
|
+
var _a, _b;
|
|
37
|
+
(_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `Event 'messagePublished' emitted for ${context.config.name} on transport ${context.config.transport}`);
|
|
38
|
+
});
|
|
39
|
+
this.events.on(event, listener);
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
32
42
|
addProvider(provider) {
|
|
33
43
|
var _a, _b;
|
|
34
44
|
this.publisherProviders.set(provider.transport, provider);
|
|
@@ -59,9 +69,7 @@ class Publisher {
|
|
|
59
69
|
}
|
|
60
70
|
return (0, trace_1.getTracer)().trace("pubsub.publish", {
|
|
61
71
|
resource: `publish ${config.transport} ${config.name}`,
|
|
62
|
-
}, (span) =>
|
|
63
|
-
return this.publishImpl(config, messages, options, span);
|
|
64
|
-
});
|
|
72
|
+
}, (span) => this.publishImpl(config, messages, options, span));
|
|
65
73
|
}
|
|
66
74
|
publishImpl(config, messages, options, span) {
|
|
67
75
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -91,10 +99,17 @@ class Publisher {
|
|
|
91
99
|
logger: this.logger,
|
|
92
100
|
extra: Object.assign({ transport: provider.transport, name: config.name }, provider.enrichPublishedMesssageLog(config)),
|
|
93
101
|
});
|
|
94
|
-
|
|
102
|
+
const json = JSON.stringify(msg);
|
|
103
|
+
this.events.emit("schemaError", {
|
|
104
|
+
originalMessage: msg,
|
|
105
|
+
error: check.error,
|
|
106
|
+
json,
|
|
107
|
+
config,
|
|
108
|
+
});
|
|
109
|
+
return { json, msg, error: check.error };
|
|
95
110
|
}
|
|
96
111
|
const json = JSON.stringify(check.data);
|
|
97
|
-
return { json, data: check.data };
|
|
112
|
+
return { json, msg, data: check.data };
|
|
98
113
|
});
|
|
99
114
|
const errors = mappedMessages.filter((mm) => mm.error);
|
|
100
115
|
if (errors.length > 0) {
|
|
@@ -102,9 +117,20 @@ class Publisher {
|
|
|
102
117
|
throw new errors_js_1.SchemaValidationError(`Schema did not accept ${errors.length} of ${messages.length} message(s): ${errorMessage}`);
|
|
103
118
|
}
|
|
104
119
|
const jsons = mappedMessages.filter((j) => j.data).map((j) => j.json);
|
|
120
|
+
if (this.events.listenerCount("messagePublishing") > 0) {
|
|
121
|
+
mappedMessages.forEach((msg) => {
|
|
122
|
+
this.events.emit("messagePublishing", {
|
|
123
|
+
message: msg.data,
|
|
124
|
+
originalMessage: msg.msg,
|
|
125
|
+
json: msg.json,
|
|
126
|
+
config,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
105
130
|
const start = (0, message_logger_js_1.startTiming)();
|
|
106
131
|
yield provider.publish(config, jsons, options);
|
|
107
132
|
const duration = (0, message_logger_js_1.getDuration)(start);
|
|
133
|
+
const hasPublishedListener = this.events.listenerCount("messagePublished") > 0;
|
|
108
134
|
mappedMessages.forEach((msg) => {
|
|
109
135
|
(0, message_logger_js_1.logMessage)({
|
|
110
136
|
note: "Published message",
|
|
@@ -113,6 +139,14 @@ class Publisher {
|
|
|
113
139
|
logger: this.logger,
|
|
114
140
|
extra: Object.assign({ transport: provider.transport, name: config.name, durationMs: duration, batchSize: messages.length }, provider.enrichPublishedMesssageLog(config)),
|
|
115
141
|
});
|
|
142
|
+
if (hasPublishedListener) {
|
|
143
|
+
this.events.emit("messagePublished", {
|
|
144
|
+
message: msg.data,
|
|
145
|
+
originalMessage: msg.msg,
|
|
146
|
+
json: msg.json,
|
|
147
|
+
config,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
116
150
|
});
|
|
117
151
|
if (span) {
|
|
118
152
|
span.setTag("pubsub.duration_ms", duration);
|
package/dist/subscriber.d.ts
CHANGED
|
@@ -1,17 +1,59 @@
|
|
|
1
1
|
import { BaseMessage, Logger, MessageContext, MessageResult, PubSubConfiguration, SubscriberProvider } from "./interfaces.js";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export interface BaseSubscriberEventContext {
|
|
4
|
+
/**
|
|
5
|
+
* The plain JSON message as received from the provider.
|
|
6
|
+
*/
|
|
7
|
+
json: string;
|
|
8
|
+
/**
|
|
9
|
+
* The message context as provided by the provider. This includes metadata about the message such as attemptsMade and willAttemptRetry which can be used to determine if the message is being retried and how many times it has been attempted before.
|
|
10
|
+
*/
|
|
4
11
|
context: MessageContext;
|
|
12
|
+
/**
|
|
13
|
+
* The configuration for the subscriber that received this message. This can be used in the event listeners to get information about the subscriber such as the transport and name, and can be used in the enrichHandledMesssageLog to add additional information to the logs.
|
|
14
|
+
*/
|
|
5
15
|
config: PubSubConfiguration;
|
|
6
16
|
}
|
|
7
|
-
export interface
|
|
17
|
+
export interface ReceivedEventContext extends BaseSubscriberEventContext {
|
|
18
|
+
}
|
|
19
|
+
export interface SubscriberSchemaErrorEventContext extends BaseSubscriberEventContext {
|
|
20
|
+
/**
|
|
21
|
+
* The zod validation error that occurred when validating the message against the schema defined in the PubSubConfiguration.
|
|
22
|
+
*/
|
|
23
|
+
error: z.ZodError;
|
|
24
|
+
}
|
|
25
|
+
export interface HandlingEventContext extends BaseSubscriberEventContext {
|
|
26
|
+
/**
|
|
27
|
+
* The message after it has been parsed by the zod schema.
|
|
28
|
+
*/
|
|
29
|
+
message: unknown;
|
|
30
|
+
}
|
|
31
|
+
export interface HandledEventContext extends HandlingEventContext {
|
|
8
32
|
durationMs: number;
|
|
9
33
|
result?: MessageResult;
|
|
10
34
|
err?: unknown;
|
|
11
35
|
}
|
|
12
|
-
export interface
|
|
13
|
-
|
|
14
|
-
|
|
36
|
+
export interface SubscriberEventMap {
|
|
37
|
+
/**
|
|
38
|
+
* Fired when a message is received, before schema validation. This can be used to modify the raw message before validation, for example to add missing properties or to log the raw message.
|
|
39
|
+
* @param context The context of the received message
|
|
40
|
+
*/
|
|
41
|
+
messageReceived: [context: ReceivedEventContext];
|
|
42
|
+
/**
|
|
43
|
+
* This is fired when a message fails schema validation. Note that the messageHandled event is also fired after this event, so you can use this event to log schema validation errors separately from processing errors in the onMessage callback.
|
|
44
|
+
* @param context The context of the message that failed schema validation
|
|
45
|
+
*/
|
|
46
|
+
schemaError: [context: SubscriberSchemaErrorEventContext];
|
|
47
|
+
/**
|
|
48
|
+
* Fired before a message is being sent to the onMessage callback.
|
|
49
|
+
* @param context The context of the message that is being processed
|
|
50
|
+
*/
|
|
51
|
+
messageHandling: [context: HandlingEventContext];
|
|
52
|
+
/**
|
|
53
|
+
* Fired after a message was processed and a result is available, or when an error has been caught in the onMessage callback.
|
|
54
|
+
* @param context The context of the message that was handled
|
|
55
|
+
*/
|
|
56
|
+
messageHandled: [context: HandledEventContext];
|
|
15
57
|
}
|
|
16
58
|
export declare class Subscriber {
|
|
17
59
|
private readonly logger;
|
|
@@ -19,7 +61,7 @@ export declare class Subscriber {
|
|
|
19
61
|
private readonly events;
|
|
20
62
|
private readonly subscribedMessages;
|
|
21
63
|
constructor(logger: Logger, providers?: SubscriberProvider[]);
|
|
22
|
-
on<TU extends keyof
|
|
64
|
+
on<TU extends keyof SubscriberEventMap>(event: TU, listener: (...args: SubscriberEventMap[TU]) => void): this;
|
|
23
65
|
/**
|
|
24
66
|
* Starts the subscriber for the given configuration. Depending on the protocol, the subscriber may not be started immediately.
|
|
25
67
|
*
|
package/dist/subscriber.js
CHANGED
|
@@ -58,6 +58,12 @@ class Subscriber {
|
|
|
58
58
|
const enrichedConfig = provider.enrichHandledMesssageLog(config);
|
|
59
59
|
const handleMessage = (jsonStr, context) => __awaiter(this, void 0, void 0, function* () {
|
|
60
60
|
const start = (0, message_logger_js_1.startTiming)();
|
|
61
|
+
const messageReceivedEventContext = {
|
|
62
|
+
json: jsonStr,
|
|
63
|
+
context,
|
|
64
|
+
config,
|
|
65
|
+
};
|
|
66
|
+
this.events.emit("messageReceived", messageReceivedEventContext);
|
|
61
67
|
const jsonObject = JSON.parse(jsonStr);
|
|
62
68
|
const decodeResult = schema.safeDecode(jsonObject);
|
|
63
69
|
if (!decodeResult.success) {
|
|
@@ -70,27 +76,37 @@ class Subscriber {
|
|
|
70
76
|
extra: Object.assign({ transport: provider.transport, name: config.name, durationMs,
|
|
71
77
|
context }, enrichedConfig),
|
|
72
78
|
});
|
|
79
|
+
const schemaErrorEventContext = {
|
|
80
|
+
json: jsonStr,
|
|
81
|
+
context,
|
|
82
|
+
config,
|
|
83
|
+
error: decodeResult.error,
|
|
84
|
+
};
|
|
85
|
+
this.events.emit("schemaError", schemaErrorEventContext);
|
|
73
86
|
const handledEventContext = {
|
|
74
|
-
|
|
87
|
+
json: jsonStr,
|
|
88
|
+
message: undefined,
|
|
75
89
|
context,
|
|
76
90
|
config,
|
|
77
91
|
durationMs,
|
|
78
92
|
result: interfaces_js_1.MessageResult.Fail,
|
|
93
|
+
err: decodeResult.error,
|
|
79
94
|
};
|
|
80
|
-
this.events.emit("schemaError", handledEventContext);
|
|
81
95
|
this.events.emit("messageHandled", handledEventContext);
|
|
82
96
|
return interfaces_js_1.MessageResult.Fail;
|
|
83
97
|
}
|
|
84
98
|
try {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
message: jsonStr,
|
|
99
|
+
const messageHandlingEventContext = {
|
|
100
|
+
json: jsonStr,
|
|
101
|
+
message: decodeResult.data,
|
|
89
102
|
context,
|
|
90
103
|
config,
|
|
91
|
-
durationMs,
|
|
92
|
-
result,
|
|
93
104
|
};
|
|
105
|
+
this.events.emit("messageHandling", messageHandlingEventContext);
|
|
106
|
+
const result = yield onMessage(decodeResult.data, context);
|
|
107
|
+
const durationMs = (0, message_logger_js_1.getDuration)(start);
|
|
108
|
+
const eventContext = Object.assign(Object.assign({}, messageHandlingEventContext), { durationMs,
|
|
109
|
+
result });
|
|
94
110
|
(0, message_logger_js_1.logMessage)({
|
|
95
111
|
note: "Subscriber processed message",
|
|
96
112
|
message: decodeResult.data,
|
|
@@ -118,7 +134,8 @@ class Subscriber {
|
|
|
118
134
|
? interfaces_js_1.MessageResult.Retry
|
|
119
135
|
: interfaces_js_1.MessageResult.Fail;
|
|
120
136
|
const eventContext = {
|
|
121
|
-
|
|
137
|
+
json: jsonStr,
|
|
138
|
+
message: decodeResult.data,
|
|
122
139
|
result,
|
|
123
140
|
context,
|
|
124
141
|
config,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@palmetto/pubsub",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/palmetto/galaxy"
|
|
@@ -56,5 +56,19 @@
|
|
|
56
56
|
"amqplib": "^0.10.8",
|
|
57
57
|
"bullmq": "^5.70.2",
|
|
58
58
|
"zod": "^4.1"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"@google-cloud/pubsub": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"amqp-connection-manager": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"amqplib": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"bullmq": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
59
73
|
}
|
|
60
74
|
}
|