@diia-inhouse/diia-queue 13.3.0 → 13.3.3
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/dist/services/externalCommunicator.js +101 -2
- package/dist/services/externalCommunicator.js.map +1 -1
- package/dist/types/interfaces/externalCommunicator.d.ts +27 -1
- package/dist/types/interfaces/options.d.ts +0 -1
- package/dist/types/services/externalCommunicator.d.ts +99 -0
- package/package.json +10 -10
|
@@ -12,10 +12,109 @@ class ExternalCommunicator {
|
|
|
12
12
|
this.eventMessageValidator = eventMessageValidator;
|
|
13
13
|
this.logger = logger;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Synchronous request/response over the external event bus.
|
|
17
|
+
*
|
|
18
|
+
* Publishes `event` to the configured external exchange wrapped as
|
|
19
|
+
* `{ uuid, request }` and awaits the subscriber's response on the same call.
|
|
20
|
+
* The response envelope is validated against `ops.validationRules`, and any embedded error is rethrown as
|
|
21
|
+
* {@link ExternalCommunicatorError}.
|
|
22
|
+
*
|
|
23
|
+
* Use this when the caller needs the response inline (e.g. a user-facing
|
|
24
|
+
* request that must block on `AGateway`). For fire-and-forget or fan-out
|
|
25
|
+
* flows, use the `default` publish + listener pattern instead.
|
|
26
|
+
*
|
|
27
|
+
* ### Transport
|
|
28
|
+
*
|
|
29
|
+
* Implemented on top of RabbitMQ's
|
|
30
|
+
* [direct reply-to](https://www.rabbitmq.com/docs/direct-reply-to)
|
|
31
|
+
* feature: the publisher consumes the pseudo-queue
|
|
32
|
+
* `amq.rabbitmq.reply-to` and stamps each request with `replyTo` +
|
|
33
|
+
* `correlationId`. The subscriber publishes its response back to that
|
|
34
|
+
* `replyTo` on the default exchange, and this call resolves when the
|
|
35
|
+
* matching `correlationId` arrives. No reply queue is declared per
|
|
36
|
+
* request, so there is no per-call broker setup overhead.
|
|
37
|
+
*
|
|
38
|
+
* ### Response contract
|
|
39
|
+
*
|
|
40
|
+
* The subscriber must respond with an
|
|
41
|
+
* {@link ExternalCommunicatorResponse} envelope:
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* { uuid: string, response: T, error?: { http_code, message?, data? } }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* On success this method returns **only the `response` field** typed as
|
|
48
|
+
* `T` — the wrapping `uuid` and the envelope itself are not exposed to
|
|
49
|
+
* the caller. Therefore `T` should describe the shape of `response`, not
|
|
50
|
+
* of the full envelope.
|
|
51
|
+
*
|
|
52
|
+
* ### Error contract
|
|
53
|
+
*
|
|
54
|
+
* Errors travel through the same envelope (`error` field on the
|
|
55
|
+
* subscriber's response — this is **not** a transport-level failure),
|
|
56
|
+
* but on the caller side they are surfaced as a thrown
|
|
57
|
+
* {@link ExternalCommunicatorError} instead of being returned. The
|
|
58
|
+
* envelope's `error` payload is mapped onto the exception so no data is
|
|
59
|
+
* lost:
|
|
60
|
+
*
|
|
61
|
+
* - `error.message` → `Error.message` (falls back to `'unknown error'`
|
|
62
|
+
* when missing).
|
|
63
|
+
* - `error.http_code` → exception's status code (read via `getCode()`).
|
|
64
|
+
* - `error.data` → exception's data bag (read via `getData()`), spread
|
|
65
|
+
* alongside `event` (the event name) and `httpCode` (a duplicate of
|
|
66
|
+
* the status code, kept on `data` for downstream consumers).
|
|
67
|
+
*
|
|
68
|
+
* Callers that need to inspect the original status or payload should
|
|
69
|
+
* `catch` the error and read those fields:
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* try {
|
|
73
|
+
* await externalCommunicator.receiveDirect<T>(event, req, ops)
|
|
74
|
+
* } catch (err) {
|
|
75
|
+
* if (err instanceof ExternalCommunicatorError) {
|
|
76
|
+
* err.getCode() // original error.http_code
|
|
77
|
+
* err.getData() // { event, httpCode, ...error.data }
|
|
78
|
+
* err.message // error.message || 'unknown error'
|
|
79
|
+
* }
|
|
80
|
+
* throw err
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @typeParam T - shape of the successful `response` payload returned by the subscriber.
|
|
85
|
+
* @param event - external event name. Must be declared in
|
|
86
|
+
* `serviceRulesConfig.topicsConfig[QueueConfigType.External][topicName].events[]`
|
|
87
|
+
* so the event resolves to an exchange (or pass `ops.exchangeName`
|
|
88
|
+
* to override the lookup).
|
|
89
|
+
* @param request - payload forwarded to the subscriber. Defaults to `{}`.
|
|
90
|
+
* @param ops - direct-call options. See {@link ReceiveDirectOps}.
|
|
91
|
+
* @returns the subscriber's `response` field, typed as `T`. The envelope's
|
|
92
|
+
* `uuid` and `error` fields are not returned.
|
|
93
|
+
*
|
|
94
|
+
* @throws {ExternalCommunicatorError} when the subscriber responds with an
|
|
95
|
+
* `error` envelope. The original `http_code`, `message`, and `data`
|
|
96
|
+
* fields are preserved on the thrown error.
|
|
97
|
+
* @throws {Error} `'Message in a wrong format was received from a direct channel'`
|
|
98
|
+
* when `validationRules` are provided and the response fails validation.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const result = await externalCommunicator.receiveDirect<{ data: string }>(
|
|
103
|
+
* 'notary.lookup',
|
|
104
|
+
* { notaryCertificateNumber: '1234', requestId: '-0' },
|
|
105
|
+
* {
|
|
106
|
+
* // Describes the inner `response` only; the envelope (uuid,
|
|
107
|
+
* // error, meta, ...) is wrapped by the validator.
|
|
108
|
+
* validationRules: { data: { type: 'string' } },
|
|
109
|
+
* timeout: DurationMs.Second * 5,
|
|
110
|
+
* },
|
|
111
|
+
* )
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
15
114
|
async receiveDirect(event, request = {}, ops) {
|
|
16
|
-
const { exchangeName, validationRules, ignoreCache,
|
|
115
|
+
const { exchangeName, validationRules, ignoreCache, timeout, registryApiVersion, requestUuid = (0, node_crypto_1.randomUUID)() } = ops;
|
|
17
116
|
const payload = { uuid: requestUuid, request };
|
|
18
|
-
const options = { ignoreCache,
|
|
117
|
+
const options = { ignoreCache, timeout, exchangeName, registryApiVersion };
|
|
19
118
|
const externalResponse = await this.externalEventBus.publishDirect(event, payload, options);
|
|
20
119
|
this.logger.debug('External direct response', externalResponse);
|
|
21
120
|
if (validationRules) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"externalCommunicator.js","sourceRoot":"","sources":["../../src/services/externalCommunicator.ts"],"names":[],"mappings":";;;AAAA,6CAAwC;AAGxC,iDAAgE;AAMhE,MAAa,oBAAoB;IAER;IACA;IACA;IAHrB,YACqB,gBAAuC,EACvC,qBAA4C,EAC5C,MAAc;QAFd,qBAAgB,GAAhB,gBAAgB,CAAuB;QACvC,0BAAqB,GAArB,qBAAqB,CAAuB;QAC5C,WAAM,GAAN,MAAM,CAAQ;IAChC,CAAC;IAEJ,KAAK,CAAC,aAAa,CAAI,KAAa,EAAE,UAAmB,EAAE,EAAE,GAAqB;QAC9E,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"externalCommunicator.js","sourceRoot":"","sources":["../../src/services/externalCommunicator.ts"],"names":[],"mappings":";;;AAAA,6CAAwC;AAGxC,iDAAgE;AAMhE,MAAa,oBAAoB;IAER;IACA;IACA;IAHrB,YACqB,gBAAuC,EACvC,qBAA4C,EAC5C,MAAc;QAFd,qBAAgB,GAAhB,gBAAgB,CAAuB;QACvC,0BAAqB,GAArB,qBAAqB,CAAuB;QAC5C,WAAM,GAAN,MAAM,CAAQ;IAChC,CAAC;IAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkGG;IACH,KAAK,CAAC,aAAa,CAAI,KAAa,EAAE,UAAmB,EAAE,EAAE,GAAqB;QAC9E,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE,WAAW,GAAG,IAAA,wBAAU,GAAE,EAAE,GAAG,GAAG,CAAA;QAEnH,MAAM,OAAO,GAAmB,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAAA;QAE9D,MAAM,OAAO,GAAyB,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAA;QAChG,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,aAAa,CAC9D,KAAK,EACL,OAAO,EACP,OAAO,CACV,CAAA;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAA;QAC/D,IAAI,eAAe,EAAE,CAAC;YAClB,IAAI,CAAC;gBACD,IAAI,CAAC,qBAAqB,CAAC,0BAA0B,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAA;YAC5F,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,MAAM,QAAQ,GAAG,8DAA8D,CAAA;gBAE/E,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC,CAAA;gBACtD,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAA;YAC7B,CAAC;QACL,CAAC;QAED,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAA;QAEpD,IAAI,KAAK,EAAE,CAAC;YACR,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uCAAuC,KAAK,KAAK,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,CAAA;YAEvH,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,IAAI,eAAe,CAAA;YAE/C,MAAM,IAAI,kCAAyB,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;QACrH,CAAC;QAED,OAAO,QAAQ,CAAA;IACnB,CAAC;CACJ;AA9ID,oDA8IC"}
|
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
import { HttpStatusCode } from '@diia-inhouse/types';
|
|
2
2
|
import { ValidationSchema } from '@diia-inhouse/validators';
|
|
3
3
|
import { ExchangeName } from './messageBrokerServiceConfig';
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link ExternalCommunicator.receiveDirect}.
|
|
6
|
+
*/
|
|
4
7
|
export interface ReceiveDirectOps {
|
|
8
|
+
/**
|
|
9
|
+
* Forwarded in the message meta; consumed by `AGateway` to route the
|
|
10
|
+
* request to a specific registry API version.
|
|
11
|
+
*/
|
|
5
12
|
registryApiVersion?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Override the target exchange. Defaults to the exchange resolved from the
|
|
15
|
+
* external queue configuration for the given event.
|
|
16
|
+
*/
|
|
6
17
|
exchangeName?: ExchangeName;
|
|
18
|
+
/**
|
|
19
|
+
* Schema describing the **inner** `response` payload. The validator wraps
|
|
20
|
+
* it with the envelope shape (`event`, `payload.uuid`, `payload.error`,
|
|
21
|
+
* `meta.date`, etc.) automatically — so pass just the fields you expect
|
|
22
|
+
* inside `response`, not the envelope itself.
|
|
23
|
+
*
|
|
24
|
+
* Null is acceptable only for legacy use cases.
|
|
25
|
+
*/
|
|
7
26
|
validationRules: ValidationSchema | null;
|
|
27
|
+
/** Bypass response caching on the subscriber side if configured. */
|
|
8
28
|
ignoreCache?: boolean;
|
|
9
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Override the default direct-response timeout (ms). Falls back to the
|
|
31
|
+
* publisher's configured `directResponseTimeout` (10s by default).
|
|
32
|
+
*/
|
|
10
33
|
timeout?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Defaults to a freshly generated `randomUUID()` when omitted.
|
|
36
|
+
*/
|
|
11
37
|
requestUuid?: string;
|
|
12
38
|
}
|
|
13
39
|
export interface ExternalCommunicatorResponseError {
|
|
@@ -7,5 +7,104 @@ export declare class ExternalCommunicator {
|
|
|
7
7
|
private readonly eventMessageValidator;
|
|
8
8
|
private readonly logger;
|
|
9
9
|
constructor(externalEventBus: ExternalEventBusQueue, eventMessageValidator: EventMessageValidator, logger: Logger);
|
|
10
|
+
/**
|
|
11
|
+
* Synchronous request/response over the external event bus.
|
|
12
|
+
*
|
|
13
|
+
* Publishes `event` to the configured external exchange wrapped as
|
|
14
|
+
* `{ uuid, request }` and awaits the subscriber's response on the same call.
|
|
15
|
+
* The response envelope is validated against `ops.validationRules`, and any embedded error is rethrown as
|
|
16
|
+
* {@link ExternalCommunicatorError}.
|
|
17
|
+
*
|
|
18
|
+
* Use this when the caller needs the response inline (e.g. a user-facing
|
|
19
|
+
* request that must block on `AGateway`). For fire-and-forget or fan-out
|
|
20
|
+
* flows, use the `default` publish + listener pattern instead.
|
|
21
|
+
*
|
|
22
|
+
* ### Transport
|
|
23
|
+
*
|
|
24
|
+
* Implemented on top of RabbitMQ's
|
|
25
|
+
* [direct reply-to](https://www.rabbitmq.com/docs/direct-reply-to)
|
|
26
|
+
* feature: the publisher consumes the pseudo-queue
|
|
27
|
+
* `amq.rabbitmq.reply-to` and stamps each request with `replyTo` +
|
|
28
|
+
* `correlationId`. The subscriber publishes its response back to that
|
|
29
|
+
* `replyTo` on the default exchange, and this call resolves when the
|
|
30
|
+
* matching `correlationId` arrives. No reply queue is declared per
|
|
31
|
+
* request, so there is no per-call broker setup overhead.
|
|
32
|
+
*
|
|
33
|
+
* ### Response contract
|
|
34
|
+
*
|
|
35
|
+
* The subscriber must respond with an
|
|
36
|
+
* {@link ExternalCommunicatorResponse} envelope:
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* { uuid: string, response: T, error?: { http_code, message?, data? } }
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* On success this method returns **only the `response` field** typed as
|
|
43
|
+
* `T` — the wrapping `uuid` and the envelope itself are not exposed to
|
|
44
|
+
* the caller. Therefore `T` should describe the shape of `response`, not
|
|
45
|
+
* of the full envelope.
|
|
46
|
+
*
|
|
47
|
+
* ### Error contract
|
|
48
|
+
*
|
|
49
|
+
* Errors travel through the same envelope (`error` field on the
|
|
50
|
+
* subscriber's response — this is **not** a transport-level failure),
|
|
51
|
+
* but on the caller side they are surfaced as a thrown
|
|
52
|
+
* {@link ExternalCommunicatorError} instead of being returned. The
|
|
53
|
+
* envelope's `error` payload is mapped onto the exception so no data is
|
|
54
|
+
* lost:
|
|
55
|
+
*
|
|
56
|
+
* - `error.message` → `Error.message` (falls back to `'unknown error'`
|
|
57
|
+
* when missing).
|
|
58
|
+
* - `error.http_code` → exception's status code (read via `getCode()`).
|
|
59
|
+
* - `error.data` → exception's data bag (read via `getData()`), spread
|
|
60
|
+
* alongside `event` (the event name) and `httpCode` (a duplicate of
|
|
61
|
+
* the status code, kept on `data` for downstream consumers).
|
|
62
|
+
*
|
|
63
|
+
* Callers that need to inspect the original status or payload should
|
|
64
|
+
* `catch` the error and read those fields:
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* try {
|
|
68
|
+
* await externalCommunicator.receiveDirect<T>(event, req, ops)
|
|
69
|
+
* } catch (err) {
|
|
70
|
+
* if (err instanceof ExternalCommunicatorError) {
|
|
71
|
+
* err.getCode() // original error.http_code
|
|
72
|
+
* err.getData() // { event, httpCode, ...error.data }
|
|
73
|
+
* err.message // error.message || 'unknown error'
|
|
74
|
+
* }
|
|
75
|
+
* throw err
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @typeParam T - shape of the successful `response` payload returned by the subscriber.
|
|
80
|
+
* @param event - external event name. Must be declared in
|
|
81
|
+
* `serviceRulesConfig.topicsConfig[QueueConfigType.External][topicName].events[]`
|
|
82
|
+
* so the event resolves to an exchange (or pass `ops.exchangeName`
|
|
83
|
+
* to override the lookup).
|
|
84
|
+
* @param request - payload forwarded to the subscriber. Defaults to `{}`.
|
|
85
|
+
* @param ops - direct-call options. See {@link ReceiveDirectOps}.
|
|
86
|
+
* @returns the subscriber's `response` field, typed as `T`. The envelope's
|
|
87
|
+
* `uuid` and `error` fields are not returned.
|
|
88
|
+
*
|
|
89
|
+
* @throws {ExternalCommunicatorError} when the subscriber responds with an
|
|
90
|
+
* `error` envelope. The original `http_code`, `message`, and `data`
|
|
91
|
+
* fields are preserved on the thrown error.
|
|
92
|
+
* @throws {Error} `'Message in a wrong format was received from a direct channel'`
|
|
93
|
+
* when `validationRules` are provided and the response fails validation.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const result = await externalCommunicator.receiveDirect<{ data: string }>(
|
|
98
|
+
* 'notary.lookup',
|
|
99
|
+
* { notaryCertificateNumber: '1234', requestId: '-0' },
|
|
100
|
+
* {
|
|
101
|
+
* // Describes the inner `response` only; the envelope (uuid,
|
|
102
|
+
* // error, meta, ...) is wrapped by the validator.
|
|
103
|
+
* validationRules: { data: { type: 'string' } },
|
|
104
|
+
* timeout: DurationMs.Second * 5,
|
|
105
|
+
* },
|
|
106
|
+
* )
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
10
109
|
receiveDirect<T>(event: string, request: unknown | undefined, ops: ReceiveDirectOps): Promise<T>;
|
|
11
110
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diia-inhouse/diia-queue",
|
|
3
|
-
"version": "13.3.
|
|
3
|
+
"version": "13.3.3",
|
|
4
4
|
"description": "Package provide queue functionality",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/types/index.d.ts",
|
|
@@ -44,15 +44,15 @@
|
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@diia-inhouse/configs": "6.1.1",
|
|
47
|
-
"@diia-inhouse/diia-logger": "3.20.
|
|
48
|
-
"@diia-inhouse/diia-metrics": "6.5.
|
|
49
|
-
"@diia-inhouse/errors": "1.18.
|
|
50
|
-
"@diia-inhouse/eslint-config": "8.8.
|
|
51
|
-
"@diia-inhouse/eslint-plugin": "1.9.
|
|
52
|
-
"@diia-inhouse/test": "7.3.
|
|
53
|
-
"@diia-inhouse/types": "12.0.
|
|
54
|
-
"@diia-inhouse/utils": "5.3.
|
|
55
|
-
"@diia-inhouse/validators": "1.28.
|
|
47
|
+
"@diia-inhouse/diia-logger": "3.20.12",
|
|
48
|
+
"@diia-inhouse/diia-metrics": "6.5.38",
|
|
49
|
+
"@diia-inhouse/errors": "1.18.23",
|
|
50
|
+
"@diia-inhouse/eslint-config": "8.8.2",
|
|
51
|
+
"@diia-inhouse/eslint-plugin": "1.9.6",
|
|
52
|
+
"@diia-inhouse/test": "7.3.23",
|
|
53
|
+
"@diia-inhouse/types": "12.0.4",
|
|
54
|
+
"@diia-inhouse/utils": "5.3.28",
|
|
55
|
+
"@diia-inhouse/validators": "1.28.34",
|
|
56
56
|
"@opentelemetry/api": "1.9.0",
|
|
57
57
|
"@types/lodash": "4.17.24",
|
|
58
58
|
"@types/node": "24.11.0",
|