@palmetto/pubsub 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.
- package/README.md +154 -0
- package/dist/bullmq/config.d.ts +21 -0
- package/dist/bullmq/config.js +2 -0
- package/dist/bullmq/connection.d.ts +2 -0
- package/dist/bullmq/connection.js +5 -0
- package/dist/bullmq/main.d.ts +6 -0
- package/dist/bullmq/main.js +21 -0
- package/dist/bullmq/publisher.d.ts +13 -0
- package/dist/bullmq/publisher.js +62 -0
- package/dist/bullmq/pubsub.d.ts +12 -0
- package/dist/bullmq/pubsub.js +35 -0
- package/dist/bullmq/subscriber.d.ts +27 -0
- package/dist/bullmq/subscriber.js +118 -0
- package/dist/errors.d.ts +17 -0
- package/dist/errors.js +37 -0
- package/dist/interfaces.d.ts +120 -0
- package/dist/interfaces.js +28 -0
- package/dist/lazy-load.d.ts +11 -0
- package/dist/lazy-load.js +79 -0
- package/dist/main.d.ts +6 -0
- package/dist/main.js +22 -0
- package/dist/publisher.d.ts +11 -0
- package/dist/publisher.js +87 -0
- package/dist/rabbitmq/config.d.ts +84 -0
- package/dist/rabbitmq/config.js +82 -0
- package/dist/rabbitmq/connection.d.ts +41 -0
- package/dist/rabbitmq/connection.js +140 -0
- package/dist/rabbitmq/main.d.ts +5 -0
- package/dist/rabbitmq/main.js +21 -0
- package/dist/rabbitmq/publisher.d.ts +29 -0
- package/dist/rabbitmq/publisher.js +98 -0
- package/dist/rabbitmq/pubsub.d.ts +15 -0
- package/dist/rabbitmq/pubsub.js +37 -0
- package/dist/rabbitmq/subscriber.d.ts +33 -0
- package/dist/rabbitmq/subscriber.js +197 -0
- package/dist/rabbitmq/utility.d.ts +6 -0
- package/dist/rabbitmq/utility.js +11 -0
- package/dist/subscriber.d.ts +28 -0
- package/dist/subscriber.js +140 -0
- package/package.json +54 -0
- package/src/bullmq/README.md +63 -0
- package/src/rabbitmq/README.md +184 -0
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @palmetto/pubsub
|
|
2
|
+
|
|
3
|
+
Provides a standard mechanism to publish and subscribe to events using a zod v4 schema. The schema is used to validate that the message published or subscribed is valid.
|
|
4
|
+
|
|
5
|
+
Messages pub/sub is done using JSON strings. The JSON strings are converted into the type using the schema. JSON strings that fail the schema parsing are rejected.
|
|
6
|
+
|
|
7
|
+
Subscribers must return one of 3 results from MessageResult:
|
|
8
|
+
|
|
9
|
+
- Ok - the message was processed successfully
|
|
10
|
+
- Retry - the message should be retried.
|
|
11
|
+
- Fail - the message processing failed and can be discarded [or dead-lettered, depending on the queue implementation]
|
|
12
|
+
|
|
13
|
+
It is up to the pub/sub implementations what to do with rejected messages. Options include:
|
|
14
|
+
|
|
15
|
+
- Dead letter queue
|
|
16
|
+
- Log an error
|
|
17
|
+
- Retry the message
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
yarn add @palmetto/pubsub zod
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
1. Create a publisher and send a message
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { z as z4 } from "zod/v4";
|
|
31
|
+
import { IdMetaSchema, Publisher, RabbitMqPublisher, RabbitQueueExchangeConfiguration } from "@palmetto/pubsub";
|
|
32
|
+
|
|
33
|
+
const rabbitMqPublisher = new RabbitMqPublisher(...);
|
|
34
|
+
|
|
35
|
+
const schema = IdMetaSchema.extend({
|
|
36
|
+
message: z4.string(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const Model = z.infer<typeof schema>;
|
|
40
|
+
|
|
41
|
+
const schemaConfig: RabbitQueueExchangeConfiguration = {
|
|
42
|
+
name: "TestModel",
|
|
43
|
+
schema: TestModelSchema,
|
|
44
|
+
transport: RABBITMQ_TRANSPORT,
|
|
45
|
+
type: "direct",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const publisher = new Publisher(console, [rabbitMqPublisher]);
|
|
49
|
+
|
|
50
|
+
const message: Model = {
|
|
51
|
+
id: undefined,
|
|
52
|
+
meta: undefined,
|
|
53
|
+
message: "Hello, world!",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
await publisher.publish(schemaConfig, message);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
1. Create subscriber and subscriber to a message
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { z as z4 } from "zod/v4";
|
|
63
|
+
import { BaseMessage, IdMetaSchema, MessageResult, Subscriber, RabbitMqSubscriber } from "@palmetto/pubsub";
|
|
64
|
+
|
|
65
|
+
const rabbitMqSubscriber = new RabbitMqSubscriber(...);
|
|
66
|
+
|
|
67
|
+
const schema = IdMetaSchema.extend({
|
|
68
|
+
message: z4.string(),
|
|
69
|
+
code: z4.string(),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const Model = z.infer<typeof schema>;
|
|
73
|
+
|
|
74
|
+
const schemaConfig: RabbitQueueExchangeConfiguration = {
|
|
75
|
+
name: "TestModel",
|
|
76
|
+
schema: TestModelSchema,
|
|
77
|
+
retryDelay: 1_000,
|
|
78
|
+
retries: 20,
|
|
79
|
+
transport: RABBITMQ_TRANSPORT,
|
|
80
|
+
type: "direct",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function onMessage(message: Model) {
|
|
84
|
+
console.log(message.message);
|
|
85
|
+
const success = ...;
|
|
86
|
+
|
|
87
|
+
return success ? MessageResult.Ok : MessageResult.Retry;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const subscriber = new Subscriber(console, [rabbitMqSubscriber]);
|
|
91
|
+
|
|
92
|
+
await subscriber.startSubscribe(schemaConfig, onMessage);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Defining schemas
|
|
96
|
+
|
|
97
|
+
All schemas must be defined using `zod/z4`. By convention, all messages should extend the `IdMetaSchema` schema, but it is not strictly required.
|
|
98
|
+
|
|
99
|
+
IdMetaSchema provides several fields useful for message tracking:
|
|
100
|
+
|
|
101
|
+
- id: a string GUID representing the unique message
|
|
102
|
+
- meta.createdAt: when the message was originally sent (ISO datetime string)
|
|
103
|
+
- meta.schemaId: a string value representing the schema
|
|
104
|
+
- currently: a hash of the JSON schema itself
|
|
105
|
+
- future use: an id into a schema registry that supports schema evolution
|
|
106
|
+
- meta.publishedBy: [[future use]] the name of the service that published the message
|
|
107
|
+
|
|
108
|
+
## Handling errors
|
|
109
|
+
|
|
110
|
+
### Schema failures
|
|
111
|
+
|
|
112
|
+
The message is checked against the provided schema for every publish and subscribe.
|
|
113
|
+
|
|
114
|
+
- Publisher: catch the `SchemaValidationError` exception.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
try {
|
|
118
|
+
await publisher.publish(config, message);
|
|
119
|
+
} catch (err: unknown) {
|
|
120
|
+
if (error instanceof SchemaValidationError) {
|
|
121
|
+
// TODO
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- Subscriber: add an event monitor on `schemaError`
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
subscriber.on(
|
|
130
|
+
"schemaError",
|
|
131
|
+
(
|
|
132
|
+
message: string, // the JSON string of the message
|
|
133
|
+
context: MessageContext, // the context of the message provided by the SubscriptionProvider
|
|
134
|
+
config: PubSubConfiguration, // the configuration for the message (eg: BullMqQueueConfiguration, RabbitQueueExchangeConfiguration)
|
|
135
|
+
) => {
|
|
136
|
+
// TODO
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Subscriber errors
|
|
142
|
+
|
|
143
|
+
When a subscriber handler fails, the SubscriberProvider should retry or fail the message.
|
|
144
|
+
|
|
145
|
+
Retries should occur when
|
|
146
|
+
|
|
147
|
+
- The user-provided handler throws an exception
|
|
148
|
+
- The handler returns MessageResult.Retry
|
|
149
|
+
|
|
150
|
+
It is up to the provider to obey the `retries` and `retryDelay` configuration properties.
|
|
151
|
+
|
|
152
|
+
### Publisher errors
|
|
153
|
+
|
|
154
|
+
When a message publish fails, it is up to the calling code to handle exceptions.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MessageContext, PubSubConfiguration } from "../interfaces";
|
|
2
|
+
export interface BullMqQueueConfiguration extends PubSubConfiguration {
|
|
3
|
+
/**
|
|
4
|
+
* The name of the queue
|
|
5
|
+
*/
|
|
6
|
+
name: string;
|
|
7
|
+
/**
|
|
8
|
+
* The name of the job to use
|
|
9
|
+
*/
|
|
10
|
+
job?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface BullMqMessageContext extends MessageContext {
|
|
13
|
+
/**
|
|
14
|
+
* name of the bull mq job
|
|
15
|
+
*/
|
|
16
|
+
job: string;
|
|
17
|
+
/**
|
|
18
|
+
* Provide an optional reply for the job here
|
|
19
|
+
*/
|
|
20
|
+
reply?: unknown;
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./config.js"), exports);
|
|
18
|
+
__exportStar(require("./connection.js"), exports);
|
|
19
|
+
__exportStar(require("./publisher.js"), exports);
|
|
20
|
+
__exportStar(require("./subscriber.js"), exports);
|
|
21
|
+
__exportStar(require("./pubsub.js"), exports);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type * as bullmq from "bullmq";
|
|
2
|
+
import { Logger, PublisherProvider } from "../interfaces";
|
|
3
|
+
import { BullMqQueueConfiguration } from "./config";
|
|
4
|
+
export declare class BullMqPublisher implements PublisherProvider {
|
|
5
|
+
private readonly connection;
|
|
6
|
+
private readonly logger;
|
|
7
|
+
private queues;
|
|
8
|
+
constructor(connection: bullmq.ConnectionOptions, logger: Logger);
|
|
9
|
+
readonly transport: string;
|
|
10
|
+
private getQueue;
|
|
11
|
+
publish(config: BullMqQueueConfiguration, message: string): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.BullMqPublisher = void 0;
|
|
13
|
+
const connection_1 = require("./connection");
|
|
14
|
+
const lazy_load_1 = require("../lazy-load");
|
|
15
|
+
class BullMqPublisher {
|
|
16
|
+
constructor(connection, logger) {
|
|
17
|
+
this.connection = connection;
|
|
18
|
+
this.logger = logger;
|
|
19
|
+
this.queues = new Map();
|
|
20
|
+
this.transport = connection_1.BULLMQ_TRANSPORT;
|
|
21
|
+
}
|
|
22
|
+
getQueue(config) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
let queue = this.queues.get(config);
|
|
25
|
+
if (queue) {
|
|
26
|
+
return queue;
|
|
27
|
+
}
|
|
28
|
+
const BullMqPackage = yield (0, lazy_load_1.lazyLoad)({
|
|
29
|
+
packageName: "bullmq",
|
|
30
|
+
context: "BullMqPublisher",
|
|
31
|
+
});
|
|
32
|
+
queue = new BullMqPackage.Queue(config.name, {
|
|
33
|
+
connection: this.connection,
|
|
34
|
+
});
|
|
35
|
+
this.queues.set(config, queue);
|
|
36
|
+
return queue;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
publish(config, message) {
|
|
40
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
41
|
+
var _a, _b;
|
|
42
|
+
const queue = yield this.getQueue(config);
|
|
43
|
+
(_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `Publishing message to ${queue.name} - ${message}`);
|
|
44
|
+
yield queue.add(config.job || connection_1.BULLMQ_DEFAULTJOB, message, {
|
|
45
|
+
attempts: config.retries,
|
|
46
|
+
backoff: {
|
|
47
|
+
type: "fixed",
|
|
48
|
+
delay: config.retryDelay || 30000,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
close() {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
for (const queue of this.queues.values()) {
|
|
56
|
+
yield queue.close();
|
|
57
|
+
}
|
|
58
|
+
this.queues.clear();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.BullMqPublisher = BullMqPublisher;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Logger, MessageContext, MessageResult, PubSubProvider, StopSubscribe } from "../interfaces";
|
|
2
|
+
import { BullMqQueueConfiguration } from "./config";
|
|
3
|
+
import type { ConnectionOptions } from "bullmq";
|
|
4
|
+
export declare class BullMqPubSubProvider implements PubSubProvider {
|
|
5
|
+
private readonly publisher;
|
|
6
|
+
private readonly subscriber;
|
|
7
|
+
constructor(connection: ConnectionOptions, logger: Logger);
|
|
8
|
+
readonly transport: string;
|
|
9
|
+
publish(config: BullMqQueueConfiguration, message: string): Promise<void>;
|
|
10
|
+
startSubscribe(config: BullMqQueueConfiguration, onMessage: (s: string, context: MessageContext) => Promise<MessageResult> | MessageResult): Promise<StopSubscribe>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.BullMqPubSubProvider = void 0;
|
|
13
|
+
const connection_1 = require("./connection");
|
|
14
|
+
const publisher_1 = require("./publisher");
|
|
15
|
+
const subscriber_1 = require("./subscriber");
|
|
16
|
+
class BullMqPubSubProvider {
|
|
17
|
+
constructor(connection, logger) {
|
|
18
|
+
this.transport = connection_1.BULLMQ_TRANSPORT;
|
|
19
|
+
this.publisher = new publisher_1.BullMqPublisher(connection, logger);
|
|
20
|
+
this.subscriber = new subscriber_1.BullMqSubscriber(connection, logger);
|
|
21
|
+
}
|
|
22
|
+
publish(config, message) {
|
|
23
|
+
return this.publisher.publish(config, message);
|
|
24
|
+
}
|
|
25
|
+
startSubscribe(config, onMessage) {
|
|
26
|
+
return this.subscriber.startSubscribe(config, onMessage);
|
|
27
|
+
}
|
|
28
|
+
close() {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
yield this.subscriber.close();
|
|
31
|
+
yield this.publisher.close();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.BullMqPubSubProvider = BullMqPubSubProvider;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type * as bullmq from "bullmq";
|
|
2
|
+
import { Logger, MessageResult, StopSubscribe, SubscriberProvider } from "../interfaces";
|
|
3
|
+
import { BullMqMessageContext, BullMqQueueConfiguration } from "./config";
|
|
4
|
+
declare class SubscribedMessage {
|
|
5
|
+
private readonly owner;
|
|
6
|
+
private readonly worker;
|
|
7
|
+
private readonly logger;
|
|
8
|
+
constructor(owner: BullMqSubscriber, worker: bullmq.Worker, logger: Logger);
|
|
9
|
+
stopSubscribe(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare class MessageFailError extends Error {
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
|
14
|
+
export declare class MessageRetryError extends Error {
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
|
17
|
+
export declare class BullMqSubscriber implements SubscriberProvider {
|
|
18
|
+
private readonly connection;
|
|
19
|
+
private readonly logger;
|
|
20
|
+
private readonly stops;
|
|
21
|
+
constructor(connection: bullmq.ConnectionOptions, logger: Logger);
|
|
22
|
+
readonly transport: string;
|
|
23
|
+
removeSubscriber(subscribedMessage: SubscribedMessage): void;
|
|
24
|
+
startSubscribe(config: BullMqQueueConfiguration, onMessage: (s: string, context: BullMqMessageContext) => Promise<MessageResult> | MessageResult): Promise<StopSubscribe>;
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.BullMqSubscriber = exports.MessageRetryError = exports.MessageFailError = void 0;
|
|
13
|
+
const interfaces_1 = require("../interfaces");
|
|
14
|
+
const connection_1 = require("./connection");
|
|
15
|
+
const lazy_load_1 = require("../lazy-load");
|
|
16
|
+
class SubscribedMessage {
|
|
17
|
+
constructor(owner, worker, logger) {
|
|
18
|
+
this.owner = owner;
|
|
19
|
+
this.worker = worker;
|
|
20
|
+
this.logger = logger;
|
|
21
|
+
}
|
|
22
|
+
stopSubscribe() {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
(_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `BullMq closing worker for ${this.worker.name}`);
|
|
26
|
+
yield this.worker.close();
|
|
27
|
+
this.owner.removeSubscriber(this);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
class MessageFailError extends Error {
|
|
32
|
+
constructor() {
|
|
33
|
+
super("Handler asked to fail this job");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.MessageFailError = MessageFailError;
|
|
37
|
+
class MessageRetryError extends Error {
|
|
38
|
+
constructor() {
|
|
39
|
+
super("Handler asked to retry this job");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.MessageRetryError = MessageRetryError;
|
|
43
|
+
class BullMqSubscriber {
|
|
44
|
+
constructor(connection, logger) {
|
|
45
|
+
this.connection = connection;
|
|
46
|
+
this.logger = logger;
|
|
47
|
+
this.stops = new Map();
|
|
48
|
+
this.transport = connection_1.BULLMQ_TRANSPORT;
|
|
49
|
+
}
|
|
50
|
+
removeSubscriber(subscribedMessage) {
|
|
51
|
+
for (const [key, value] of this.stops) {
|
|
52
|
+
if (value === subscribedMessage) {
|
|
53
|
+
this.stops.delete(key);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
startSubscribe(config, onMessage) {
|
|
59
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
60
|
+
var _a, _b, _c, _d;
|
|
61
|
+
const BullMqPackage = yield (0, lazy_load_1.lazyLoad)({
|
|
62
|
+
packageName: "bullmq",
|
|
63
|
+
context: "BullMqSubscriber",
|
|
64
|
+
});
|
|
65
|
+
const worker = new BullMqPackage.Worker(config.name, (job) => __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
const context = {
|
|
67
|
+
job: job.name,
|
|
68
|
+
retries: job.attemptsMade,
|
|
69
|
+
firstSent: new Date(job.timestamp),
|
|
70
|
+
// lastSent: new Date(job.attemptsStarted),
|
|
71
|
+
};
|
|
72
|
+
const result = yield onMessage(job.data, context);
|
|
73
|
+
if (result === interfaces_1.MessageResult.Ok) {
|
|
74
|
+
return context.reply;
|
|
75
|
+
}
|
|
76
|
+
if (result === interfaces_1.MessageResult.Retry) {
|
|
77
|
+
throw new MessageRetryError();
|
|
78
|
+
}
|
|
79
|
+
throw new BullMqPackage.UnrecoverableError("Handler asked to fail this job");
|
|
80
|
+
}), {
|
|
81
|
+
connection: this.connection,
|
|
82
|
+
autorun: false,
|
|
83
|
+
});
|
|
84
|
+
worker.on("failed", (job, err) => {
|
|
85
|
+
if (!job) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.logger.log(`Job failed ${err}`);
|
|
89
|
+
});
|
|
90
|
+
worker.on("error", (err) => {
|
|
91
|
+
this.logger.error(`BullMq PubSub handler exception: ${err}`);
|
|
92
|
+
});
|
|
93
|
+
(_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `BullMq PubSub subscriber starting for ${worker.name}`);
|
|
94
|
+
worker
|
|
95
|
+
.run()
|
|
96
|
+
.then(() => {
|
|
97
|
+
var _a, _b;
|
|
98
|
+
(_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `BullMq PubSub subscriber stopped for ${worker.name}`);
|
|
99
|
+
})
|
|
100
|
+
.catch((err) => {
|
|
101
|
+
this.logger.error(`BullMq PubSub subscriber crashed for ${worker.name}`, err);
|
|
102
|
+
});
|
|
103
|
+
const subscribedMessage = new SubscribedMessage(this, worker, this.logger);
|
|
104
|
+
this.stops.set(config, subscribedMessage);
|
|
105
|
+
(_d = (_c = this.logger).debug) === null || _d === void 0 ? void 0 : _d.call(_c, `BullMq PubSub subscriber started for ${worker.name}`);
|
|
106
|
+
return () => subscribedMessage.stopSubscribe();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
close() {
|
|
110
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
111
|
+
for (const sub of this.stops.values()) {
|
|
112
|
+
yield sub.stopSubscribe();
|
|
113
|
+
}
|
|
114
|
+
this.stops.clear();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.BullMqSubscriber = BullMqSubscriber;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class AlreadySubscribingError extends Error {
|
|
2
|
+
name: string;
|
|
3
|
+
constructor(name: string);
|
|
4
|
+
static isAlreadySubscribingError(err: unknown): err is AlreadySubscribingError;
|
|
5
|
+
}
|
|
6
|
+
export declare class ConfigurationError extends Error {
|
|
7
|
+
static isConfigurationError(err: unknown): err is ConfigurationError;
|
|
8
|
+
}
|
|
9
|
+
export declare class MissingPubSubProviderError extends Error {
|
|
10
|
+
static isMissingPubSubProviderError(err: unknown): err is MissingPubSubProviderError;
|
|
11
|
+
}
|
|
12
|
+
export declare class PublishError extends Error {
|
|
13
|
+
static isPublishError(err: unknown): err is PublishError;
|
|
14
|
+
}
|
|
15
|
+
export declare class SchemaValidationError extends Error {
|
|
16
|
+
static isSchemaValidationError(err: unknown): err is SchemaValidationError;
|
|
17
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchemaValidationError = exports.PublishError = exports.MissingPubSubProviderError = exports.ConfigurationError = exports.AlreadySubscribingError = void 0;
|
|
4
|
+
class AlreadySubscribingError extends Error {
|
|
5
|
+
constructor(name) {
|
|
6
|
+
super(`Subscription for ${name} already started`);
|
|
7
|
+
this.name = name;
|
|
8
|
+
}
|
|
9
|
+
static isAlreadySubscribingError(err) {
|
|
10
|
+
return err instanceof AlreadySubscribingError;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.AlreadySubscribingError = AlreadySubscribingError;
|
|
14
|
+
class ConfigurationError extends Error {
|
|
15
|
+
static isConfigurationError(err) {
|
|
16
|
+
return err instanceof ConfigurationError;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.ConfigurationError = ConfigurationError;
|
|
20
|
+
class MissingPubSubProviderError extends Error {
|
|
21
|
+
static isMissingPubSubProviderError(err) {
|
|
22
|
+
return err instanceof MissingPubSubProviderError;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.MissingPubSubProviderError = MissingPubSubProviderError;
|
|
26
|
+
class PublishError extends Error {
|
|
27
|
+
static isPublishError(err) {
|
|
28
|
+
return err instanceof PublishError;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.PublishError = PublishError;
|
|
32
|
+
class SchemaValidationError extends Error {
|
|
33
|
+
static isSchemaValidationError(err) {
|
|
34
|
+
return err instanceof SchemaValidationError;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.SchemaValidationError = SchemaValidationError;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z as z4 } from "zod/v4";
|
|
2
|
+
export declare const MetaSchema: z4.ZodObject<{
|
|
3
|
+
createdAt: z4.ZodISODateTime;
|
|
4
|
+
schemaId: z4.ZodString;
|
|
5
|
+
publishedBy: z4.ZodString;
|
|
6
|
+
}, z4.core.$strip>;
|
|
7
|
+
export declare const IdMetaSchema: z4.ZodObject<{
|
|
8
|
+
id: z4.ZodOptional<z4.ZodString>;
|
|
9
|
+
meta: z4.ZodOptional<z4.ZodObject<{
|
|
10
|
+
createdAt: z4.ZodISODateTime;
|
|
11
|
+
schemaId: z4.ZodString;
|
|
12
|
+
publishedBy: z4.ZodString;
|
|
13
|
+
}, z4.core.$strip>>;
|
|
14
|
+
}, z4.core.$strip>;
|
|
15
|
+
export type Meta = z4.infer<typeof MetaSchema>;
|
|
16
|
+
/**
|
|
17
|
+
* All messages should be based on this schema
|
|
18
|
+
*/
|
|
19
|
+
export type BaseMessage = z4.infer<typeof IdMetaSchema>;
|
|
20
|
+
/**
|
|
21
|
+
* All pubsub publishers must implement this interface
|
|
22
|
+
*/
|
|
23
|
+
export interface PublisherProvider {
|
|
24
|
+
transport: string;
|
|
25
|
+
publish(config: PubSubConfiguration, message: string): Promise<void> | void;
|
|
26
|
+
close(): Promise<void> | void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Minimal configuration needed to publish or subscribe to a message
|
|
30
|
+
*/
|
|
31
|
+
export interface PubSubConfiguration {
|
|
32
|
+
/**
|
|
33
|
+
* The name of the message
|
|
34
|
+
*/
|
|
35
|
+
name: string;
|
|
36
|
+
/**
|
|
37
|
+
* The schema for the message - the message is verfied during publish and subscribe
|
|
38
|
+
*/
|
|
39
|
+
schema: z4.ZodType;
|
|
40
|
+
/**
|
|
41
|
+
* The message transport to use (defined by the event-pubsub implementations)
|
|
42
|
+
*/
|
|
43
|
+
transport: string;
|
|
44
|
+
/**
|
|
45
|
+
* Maximum number of times to retry a message
|
|
46
|
+
*/
|
|
47
|
+
retries?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Minimum amount of time to wait, in milliseconds, before retrying a message
|
|
50
|
+
*/
|
|
51
|
+
retryDelay?: number;
|
|
52
|
+
}
|
|
53
|
+
export declare enum MessageResult {
|
|
54
|
+
/**
|
|
55
|
+
* the message was handled successfully or can otherwise be ignored
|
|
56
|
+
*/
|
|
57
|
+
Ok = "ok",
|
|
58
|
+
/**
|
|
59
|
+
* The message should be retried
|
|
60
|
+
*/
|
|
61
|
+
Retry = "retry",
|
|
62
|
+
/**
|
|
63
|
+
* The message failed and is either discarded or stored in a dead-letter queue
|
|
64
|
+
*/
|
|
65
|
+
Fail = "fail"
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Simple interface for logging, should match console/NestJS LoggerService/etc.
|
|
69
|
+
*/
|
|
70
|
+
export interface Logger {
|
|
71
|
+
/**
|
|
72
|
+
* Write a 'log' level log.
|
|
73
|
+
*/
|
|
74
|
+
log(message: unknown, ...optionalParams: unknown[]): unknown;
|
|
75
|
+
/**
|
|
76
|
+
* Write an 'error' level log.
|
|
77
|
+
*/
|
|
78
|
+
error(message: unknown, ...optionalParams: unknown[]): unknown;
|
|
79
|
+
/**
|
|
80
|
+
* Write a 'warn' level log.
|
|
81
|
+
*/
|
|
82
|
+
warn(message: unknown, ...optionalParams: unknown[]): unknown;
|
|
83
|
+
/**
|
|
84
|
+
* Write a 'debug' level log.
|
|
85
|
+
*/
|
|
86
|
+
debug?(message: unknown, ...optionalParams: unknown[]): unknown;
|
|
87
|
+
/**
|
|
88
|
+
* Write a 'verbose' level log.
|
|
89
|
+
*/
|
|
90
|
+
verbose?(message: unknown, ...optionalParams: unknown[]): unknown;
|
|
91
|
+
/**
|
|
92
|
+
* Write a 'fatal' level log.
|
|
93
|
+
*/
|
|
94
|
+
fatal?(message: unknown, ...optionalParams: unknown[]): unknown;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A callback the stops the subscriber
|
|
98
|
+
*/
|
|
99
|
+
export type StopSubscribe = () => Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* All pubsub subscribers must implement this interface
|
|
102
|
+
*/
|
|
103
|
+
export interface SubscriberProvider {
|
|
104
|
+
transport: string;
|
|
105
|
+
startSubscribe(config: PubSubConfiguration, onMessage: (s: string, context: MessageContext) => Promise<MessageResult> | MessageResult): Promise<StopSubscribe> | StopSubscribe;
|
|
106
|
+
close(): Promise<void> | void;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* A class that implements both the publisher and subscriber
|
|
110
|
+
*/
|
|
111
|
+
export type PubSubProvider = PublisherProvider & SubscriberProvider;
|
|
112
|
+
/**
|
|
113
|
+
* A class that implements either a publisher, subscriber or both
|
|
114
|
+
*/
|
|
115
|
+
export type PubOrSubProvider = PubSubProvider | PublisherProvider | SubscriberProvider;
|
|
116
|
+
export interface MessageContext {
|
|
117
|
+
firstSent?: Date;
|
|
118
|
+
lastSent?: Date;
|
|
119
|
+
retries?: number;
|
|
120
|
+
}
|