@avtechno/sfr 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 +72 -0
- package/dist/example.mjs +33 -0
- package/dist/index.mjs +84 -0
- package/dist/logger.mjs +37 -0
- package/dist/mq.mjs +217 -0
- package/dist/sfr-pipeline.mjs +465 -0
- package/dist/templates.mjs +47 -0
- package/dist/types/example.d.mts +11 -0
- package/dist/types/index.d.mts +7 -0
- package/dist/types/logger.d.mts +3 -0
- package/dist/types/mq.d.mts +105 -0
- package/dist/types/sfr-pipeline.d.mts +33 -0
- package/dist/types/templates.d.mts +8 -0
- package/dist/types/util.d.mts +7 -0
- package/dist/util.mjs +25 -0
- package/package.json +51 -0
- package/src/example.mts +35 -0
- package/src/index.mts +99 -0
- package/src/logger.mts +52 -0
- package/src/mq.mts +236 -0
- package/src/sfr-pipeline.mts +508 -0
- package/src/templates.mts +55 -0
- package/src/types/index.d.ts +395 -0
- package/src/util.mts +32 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function REST<V, H, C>(struct: RESTHandlerDescriptor<V, H, C>): H & C;
|
|
2
|
+
export declare function WS<V, H, C>(struct: WSHandlerDescriptor<V, H, C>): {
|
|
3
|
+
validators: RequestValidators;
|
|
4
|
+
controllers: RequestControllers;
|
|
5
|
+
handlers: WSRequestHandlers;
|
|
6
|
+
cfg: SFRConfig;
|
|
7
|
+
};
|
|
8
|
+
export declare function MQ<V, H, C>(struct: MQHandlerDescriptor<V, H, C>): H & C;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Response } from "express";
|
|
2
|
+
import Joi from "joi";
|
|
3
|
+
export declare function get_stats(dirs: string[], base_dir: string): Promise<string[]>;
|
|
4
|
+
export declare function assess_namespace(stats: string[], dir: string): Promise<any>;
|
|
5
|
+
export declare const template: (rule: object, v: object) => string | false;
|
|
6
|
+
export declare const object_id: Joi.StringSchema<string>;
|
|
7
|
+
export declare const handle_res: (controller: Promise<any>, res: Response) => void;
|
package/dist/util.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import Joi from "joi";
|
|
4
|
+
export async function get_stats(dirs, base_dir) {
|
|
5
|
+
let temp = await Promise.all(dirs.map((v) => fs.lstat(path.join(base_dir, v)).then((stats) => ({ stats, dir: v }))));
|
|
6
|
+
const result = temp.filter(({ stats }) => stats.isFile()).map((v) => v.dir);
|
|
7
|
+
return result;
|
|
8
|
+
}
|
|
9
|
+
export async function assess_namespace(stats, dir) {
|
|
10
|
+
let dirs = stats.map((v) => `file:///${dir.replaceAll("\\", "/")}/${v}`);
|
|
11
|
+
const result = await Promise.all(dirs.map((d) => import(d).then((v) => [d.replace(".mjs", "").substring(d.lastIndexOf("/") + 1), v.default]))).then(Object.fromEntries);
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
export const template = (rule, v) => _validate(Joi.object(rule).validate(v));
|
|
15
|
+
function _validate(expression) {
|
|
16
|
+
let result = expression.error;
|
|
17
|
+
return result ? result.details[0].message : false;
|
|
18
|
+
}
|
|
19
|
+
export const object_id = Joi.string().hex().length(24).required();
|
|
20
|
+
/* QoLs */
|
|
21
|
+
export const handle_res = (controller, res) => {
|
|
22
|
+
controller
|
|
23
|
+
.then((data) => res.json({ data }))
|
|
24
|
+
.catch((error) => res.status(400).json({ error }));
|
|
25
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@avtechno/sfr",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An opinionated way of writing services using ExpressJS.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"lib",
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"main": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/types/index.d.mts",
|
|
13
|
+
"exports": {
|
|
14
|
+
"types": "./dist/types/index.d.mts",
|
|
15
|
+
"default": "./dist/index.mjs",
|
|
16
|
+
"import": ["./dist/index.mjs", "./dist/mq.mjs"]
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"clean": "rimraf ./dist",
|
|
20
|
+
"build": "yarn clean && tsc --project tsconfig.json",
|
|
21
|
+
"build:watch": "yarn clean && tsc --project tsconfig.json -w",
|
|
22
|
+
"postversion": "yarn build"
|
|
23
|
+
},
|
|
24
|
+
"prepublish": "tsc",
|
|
25
|
+
"author": "Emmanuel Abellana",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"amqplib": "0.10.7",
|
|
29
|
+
"express": "^4.18.2",
|
|
30
|
+
"express-session": "^1.17.3",
|
|
31
|
+
"file-type": "^18.5.0",
|
|
32
|
+
"joi": "^17.11.0",
|
|
33
|
+
"joi-to-swagger": "^6.2.0",
|
|
34
|
+
"js-yaml": "^4.1.0",
|
|
35
|
+
"multer": "^1.4.5-lts.1",
|
|
36
|
+
"socket.io": "^4.7.2",
|
|
37
|
+
"winston": "^3.17.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/amqplib": "^0.10.5",
|
|
41
|
+
"@types/express": "^4.17.19",
|
|
42
|
+
"@types/express-session": "^1.18.0",
|
|
43
|
+
"@types/js-yaml": "^4.0.7",
|
|
44
|
+
"@types/multer": "^1.4.11",
|
|
45
|
+
"@types/openapi-v3": "^3.0.0",
|
|
46
|
+
"openapi-types": "^12.1.3",
|
|
47
|
+
"rimraf": "^5.0.1",
|
|
48
|
+
"typedoc": "^0.25.2",
|
|
49
|
+
"typescript": "^5.2.2"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/example.mts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Joi from "joi";
|
|
2
|
+
import { REST } from "./templates.mjs";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export default REST({
|
|
6
|
+
cfg : {
|
|
7
|
+
base_dir : "example",
|
|
8
|
+
public : false,
|
|
9
|
+
},
|
|
10
|
+
validators: {
|
|
11
|
+
"get-something": {
|
|
12
|
+
"name": Joi.string().required(),
|
|
13
|
+
"age": Joi.number().required(),
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
handlers: {
|
|
17
|
+
GET : {
|
|
18
|
+
"get-something": {
|
|
19
|
+
description : "Get something I guess?",
|
|
20
|
+
async fn(req, res){
|
|
21
|
+
const result = await this.db_call()
|
|
22
|
+
res.status(200).json({
|
|
23
|
+
data: result
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
controllers: {
|
|
30
|
+
async db_call(){
|
|
31
|
+
/* Perform database calls here through accessing the "this" context (if injections are set) */
|
|
32
|
+
return "Something";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
})
|
package/src/index.mts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/// <reference path="types/index.d.ts" />
|
|
2
|
+
import yaml from "js-yaml";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
|
|
6
|
+
import { REST, WS, MQ } from "./templates.mjs";
|
|
7
|
+
import { SFRPipeline } from "./sfr-pipeline.mjs";
|
|
8
|
+
import { MQLib } from "./mq.mjs";
|
|
9
|
+
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
|
|
12
|
+
export default async (cfg: ParserCFG, oas_cfg: OASConfig, connectors: SFRProtocols, base_url?: string):Promise<ServiceManifest> => {
|
|
13
|
+
//TODO: Verify connectors
|
|
14
|
+
const sfr = new SFRPipeline(cfg, oas_cfg, connectors);
|
|
15
|
+
|
|
16
|
+
// Returned service artifacts for both OpenAPI and AsyncAPI are written into the server directory
|
|
17
|
+
// An express static endpoint is pointed to this directory for service directory.
|
|
18
|
+
const documents = await sfr.init(base_url);
|
|
19
|
+
|
|
20
|
+
write_service_discovery(cfg, documents);//Writes services to output dir (cfg.out)
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
...oas_cfg,
|
|
24
|
+
documents
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
async function write_service_discovery(cfg: ParserCFG, documents: ServiceDocuments) {
|
|
29
|
+
//Setup files in case they do not exist.
|
|
30
|
+
|
|
31
|
+
//Setup output folder
|
|
32
|
+
await fs.mkdir(path.join(cwd, cfg.out), { recursive: true });
|
|
33
|
+
|
|
34
|
+
// Loop over each protocol
|
|
35
|
+
for (let [protocol, documentation] of Object.entries(documents)) {
|
|
36
|
+
//Strictly await for setup to create dirs for each protocol.
|
|
37
|
+
await fs.mkdir(path.join(cwd, cfg.out, protocol.toLowerCase()), { recursive: true });
|
|
38
|
+
//Convert documentation into service manifest.
|
|
39
|
+
|
|
40
|
+
//OpenAPI Documents : paths
|
|
41
|
+
//AsyncAPI Documents : channels
|
|
42
|
+
switch (protocol) {
|
|
43
|
+
case "REST": {
|
|
44
|
+
documentation = documentation as OAPI_Document;
|
|
45
|
+
for (const [path, methods] of Object.entries(documentation.paths)) {
|
|
46
|
+
write_to_file(cfg, `rest/${path}`, methods);
|
|
47
|
+
}
|
|
48
|
+
} break;
|
|
49
|
+
|
|
50
|
+
case "WS": {
|
|
51
|
+
|
|
52
|
+
} break;
|
|
53
|
+
|
|
54
|
+
case "MQ": {
|
|
55
|
+
documentation = documentation as AsyncAPIDocument;
|
|
56
|
+
write_to_file(cfg, "mq/index.yaml", documentation);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//Tasked with recursively creating directories and spec files.
|
|
63
|
+
async function write_to_file(cfg: ParserCFG, dir: string, data: object) {
|
|
64
|
+
//Path is a slash(/) delimited string, with the end delimiter indicating the filename to be used.
|
|
65
|
+
const paths = dir.split("/");
|
|
66
|
+
|
|
67
|
+
//Indicates a root file
|
|
68
|
+
if (paths.length === 1) {
|
|
69
|
+
await fs.writeFile(path.join(cwd, cfg.out, paths[0]), yaml.dump(data), { flag: "w+" });
|
|
70
|
+
} else {//Indicates a nested file
|
|
71
|
+
const file = paths.pop();//Pops the path array to be used for dir creation
|
|
72
|
+
await fs.mkdir(path.join(cwd, cfg.out, ...paths), { recursive: true });
|
|
73
|
+
fs.writeFile(path.join(cwd, cfg.out, ...paths, `${file}.yml`), yaml.dump(data), { flag: "w+" });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function inject(injections : InjectedFacilities){
|
|
78
|
+
SFRPipeline.injections.rest.handlers = {
|
|
79
|
+
...SFRPipeline.injections.rest.handlers,
|
|
80
|
+
...injections.handlers
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
SFRPipeline.injections.rest.controllers = {
|
|
84
|
+
...SFRPipeline.injections.rest.controllers,
|
|
85
|
+
...injections.controllers
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
SFRPipeline.injections.mq.handlers = {
|
|
89
|
+
...SFRPipeline.injections.mq.handlers,
|
|
90
|
+
...injections.handlers
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
SFRPipeline.injections.mq.controllers = {
|
|
94
|
+
...SFRPipeline.injections.mq.controllers,
|
|
95
|
+
...injections.controllers
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { REST, WS, MQ, MQLib, inject };
|
package/src/logger.mts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
|
|
3
|
+
// Define log levels
|
|
4
|
+
const levels = {
|
|
5
|
+
error: 0,
|
|
6
|
+
warn: 1,
|
|
7
|
+
info: 2,
|
|
8
|
+
http: 3,
|
|
9
|
+
verbose: 4,
|
|
10
|
+
debug: 5,
|
|
11
|
+
silly: 6,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Define colors for each level
|
|
15
|
+
const colors = {
|
|
16
|
+
error: 'red',
|
|
17
|
+
warn: 'yellow',
|
|
18
|
+
info: 'green',
|
|
19
|
+
http: 'magenta',
|
|
20
|
+
verbose: 'cyan',
|
|
21
|
+
debug: 'blue',
|
|
22
|
+
silly: 'gray',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Add colors to Winston
|
|
26
|
+
winston.addColors(colors);
|
|
27
|
+
|
|
28
|
+
// Define the format for logs
|
|
29
|
+
const format = winston.format.combine(
|
|
30
|
+
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
|
|
31
|
+
winston.format.colorize({ all: true }),
|
|
32
|
+
winston.format.printf(
|
|
33
|
+
(info) =>
|
|
34
|
+
`${info.timestamp} ${info.level}: ${info.message}${
|
|
35
|
+
info.metadata ? ` ${JSON.stringify(info.metadata)}` : ''
|
|
36
|
+
}`
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Create the Winston logger
|
|
41
|
+
const logger = winston.createLogger({
|
|
42
|
+
level: process.env.NODE_ENV === 'development' && Boolean(process.env.DEBUG_SFR) ? 'debug' : 'info',
|
|
43
|
+
levels,
|
|
44
|
+
format,
|
|
45
|
+
transports: [
|
|
46
|
+
// Console transport
|
|
47
|
+
new winston.transports.Console(),
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Export the logger
|
|
52
|
+
export { logger };
|
package/src/mq.mts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { Connection, Options, connect, Channel, ConsumeMessage } from "amqplib";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MQLib class for establishing a connection to the message queue and creating channels.
|
|
5
|
+
*/
|
|
6
|
+
export class MQLib {
|
|
7
|
+
private connection?: Connection;
|
|
8
|
+
private channel?: Channel;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Initializes the connection and channel to the message queue.
|
|
12
|
+
*
|
|
13
|
+
* @param MQ_URL - The URL of the message queue.
|
|
14
|
+
* @example
|
|
15
|
+
* const mqLib = new MQLib();
|
|
16
|
+
* await mqLib.init("amqp://localhost");
|
|
17
|
+
*/
|
|
18
|
+
async init(MQ_URL: string) {
|
|
19
|
+
await connect(MQ_URL)
|
|
20
|
+
.then(async (v) => {
|
|
21
|
+
//@ts-ignore
|
|
22
|
+
this.connection = v;
|
|
23
|
+
this.channel = await v.createChannel();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns the connection object.
|
|
29
|
+
*
|
|
30
|
+
* @returns The connection object to the message queue.
|
|
31
|
+
*/
|
|
32
|
+
get_connection() {
|
|
33
|
+
return this.connection;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns the channel object.
|
|
38
|
+
*
|
|
39
|
+
* @returns The channel object to the message queue.
|
|
40
|
+
*/
|
|
41
|
+
get_channel() {
|
|
42
|
+
return this.channel;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* BroadcastMQ class to handle different types of broadcast communication patterns: Publish-Subscribe, Routing, and Topic.
|
|
48
|
+
*/
|
|
49
|
+
export class BroadcastMQ {
|
|
50
|
+
constructor(public channel: Channel, protected type: CommunicationPattern) {}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Publishes a message to the specified exchange based on the communication pattern.
|
|
54
|
+
*
|
|
55
|
+
* @param exchange - The name of the exchange to send the message to.
|
|
56
|
+
* @param key - The routing key or pattern to use, depending on the communication pattern.
|
|
57
|
+
* @param payload - The message to be sent.
|
|
58
|
+
* @param options - Additional publishing options.
|
|
59
|
+
* @example
|
|
60
|
+
* // Publish a message to a 'logsExchange' with a routing key 'error' for a routing pattern.
|
|
61
|
+
* const mq = new BroadcastMQ(channel, "Routing");
|
|
62
|
+
* mq.publish("logsExchange", "error", { level: "error", message: "Something went wrong" });
|
|
63
|
+
*/
|
|
64
|
+
async publish(exchange: string, key: string, payload: any, options?: Options.Publish) {
|
|
65
|
+
switch (this.type) {
|
|
66
|
+
case "Publish-Subscribe": {
|
|
67
|
+
await this.channel.assertExchange(exchange, "fanout", { durable: false });
|
|
68
|
+
this.channel.publish(exchange, "", encode_payload(payload), options);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
case "Routing": {
|
|
73
|
+
await this.channel.assertExchange(exchange, "direct", { durable: false });
|
|
74
|
+
this.channel.publish(exchange, key, encode_payload(payload), options);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case "Topic": {
|
|
79
|
+
await this.channel.assertExchange(exchange, "topic", { durable: false });
|
|
80
|
+
this.channel.publish(exchange, key, encode_payload(payload), options);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Subscribes to an exchange to receive messages based on the communication pattern.
|
|
88
|
+
*
|
|
89
|
+
* @param exchange - The name of the exchange to subscribe to.
|
|
90
|
+
* @param key - The routing key or pattern to listen for.
|
|
91
|
+
* @param cfg - Configuration options for the consumer, including the callback function.
|
|
92
|
+
* @param options - Additional consumption options.
|
|
93
|
+
* @example
|
|
94
|
+
* // Subscribe to the 'logsExchange' for all 'error' messages in the Routing pattern.
|
|
95
|
+
* const mq = new BroadcastMQ(channel, "Routing");
|
|
96
|
+
* mq.subscribe("logsExchange", "error", { fn: handleErrorLogs, options: { noAck: true } });
|
|
97
|
+
*/
|
|
98
|
+
async subscribe(
|
|
99
|
+
exchange: string,
|
|
100
|
+
key: string,
|
|
101
|
+
cfg: ConsumerConfig,
|
|
102
|
+
options?: Options.Publish
|
|
103
|
+
) {
|
|
104
|
+
let fn = cfg.fn.bind({ channel: this.channel, type: this.type });
|
|
105
|
+
|
|
106
|
+
switch (this.type) {
|
|
107
|
+
case "Publish-Subscribe": {
|
|
108
|
+
await this.channel.assertExchange(exchange, "fanout", { durable: false });
|
|
109
|
+
const { queue } = await this.channel.assertQueue("", { exclusive: true });
|
|
110
|
+
|
|
111
|
+
this.channel.bindQueue(queue, exchange, "");
|
|
112
|
+
this.channel.consume(queue, (v) => fn(parse_payload(v)), cfg.options);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
case "Routing": {
|
|
117
|
+
await this.channel.assertExchange(exchange, "direct", { durable: false });
|
|
118
|
+
const { queue } = await this.channel.assertQueue("", { exclusive: true });
|
|
119
|
+
await this.channel.bindQueue(queue, exchange, key);
|
|
120
|
+
|
|
121
|
+
this.channel.consume(queue, (v) => fn(parse_payload(v)), options);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case "Topic": {
|
|
126
|
+
await this.channel.assertExchange(exchange, "topic", { durable: false });
|
|
127
|
+
const { queue } = await this.channel.assertQueue("", { exclusive: true });
|
|
128
|
+
|
|
129
|
+
this.channel.bindQueue(queue, exchange, key);
|
|
130
|
+
this.channel.consume(queue, (v) => fn(parse_payload(v)), options);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* TargetedMQ class to handle Point-to-Point and Request-Reply communication patterns.
|
|
139
|
+
*/
|
|
140
|
+
export class TargetedMQ {
|
|
141
|
+
constructor(public channel: Channel, protected type: CommunicationPattern) {}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Sends a message to the specified queue depending on the communication pattern.
|
|
145
|
+
*
|
|
146
|
+
* @param binding - The name of the queue or binding to send the message to.
|
|
147
|
+
* @param payload - The message to be sent.
|
|
148
|
+
* @param options - Additional publishing options.
|
|
149
|
+
* @example
|
|
150
|
+
* // Send a message to a queue for point-to-point communication
|
|
151
|
+
* const mq = new TargetedMQ(channel, "Point-to-Point");
|
|
152
|
+
* mq.produce("taskQueue", { taskId: 1, action: "process" });
|
|
153
|
+
*/
|
|
154
|
+
async produce(binding: string, payload: any, options?: Options.Publish) {
|
|
155
|
+
/* Alter produce strategy depending on the instance's configured comm pattern */
|
|
156
|
+
switch (this.type) {
|
|
157
|
+
case "Point-to-Point": {
|
|
158
|
+
await this.channel.assertQueue(binding);
|
|
159
|
+
return this.channel.sendToQueue(binding, encode_payload(payload), options);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case "Request-Reply": {
|
|
163
|
+
await this.channel.assertQueue(binding);
|
|
164
|
+
return this.channel.sendToQueue(binding, encode_payload(payload), options);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Sends a reply to the message sender in a Request-Reply pattern.
|
|
171
|
+
*
|
|
172
|
+
* @param msg - The original message to reply to.
|
|
173
|
+
* @param payload - The reply message to send back to the requester.
|
|
174
|
+
* @example
|
|
175
|
+
* // Send a response back to the client in a Request-Reply pattern
|
|
176
|
+
* const mq = new TargetedMQ(channel, "Request-Reply");
|
|
177
|
+
* mq.reply(msg, { result: "Processed successfully" });
|
|
178
|
+
*/
|
|
179
|
+
async reply(msg: ConsumeMessage, payload: any) {
|
|
180
|
+
if (this.type !== "Request-Reply") return;
|
|
181
|
+
|
|
182
|
+
this.channel.sendToQueue(msg.properties.replyTo, encode_payload(payload));
|
|
183
|
+
this.channel.ack(msg);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Consumes messages from the specified queue based on the communication pattern.
|
|
188
|
+
*
|
|
189
|
+
* @param queue - The name of the queue to consume messages from.
|
|
190
|
+
* @param cfg - Configuration options for the consumer, including the callback function.
|
|
191
|
+
* @example
|
|
192
|
+
* // Consume a message from the 'taskQueue' in a Point-to-Point communication pattern
|
|
193
|
+
* const mq = new TargetedMQ(channel, "Point-to-Point");
|
|
194
|
+
* mq.consume("taskQueue", { fn: processTask, options: { noAck: true } });
|
|
195
|
+
*/
|
|
196
|
+
async consume(queue: string, cfg: ConsumerConfig) {
|
|
197
|
+
let fn = cfg.fn.bind({ channel: this.channel, type: this.type });
|
|
198
|
+
|
|
199
|
+
switch (this.type) {
|
|
200
|
+
case "Point-to-Point": {
|
|
201
|
+
await this.channel.assertQueue(queue);
|
|
202
|
+
this.channel.consume(queue, (v) => fn(parse_payload(v)), cfg.options);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case "Request-Reply": {
|
|
207
|
+
await this.channel.assertQueue(queue);
|
|
208
|
+
this.channel.consume(queue, (v) => fn(parse_payload(v)), cfg.options);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Helper Functions */
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Parses the payload of a message.
|
|
219
|
+
*
|
|
220
|
+
* @param msg - The consumed message.
|
|
221
|
+
* @returns The parsed message content.
|
|
222
|
+
*/
|
|
223
|
+
function parse_payload(msg: any) {
|
|
224
|
+
if(!msg)return {};
|
|
225
|
+
return { ...msg, content: JSON.parse(msg.content.toString()) };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Encodes a message as a Buffer to be sent over the message queue.
|
|
230
|
+
*
|
|
231
|
+
* @param msg - The message to be encoded.
|
|
232
|
+
* @returns The encoded message as a Buffer.
|
|
233
|
+
*/
|
|
234
|
+
function encode_payload(msg: any) {
|
|
235
|
+
return Buffer.from(JSON.stringify(msg));
|
|
236
|
+
}
|