@diia-inhouse/diia-queue 13.3.1 → 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.
@@ -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, retry, timeout, registryApiVersion, requestUuid = (0, node_crypto_1.randomUUID)() } = ops;
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, retry, timeout, exchangeName, registryApiVersion };
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,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,WAAW,GAAG,IAAA,wBAAU,GAAE,EAAE,GAAG,GAAG,CAAA;QAE1H,MAAM,OAAO,GAAmB,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAAA;QAE9D,MAAM,OAAO,GAAyB,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAA;QACvG,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;AA3CD,oDA2CC"}
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
- retry?: boolean;
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 {
@@ -13,7 +13,6 @@ export interface PublishDirectOptions {
13
13
  exchangeName?: string;
14
14
  timeout?: number;
15
15
  ignoreCache?: boolean;
16
- retry?: boolean;
17
16
  registryApiVersion?: string;
18
17
  }
19
18
  export interface PublishOptions {
@@ -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.1",
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.6",
48
- "@diia-inhouse/diia-metrics": "6.5.29",
49
- "@diia-inhouse/errors": "1.18.19",
50
- "@diia-inhouse/eslint-config": "8.8.0",
51
- "@diia-inhouse/eslint-plugin": "1.9.5",
52
- "@diia-inhouse/test": "7.3.18",
53
- "@diia-inhouse/types": "12.0.1",
54
- "@diia-inhouse/utils": "5.3.23",
55
- "@diia-inhouse/validators": "1.28.30",
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",