@lakutata/nats 2.3.0 → 3.0.0-beta.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/LICENSE +23 -0
- package/README.md +288 -25
- package/dist/cjs/codecs/JSONCodec.d.ts +32 -0
- package/dist/cjs/codecs/JSONCodec.js +44 -0
- package/dist/cjs/codecs/MessagePackCodec.d.ts +22 -0
- package/dist/cjs/codecs/MessagePackCodec.js +35 -0
- package/dist/cjs/components/NATS.d.ts +376 -0
- package/dist/cjs/components/NATS.js +772 -0
- package/dist/cjs/entrypoints/NatsServiceEntrypoint.d.ts +102 -0
- package/dist/cjs/entrypoints/NatsServiceEntrypoint.js +127 -0
- package/dist/cjs/exceptions/NatsBadRequestException.d.ts +11 -0
- package/dist/cjs/exceptions/NatsBadRequestException.js +18 -0
- package/dist/cjs/exceptions/NatsBulkException.d.ts +19 -0
- package/dist/cjs/exceptions/NatsBulkException.js +26 -0
- package/dist/cjs/exceptions/NatsForbiddenException.d.ts +11 -0
- package/dist/cjs/exceptions/NatsForbiddenException.js +18 -0
- package/dist/cjs/exceptions/NatsInternalServerException.d.ts +12 -0
- package/dist/cjs/exceptions/NatsInternalServerException.js +19 -0
- package/dist/cjs/exceptions/NatsNoRespondersAvailableException.d.ts +12 -0
- package/dist/cjs/exceptions/NatsNoRespondersAvailableException.js +19 -0
- package/dist/cjs/exceptions/NatsNotFoundException.d.ts +11 -0
- package/dist/cjs/exceptions/NatsNotFoundException.js +18 -0
- package/dist/cjs/exceptions/NatsRequestTimeoutException.d.ts +11 -0
- package/dist/cjs/exceptions/NatsRequestTimeoutException.js +18 -0
- package/dist/cjs/exports/Nats.d.ts +18 -0
- package/dist/cjs/exports/Nats.js +39 -0
- package/dist/cjs/interfaces/NatsClientOptions.d.ts +106 -0
- package/dist/{lib → cjs/lib}/ServiceEventCodec.d.ts +0 -1
- package/dist/{lib → cjs/lib}/ServiceEventCodec.js +19 -9
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/providers/ServiceProxy.d.ts +210 -0
- package/dist/cjs/providers/ServiceProxy.js +274 -0
- package/dist/cjs/types/SubscribeOptions.d.ts +33 -0
- package/dist/esm/codecs/JSONCodec.js +41 -0
- package/dist/esm/codecs/MessagePackCodec.js +32 -0
- package/dist/esm/components/NATS.js +767 -0
- package/dist/esm/entrypoints/NatsServiceEntrypoint.js +123 -0
- package/dist/esm/exceptions/NatsBadRequestException.js +14 -0
- package/dist/esm/exceptions/NatsBulkException.js +22 -0
- package/dist/esm/exceptions/NatsForbiddenException.js +14 -0
- package/dist/esm/exceptions/NatsInternalServerException.js +15 -0
- package/dist/esm/exceptions/NatsNoRespondersAvailableException.js +15 -0
- package/dist/esm/exceptions/NatsNotFoundException.js +14 -0
- package/dist/esm/exceptions/NatsRequestTimeoutException.js +14 -0
- package/dist/esm/exports/Nats.js +15 -0
- package/dist/esm/interfaces/NatsClientOptions.js +1 -0
- package/dist/esm/lib/ServiceEventCodec.js +21 -0
- package/dist/esm/providers/ServiceProxy.js +270 -0
- package/dist/esm/types/SubscribeOptions.js +1 -0
- package/dist/types/codecs/JSONCodec.d.ts +32 -0
- package/dist/types/codecs/MessagePackCodec.d.ts +22 -0
- package/dist/types/components/NATS.d.ts +376 -0
- package/dist/types/entrypoints/NatsServiceEntrypoint.d.ts +102 -0
- package/dist/types/exceptions/NatsBadRequestException.d.ts +11 -0
- package/dist/types/exceptions/NatsBulkException.d.ts +19 -0
- package/dist/types/exceptions/NatsForbiddenException.d.ts +11 -0
- package/dist/types/exceptions/NatsInternalServerException.d.ts +12 -0
- package/dist/types/exceptions/NatsNoRespondersAvailableException.d.ts +12 -0
- package/dist/types/exceptions/NatsNotFoundException.d.ts +11 -0
- package/dist/types/exceptions/NatsRequestTimeoutException.d.ts +11 -0
- package/dist/types/exports/Nats.d.ts +18 -0
- package/dist/types/interfaces/NatsClientOptions.d.ts +106 -0
- package/dist/types/lib/ServiceEventCodec.d.ts +9 -0
- package/dist/types/providers/ServiceProxy.d.ts +210 -0
- package/dist/types/types/SubscribeOptions.d.ts +33 -0
- package/package.json +42 -33
- package/CHANGELOG.md +0 -288
- package/dist/CommonExports.d.ts +0 -19
- package/dist/CommonExports.d.ts.map +0 -1
- package/dist/CommonExports.js +0 -35
- package/dist/codecs/JSONCodec.d.ts +0 -3
- package/dist/codecs/JSONCodec.d.ts.map +0 -1
- package/dist/codecs/JSONCodec.js +0 -15
- package/dist/codecs/MessagePackCodec.d.ts +0 -3
- package/dist/codecs/MessagePackCodec.d.ts.map +0 -1
- package/dist/codecs/MessagePackCodec.js +0 -16
- package/dist/components/NATS.d.ts +0 -252
- package/dist/components/NATS.d.ts.map +0 -1
- package/dist/components/NATS.js +0 -564
- package/dist/entrypoints/SetupNatsServiceEntrypoint.d.ts +0 -8
- package/dist/entrypoints/SetupNatsServiceEntrypoint.d.ts.map +0 -1
- package/dist/entrypoints/SetupNatsServiceEntrypoint.js +0 -41
- package/dist/exceptions/NatsBadRequestException.d.ts +0 -7
- package/dist/exceptions/NatsBadRequestException.d.ts.map +0 -1
- package/dist/exceptions/NatsBadRequestException.js +0 -13
- package/dist/exceptions/NatsBulkException.d.ts +0 -11
- package/dist/exceptions/NatsBulkException.d.ts.map +0 -1
- package/dist/exceptions/NatsBulkException.js +0 -17
- package/dist/exceptions/NatsForbiddenException.d.ts +0 -7
- package/dist/exceptions/NatsForbiddenException.d.ts.map +0 -1
- package/dist/exceptions/NatsForbiddenException.js +0 -13
- package/dist/exceptions/NatsInternalServerException.d.ts +0 -7
- package/dist/exceptions/NatsInternalServerException.d.ts.map +0 -1
- package/dist/exceptions/NatsInternalServerException.js +0 -13
- package/dist/exceptions/NatsNoRespondersAvailableException.d.ts +0 -7
- package/dist/exceptions/NatsNoRespondersAvailableException.d.ts.map +0 -1
- package/dist/exceptions/NatsNoRespondersAvailableException.js +0 -13
- package/dist/exceptions/NatsNotFoundException.d.ts +0 -7
- package/dist/exceptions/NatsNotFoundException.d.ts.map +0 -1
- package/dist/exceptions/NatsNotFoundException.js +0 -13
- package/dist/exceptions/NatsRequestTimeoutException.d.ts +0 -7
- package/dist/exceptions/NatsRequestTimeoutException.d.ts.map +0 -1
- package/dist/exceptions/NatsRequestTimeoutException.js +0 -13
- package/dist/exceptions/ServiceInvokeException.d.ts +0 -6
- package/dist/exceptions/ServiceInvokeException.d.ts.map +0 -1
- package/dist/exceptions/ServiceInvokeException.js +0 -11
- package/dist/interfaces/NatsClientOptions.d.ts +0 -26
- package/dist/interfaces/NatsClientOptions.d.ts.map +0 -1
- package/dist/lib/ServiceEventCodec.d.ts.map +0 -1
- package/dist/lib/ServiceResponseCodec.d.ts +0 -22
- package/dist/lib/ServiceResponseCodec.d.ts.map +0 -1
- package/dist/lib/ServiceResponseCodec.js +0 -50
- package/dist/providers/ServiceProxy.d.ts +0 -85
- package/dist/providers/ServiceProxy.d.ts.map +0 -1
- package/dist/providers/ServiceProxy.js +0 -124
- package/dist/types/SubscribeOptions.d.ts +0 -12
- package/dist/types/SubscribeOptions.d.ts.map +0 -1
- package/docs/large-payload-design.md +0 -199
- package/tsconfig.json +0 -8
- /package/dist/{interfaces → cjs/interfaces}/NatsClientOptions.js +0 -0
- /package/dist/{types → cjs/types}/SubscribeOptions.js +0 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.NATS = exports.buildNatsClientOptions = void 0;
|
|
13
|
+
const core_1 = require("@lakutata/core");
|
|
14
|
+
const di_1 = require("@lakutata/core/decorator/di");
|
|
15
|
+
const nats_1 = require("nats");
|
|
16
|
+
const NatsBadRequestException_js_1 = require("../exceptions/NatsBadRequestException.js");
|
|
17
|
+
const NatsForbiddenException_js_1 = require("../exceptions/NatsForbiddenException.js");
|
|
18
|
+
const NatsNotFoundException_js_1 = require("../exceptions/NatsNotFoundException.js");
|
|
19
|
+
const NatsRequestTimeoutException_js_1 = require("../exceptions/NatsRequestTimeoutException.js");
|
|
20
|
+
const NatsNoRespondersAvailableException_js_1 = require("../exceptions/NatsNoRespondersAvailableException.js");
|
|
21
|
+
const NatsInternalServerException_js_1 = require("../exceptions/NatsInternalServerException.js");
|
|
22
|
+
const NatsBulkException_js_1 = require("../exceptions/NatsBulkException.js");
|
|
23
|
+
const ServiceEventCodec_js_1 = require("../lib/ServiceEventCodec.js");
|
|
24
|
+
const MessagePackCodec_js_1 = require("../codecs/MessagePackCodec.js");
|
|
25
|
+
const node_crypto_1 = require("node:crypto");
|
|
26
|
+
//The headers of the bulk channel protocol (in the headers, the payload is left untouched)
|
|
27
|
+
const HDR_BULK = 'X-Lkt-Bulk'; //Capability: this side supports the bulk references
|
|
28
|
+
const HDR_REF = 'X-Lkt-Ref'; //Marker: the payload is a reference {__ref} to the object store
|
|
29
|
+
const DEFAULT_REQUEST_TIMEOUT = 10 * 60 * 1000; //The time a request waits for its response by default
|
|
30
|
+
const BULK_PROBE = '_LKT.BULK.'; //The subjects of the capability probes: answered by the responders supporting the bulk references
|
|
31
|
+
/**
|
|
32
|
+
* Build the component options of a {@link NATS} component, to register it in the `components` of an application (or a
|
|
33
|
+
* module) under the name of your choice. The options are validated when the component is created, not here.
|
|
34
|
+
* @param options The connection options: the servers, the codec, the authentication, the reconnection and the bulk
|
|
35
|
+
* channel of the large payloads.
|
|
36
|
+
* @returns The component options (`{class: NATS, ...options}`).
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* import {Application} from 'lakutata'
|
|
40
|
+
* import {buildNatsClientOptions, type NATS} from '@lakutata/nats'
|
|
41
|
+
*
|
|
42
|
+
* Application
|
|
43
|
+
* .run({
|
|
44
|
+
* id: 'notifier.app',
|
|
45
|
+
* name: 'Notifier',
|
|
46
|
+
* components: {
|
|
47
|
+
* nats: buildNatsClientOptions({
|
|
48
|
+
* servers: ['nats://10.0.0.1:4222', 'nats://10.0.0.2:4222'],
|
|
49
|
+
* user: 'notifier',
|
|
50
|
+
* pass: 'secret',
|
|
51
|
+
* name: 'notifier',
|
|
52
|
+
* //Never give up reconnecting
|
|
53
|
+
* maxReconnectAttempts: -1
|
|
54
|
+
* })
|
|
55
|
+
* }
|
|
56
|
+
* })
|
|
57
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
58
|
+
* const nats: NATS = await app.getObject('nats')
|
|
59
|
+
* nats.publish('notification.sent', {to: 'ada@example.com'})
|
|
60
|
+
* })
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
const buildNatsClientOptions = (options) => {
|
|
64
|
+
return {
|
|
65
|
+
class: NATS,
|
|
66
|
+
servers: options.servers,
|
|
67
|
+
codec: options.codec,
|
|
68
|
+
timeout: options.timeout,
|
|
69
|
+
token: options.token,
|
|
70
|
+
user: options.user,
|
|
71
|
+
pass: options.pass,
|
|
72
|
+
debug: options.debug,
|
|
73
|
+
maxPingOut: options.maxPingOut,
|
|
74
|
+
maxReconnectAttempts: options.maxReconnectAttempts,
|
|
75
|
+
name: options.name,
|
|
76
|
+
noEcho: options.noEcho,
|
|
77
|
+
noRandomize: options.noRandomize,
|
|
78
|
+
pingInterval: options.pingInterval,
|
|
79
|
+
reconnect: options.reconnect,
|
|
80
|
+
bulk: options.bulk,
|
|
81
|
+
bulkBucket: options.bulkBucket,
|
|
82
|
+
bulkTTL: options.bulkTTL,
|
|
83
|
+
bulkReplicas: options.bulkReplicas
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
exports.buildNatsClientOptions = buildNatsClientOptions;
|
|
87
|
+
/**
|
|
88
|
+
* The NATS component: a connection to a NATS server (or cluster) to publish messages, subscribe to subjects, send
|
|
89
|
+
* requests and answer them, and emit and receive the service events. Register it in the `components` of an application
|
|
90
|
+
* with {@link buildNatsClientOptions} (or `{class: NATS, ...options}`) and get it with `getObject()` or `@Inject`; it is
|
|
91
|
+
* a singleton. The service entrypoint ({@link natsServiceEntrypoint}) and the service proxies ({@link ServiceProxy})
|
|
92
|
+
* use it by its component name.
|
|
93
|
+
*
|
|
94
|
+
* It connects when it is created: a server which cannot be reached within the `timeout` option fails its creation, and
|
|
95
|
+
* so the application's start. A lost connection is reestablished in the background (see the `reconnect` and
|
|
96
|
+
* `maxReconnectAttempts` options); once the reconnection is given up, an `'error'` event is emitted and the calls fail
|
|
97
|
+
* with the NATS client error `CONNECTION_CLOSED`. When it is destroyed, it drains its connections: the subscriptions stop receiving, the messages
|
|
98
|
+
* in progress are handled and the outgoing ones flushed before the connections close.
|
|
99
|
+
*
|
|
100
|
+
* Every payload is encoded with the codec ({@link messagePackCodec} by default). A payload larger than the server's
|
|
101
|
+
* `max_payload` (1 MiB by default) cannot be sent as a message: a request or a response of 90% of it or more is relayed
|
|
102
|
+
* through a JetStream object store when both sides enabled the bulk channel (the `bulk` option, on by default, which
|
|
103
|
+
* needs JetStream on the server), as a reference the receiving side reads transparently. The published messages and the
|
|
104
|
+
* events are never relayed: over `max_payload`, publishing them throws.
|
|
105
|
+
*
|
|
106
|
+
* It emits an `'error'` event, only when it has listeners, for the failures nobody else can report: a subscription
|
|
107
|
+
* callback or an event listener which threw, a message which could not be decoded, a subscription error (a permissions
|
|
108
|
+
* violation).
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* import {Application} from 'lakutata'
|
|
112
|
+
* import {buildNatsClientOptions, type NATS} from '@lakutata/nats'
|
|
113
|
+
*
|
|
114
|
+
* Application
|
|
115
|
+
* .run({
|
|
116
|
+
* id: 'pricing.app',
|
|
117
|
+
* name: 'Pricing',
|
|
118
|
+
* components: {
|
|
119
|
+
* nats: buildNatsClientOptions({servers: 'nats://127.0.0.1:4222', name: 'pricing'})
|
|
120
|
+
* }
|
|
121
|
+
* })
|
|
122
|
+
* .onLaunched(async (app: Application): Promise<void> => {
|
|
123
|
+
* const nats: NATS = await app.getObject('nats')
|
|
124
|
+
* nats.on('error', (error: Error): void => console.error('NATS message handling failed', error))
|
|
125
|
+
* //Answer the requests, shared between the instances of the application: the value returned is the response
|
|
126
|
+
* nats.subscribe('price.get', async (input: {sku: string}): Promise<number> => input.sku.length * 10, {queue: 'pricing'})
|
|
127
|
+
* //The messages of every region: the subject tells which one
|
|
128
|
+
* nats.subscribe('price.changed.*', (price: number, subject: string): void => console.log(subject, price))
|
|
129
|
+
* nats.publish('price.changed.eu', 42)
|
|
130
|
+
* console.log(await nats.request('price.get', {sku: 'ABC'}, 5000)) //30
|
|
131
|
+
* //The service events of this application, received by the applications listening to them
|
|
132
|
+
* nats.emitServiceEvent('priceChanged', 'ABC', 30)
|
|
133
|
+
* nats.onServiceEvent('stock.service', 'stockChanged', (sku: string, quantity: number): void => console.log(sku, quantity))
|
|
134
|
+
* })
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
class NATS extends core_1.Component {
|
|
138
|
+
constructor() {
|
|
139
|
+
super(...arguments);
|
|
140
|
+
/**
|
|
141
|
+
* The subscriptions of the service event listeners, by event subject, then by listener.
|
|
142
|
+
*/
|
|
143
|
+
this.serviceEventSubscriptionMap = new Map();
|
|
144
|
+
/**
|
|
145
|
+
* Whether the peers (by subject) support the bulk references, learned from the capability header of their responses and requests.
|
|
146
|
+
* A reference is only sent to a peer known to support it, the others get the payload itself (backward compatible)
|
|
147
|
+
* @private
|
|
148
|
+
*/
|
|
149
|
+
this.#peerCapabilityCache = new Map();
|
|
150
|
+
/**
|
|
151
|
+
* Whether the component closes its connections (destroyed): their close is not an error then
|
|
152
|
+
*/
|
|
153
|
+
this.#closing = false;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* NATS client instance
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
#conn;
|
|
160
|
+
/**
|
|
161
|
+
* The bulk connection and object store (only when the bulk channel is enabled)
|
|
162
|
+
* @private
|
|
163
|
+
*/
|
|
164
|
+
#bulkConn;
|
|
165
|
+
#objectStore;
|
|
166
|
+
/**
|
|
167
|
+
* Whether the peers (by subject) support the bulk references, learned from the capability header of their responses and requests.
|
|
168
|
+
* A reference is only sent to a peer known to support it, the others get the payload itself (backward compatible)
|
|
169
|
+
* @private
|
|
170
|
+
*/
|
|
171
|
+
#peerCapabilityCache;
|
|
172
|
+
/**
|
|
173
|
+
* Whether the component closes its connections (destroyed): their close is not an error then
|
|
174
|
+
*/
|
|
175
|
+
#closing;
|
|
176
|
+
/**
|
|
177
|
+
* Connect to the server, then prepare the bulk channel when it is enabled (a failure of the bulk channel only
|
|
178
|
+
* disables it, with a warning).
|
|
179
|
+
* @throws {Error} The NATS client error when no server can be reached.
|
|
180
|
+
*/
|
|
181
|
+
async init() {
|
|
182
|
+
const connectOptions = {
|
|
183
|
+
servers: this.servers,
|
|
184
|
+
user: this.user,
|
|
185
|
+
pass: this.pass,
|
|
186
|
+
timeout: this.timeout,
|
|
187
|
+
token: this.token,
|
|
188
|
+
debug: this.debug,
|
|
189
|
+
maxPingOut: this.maxPingOut,
|
|
190
|
+
maxReconnectAttempts: this.maxReconnectAttempts,
|
|
191
|
+
name: this.name,
|
|
192
|
+
noEcho: this.noEcho,
|
|
193
|
+
noRandomize: this.noRandomize,
|
|
194
|
+
pingInterval: this.pingInterval,
|
|
195
|
+
reconnect: this.reconnect
|
|
196
|
+
};
|
|
197
|
+
this.#conn = await (0, nats_1.connect)(connectOptions);
|
|
198
|
+
//Closed without being destroyed (its reconnection given up): reported, the calls failing from now on
|
|
199
|
+
const conn = this.#conn;
|
|
200
|
+
void conn.closed().then((error) => {
|
|
201
|
+
if (this.#closing)
|
|
202
|
+
return;
|
|
203
|
+
this.emitError(error ?? new Error('The NATS connection is closed: its reconnection was given up, the calls fail with CONNECTION_CLOSED'));
|
|
204
|
+
});
|
|
205
|
+
if (this.bulk) {
|
|
206
|
+
try {
|
|
207
|
+
//A separate connection for the large payloads, so they never block the connection of the requests;
|
|
208
|
+
//the bucket of the object store keeping them is got or created (short time to live)
|
|
209
|
+
this.#bulkConn = await (0, nats_1.connect)({
|
|
210
|
+
...connectOptions,
|
|
211
|
+
name: this.name ? `${this.name}-bulk` : undefined
|
|
212
|
+
});
|
|
213
|
+
const bucket = this.bulkBucket ?? `lkt-bulk-${this.name ?? 'default'}`;
|
|
214
|
+
//The replicas are only sent when configured: by default the server uses its default (one replica).
|
|
215
|
+
//A strict NATS server rejects the field (invalid json: unknown field "replicas"),
|
|
216
|
+
//and sending the default brings nothing
|
|
217
|
+
const osOptions = {
|
|
218
|
+
storage: nats_1.StorageType.File,
|
|
219
|
+
ttl: (0, nats_1.nanos)(this.bulkTTL ?? 5 * 60 * 1000)
|
|
220
|
+
};
|
|
221
|
+
if (this.bulkReplicas !== undefined)
|
|
222
|
+
osOptions.replicas = this.bulkReplicas;
|
|
223
|
+
this.#objectStore = await this.#bulkConn.jetstream().views.os(bucket, osOptions);
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
//Graceful degradation: a failure of the bulk channel (no JetStream, creation rejected, timeout…) never fails the start.
|
|
227
|
+
//The half created connection is closed and the bulk channel disabled (bulkEnabled depends on the object store),
|
|
228
|
+
//the requests and the events keep working, only the large payloads cannot be relayed
|
|
229
|
+
this.#objectStore = undefined;
|
|
230
|
+
if (this.#bulkConn) {
|
|
231
|
+
try {
|
|
232
|
+
await this.#bulkConn.close();
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
//The close failed
|
|
236
|
+
}
|
|
237
|
+
this.#bulkConn = undefined;
|
|
238
|
+
}
|
|
239
|
+
console.warn(`[NATS] The bulk channel failed to start and is disabled (the requests and the events are not affected, the large payloads cannot be relayed): ${e?.message ?? e}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Drain the connections: stop receiving, finish the messages in progress and flush the outgoing ones, then close.
|
|
245
|
+
*/
|
|
246
|
+
async destroy() {
|
|
247
|
+
this.#closing = true;
|
|
248
|
+
await this.drainConnection(this.#conn);
|
|
249
|
+
if (this.#bulkConn)
|
|
250
|
+
await this.drainConnection(this.#bulkConn);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Graceful shutdown: drain stops receiving, finishes the messages in progress and flushes the outgoing ones before closing,
|
|
254
|
+
* so the requests in progress survive a rolling update; a failed drain closes the connection
|
|
255
|
+
* @param conn
|
|
256
|
+
* @private
|
|
257
|
+
*/
|
|
258
|
+
async drainConnection(conn) {
|
|
259
|
+
try {
|
|
260
|
+
await conn.drain();
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
try {
|
|
264
|
+
await conn.close();
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
//The connection is already unusable
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Publish a message: every subscription to the subject receives it (one per queue group). It is not answered, and is
|
|
273
|
+
* lost when nobody listens.
|
|
274
|
+
* @param subject The subject, without wildcards (`order.created`).
|
|
275
|
+
* @param payload The data, encoded with the codec.
|
|
276
|
+
* @throws {Error} The NATS client error `MAX_PAYLOAD_EXCEEDED` when the encoded payload is larger than the server's
|
|
277
|
+
* `max_payload` (the published messages are never relayed through the bulk channel), `CONNECTION_CLOSED` once the
|
|
278
|
+
* connection is closed, `BAD_SUBJECT` for an empty subject; the codec's error when the payload cannot be encoded.
|
|
279
|
+
*/
|
|
280
|
+
publish(subject, payload) {
|
|
281
|
+
return this.#conn.publish(subject, this.codec.encode(payload));
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Send a request and wait for its first response. A subscription answers it with its callback's return value (see
|
|
285
|
+
* {@link NATS.subscribe}); when nobody listens on the subject, it fails at once.
|
|
286
|
+
*
|
|
287
|
+
* A payload of 90% of the server's `max_payload` or more is relayed through the bulk channel when the subject's
|
|
288
|
+
* responders support it: learned from a previous response on the subject, else asked once by a probe of the subject
|
|
289
|
+
* (answered by the responders of this version with the bulk channel on; the older ones do not answer it). Otherwise it
|
|
290
|
+
* is sent as a message, and fails over `max_payload`. A large response is relayed the same way and read
|
|
291
|
+
* transparently.
|
|
292
|
+
* @param subject The subject of the responders.
|
|
293
|
+
* @param payload The data, encoded with the codec.
|
|
294
|
+
* @param timeout The time to wait for the response, in milliseconds; `0` or no value waits 600000 (10 minutes).
|
|
295
|
+
* @returns The response, decoded.
|
|
296
|
+
* @throws {NatsNoRespondersAvailableException} When nobody listens on the subject.
|
|
297
|
+
* @throws {NatsRequestTimeoutException} When no response arrives in time (the request may have been processed).
|
|
298
|
+
* @throws {NatsBulkException} When a large request cannot be stored, or a large response cannot be read.
|
|
299
|
+
* @throws {Error} The NATS client error `MAX_PAYLOAD_EXCEEDED` when a payload too large is not relayed,
|
|
300
|
+
* `CONNECTION_CLOSED` once the connection is closed; the codec's error when the payload or the response cannot be
|
|
301
|
+
* encoded or decoded.
|
|
302
|
+
*/
|
|
303
|
+
async request(subject, payload, timeout) {
|
|
304
|
+
const reqHeaders = (0, nats_1.headers)();
|
|
305
|
+
if (this.bulkEnabled)
|
|
306
|
+
reqHeaders.set(HDR_BULK, '1'); //The capability is declared when this side enabled the bulk channel
|
|
307
|
+
//The capability of the responders: known from a previous response, else probed when the payload needs it
|
|
308
|
+
const packed = await this.packPayload(payload, async () => this.#peerCapabilityCache.get(subject) ?? await this.probeBulk(subject));
|
|
309
|
+
if (packed.isRef)
|
|
310
|
+
reqHeaders.set(HDR_REF, '1');
|
|
311
|
+
try {
|
|
312
|
+
//Without a timeout (or 0), 10 minutes: the NATS client waits only a second otherwise
|
|
313
|
+
const response = await this.#conn.request(subject, packed.bytes, { timeout: timeout || DEFAULT_REQUEST_TIMEOUT, headers: reqHeaders });
|
|
314
|
+
//A status message of the server (400, 403, 404, 408, 500…) rather than a response: its exception
|
|
315
|
+
if (response.headers?.hasError && !response.data.length)
|
|
316
|
+
throw Object.assign(new Error(response.headers.description || `NATS status ${response.headers.code}`), { code: response.headers.code });
|
|
317
|
+
//Learn from the response whether the peer supports the bulk references
|
|
318
|
+
this.#peerCapabilityCache.set(subject, response.headers?.get(HDR_BULK) === '1');
|
|
319
|
+
//Restore the response (possibly a reference to the object store)
|
|
320
|
+
return await this.unpackPayload(response.data, response.headers?.get(HDR_REF) === '1');
|
|
321
|
+
}
|
|
322
|
+
catch (e) {
|
|
323
|
+
if (e.code) {
|
|
324
|
+
switch (e.code.toString()) {
|
|
325
|
+
case '400':
|
|
326
|
+
throw new NatsBadRequestException_js_1.NatsBadRequestException();
|
|
327
|
+
case '403':
|
|
328
|
+
throw new NatsForbiddenException_js_1.NatsForbiddenException();
|
|
329
|
+
case '404':
|
|
330
|
+
throw new NatsNotFoundException_js_1.NatsNotFoundException();
|
|
331
|
+
case '408':
|
|
332
|
+
case 'TIMEOUT':
|
|
333
|
+
throw new NatsRequestTimeoutException_js_1.NatsRequestTimeoutException();
|
|
334
|
+
case '503':
|
|
335
|
+
throw new NatsNoRespondersAvailableException_js_1.NatsNoRespondersAvailableException();
|
|
336
|
+
case '500':
|
|
337
|
+
throw new NatsInternalServerException_js_1.NatsInternalServerException();
|
|
338
|
+
default:
|
|
339
|
+
throw e;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
throw e;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
//The referenced request object was read (the response came back), it is deleted; its time to live covers a failure
|
|
348
|
+
if (packed.objId)
|
|
349
|
+
this.deleteBulk(packed.objId).catch(() => undefined);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Subscribe to a subject and handle its messages with a callback. The payloads are decoded (and read from the bulk
|
|
354
|
+
* channel when they are references) before the callback runs. A request (a message with a reply subject, sent by
|
|
355
|
+
* {@link NATS.request}) is answered with the callback's return value, awaited and encoded, relayed through the bulk
|
|
356
|
+
* channel when it is large and the requester supports it; the return value of a published message is ignored.
|
|
357
|
+
*
|
|
358
|
+
* When the callback throws (or a payload cannot be decoded, or a response cannot be sent), the error is emitted as an
|
|
359
|
+
* `'error'` event, and a request is answered with `subscribeOptions.errorResponse`, or not answered at all (the
|
|
360
|
+
* requester waits for its timeout).
|
|
361
|
+
* @param subject The subject, possibly with wildcards: `*` matches one token (`order.*`), `>` the remaining tokens
|
|
362
|
+
* (`order.>`).
|
|
363
|
+
* @param callback The handler: receives the decoded payload and the subject the message arrived on (useful with the
|
|
364
|
+
* wildcards), and returns the response to a request.
|
|
365
|
+
* @param subscribeOptions The queue group, the number of messages, the sequential handling and the error response.
|
|
366
|
+
* @returns The NATS subscription: `unsubscribe()` stops it at once, `drain()` once the messages received are handled.
|
|
367
|
+
* @throws {Error} The NATS client error `CONNECTION_CLOSED` once the connection is closed, `BAD_SUBJECT` for an invalid
|
|
368
|
+
* subject.
|
|
369
|
+
*/
|
|
370
|
+
subscribe(subject, callback, subscribeOptions) {
|
|
371
|
+
return this.subscribeTo(subject, callback, subscribeOptions, true);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Subscribe to a subject, as {@link NATS.subscribe}; the subscriptions of the events do not answer the capability
|
|
375
|
+
* probes of the requesters (nobody requests them).
|
|
376
|
+
* @param subject The subject, wildcards allowed.
|
|
377
|
+
* @param callback Called with each message's data and subject; its result answers a request.
|
|
378
|
+
* @param subscribeOptions The queue group, the number of messages, the iteration and the error responses.
|
|
379
|
+
* @param answerProbes Whether the subscription answers the capability probes of the large requests to its subject.
|
|
380
|
+
* @returns The NATS subscription.
|
|
381
|
+
* @protected
|
|
382
|
+
*/
|
|
383
|
+
subscribeTo(subject, callback, subscribeOptions, answerProbes) {
|
|
384
|
+
const subscription = this.#conn.subscribe(subject, {
|
|
385
|
+
queue: subscribeOptions?.queue,
|
|
386
|
+
max: subscribeOptions?.max
|
|
387
|
+
});
|
|
388
|
+
if (answerProbes && this.bulkEnabled && !subject.startsWith(BULK_PROBE)) {
|
|
389
|
+
//The capability probes of the requesters (their first large request): answered while the subscription lasts
|
|
390
|
+
const probes = this.#conn.subscribe(`${BULK_PROBE}${subject}`, {
|
|
391
|
+
queue: subscribeOptions?.queue,
|
|
392
|
+
callback: (error, msg) => {
|
|
393
|
+
if (error || !msg.reply)
|
|
394
|
+
return;
|
|
395
|
+
const capability = (0, nats_1.headers)();
|
|
396
|
+
capability.set(HDR_BULK, '1');
|
|
397
|
+
msg.respond(new Uint8Array(0), { headers: capability });
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
void subscription.closed.then(() => probes.unsubscribe(), () => probes.unsubscribe());
|
|
401
|
+
}
|
|
402
|
+
const handleMessage = async (msg) => {
|
|
403
|
+
try {
|
|
404
|
+
//A payload marked as a reference is read from the object store (transparent for the small messages and the events)
|
|
405
|
+
const data = await this.unpackPayload(msg.data, msg.headers?.get(HDR_REF) === '1');
|
|
406
|
+
const result = await callback(data, msg.subject);
|
|
407
|
+
//Only a request (with a reply subject) is answered, a published message or an event is not
|
|
408
|
+
if (msg.reply) {
|
|
409
|
+
const respHeaders = (0, nats_1.headers)();
|
|
410
|
+
if (this.bulkEnabled)
|
|
411
|
+
respHeaders.set(HDR_BULK, '1'); //The capability is declared when this side enabled the bulk channel
|
|
412
|
+
//A large response can be a reference when the requester supports them
|
|
413
|
+
const clientSupports = msg.headers?.get(HDR_BULK) === '1';
|
|
414
|
+
const packed = await this.packPayload(result, clientSupports);
|
|
415
|
+
if (packed.isRef)
|
|
416
|
+
respHeaders.set(HDR_REF, '1');
|
|
417
|
+
msg.respond(packed.bytes, { headers: respHeaders });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
catch (e) {
|
|
421
|
+
this.emitError(e);
|
|
422
|
+
//A failed request is answered with the error response of the upper layer (sent directly, with the capability), the requester does not wait for its timeout
|
|
423
|
+
if (msg.reply && subscribeOptions?.errorResponse) {
|
|
424
|
+
try {
|
|
425
|
+
const errHeaders = (0, nats_1.headers)();
|
|
426
|
+
if (this.bulkEnabled)
|
|
427
|
+
errHeaders.set(HDR_BULK, '1');
|
|
428
|
+
const errorPayload = subscribeOptions.errorResponse(e);
|
|
429
|
+
if (errorPayload !== undefined)
|
|
430
|
+
msg.respond(this.codec.encode(errorPayload), { headers: errHeaders });
|
|
431
|
+
}
|
|
432
|
+
catch (encodeError) {
|
|
433
|
+
this.emitError(encodeError);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
if (subscribeOptions?.iterator) {
|
|
439
|
+
//An error of the subscription (a permissions violation, a connection error) ends the iteration: it is reported
|
|
440
|
+
const consume = async () => {
|
|
441
|
+
try {
|
|
442
|
+
for await (const msg of subscription) {
|
|
443
|
+
await handleMessage(msg);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (e) {
|
|
447
|
+
this.emitError(e);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
setImmediate(() => void consume());
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
//handleMessage() reports the errors of the messages
|
|
454
|
+
subscription.callback = (err, msg) => {
|
|
455
|
+
//nats.js calls back with (err, {}) on a subscription error, the empty message cannot be handled: the error is reported
|
|
456
|
+
if (err) {
|
|
457
|
+
this.emitError(err);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
void handleMessage(msg);
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return subscription;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Emit an `'error'` event when the component has listeners, and do nothing otherwise (an `'error'` event
|
|
467
|
+
* without listener would throw).
|
|
468
|
+
* @param error The error to report.
|
|
469
|
+
*/
|
|
470
|
+
emitError(error) {
|
|
471
|
+
if (this.listenerCount('error') > 0)
|
|
472
|
+
this.emit('error', error);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Whether the bulk channel is ready: enabled by the `bulk` option and started (its connection and object store
|
|
476
|
+
* prepared).
|
|
477
|
+
*/
|
|
478
|
+
get bulkEnabled() {
|
|
479
|
+
return !!this.bulk && !!this.#objectStore;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* The encoded size from which a payload is relayed through the bulk channel, in bytes: 90% of the current server's
|
|
483
|
+
* `max_payload` (1 MiB when unknown).
|
|
484
|
+
*/
|
|
485
|
+
get bulkThreshold() {
|
|
486
|
+
const maxPayload = this.#conn.info?.max_payload ?? 1024 * 1024;
|
|
487
|
+
return Math.floor(maxPayload * 0.9);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Whether the current server supports the message headers, which the bulk channel needs to mark its references.
|
|
491
|
+
*/
|
|
492
|
+
get headersSupported() {
|
|
493
|
+
return this.#conn.info?.headers === true;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Store bytes in the bulk channel's object store, for its time to live (`bulkTTL`).
|
|
497
|
+
* @param bytes The bytes to store.
|
|
498
|
+
* @returns The id of the object (a random UUID), to read it with {@link NATS.getBulk}.
|
|
499
|
+
* @throws {NatsBulkException} When the bulk channel is not enabled, or the object store rejects the write.
|
|
500
|
+
*/
|
|
501
|
+
async putBulk(bytes) {
|
|
502
|
+
if (!this.#objectStore)
|
|
503
|
+
throw new NatsBulkException_js_1.NatsBulkException('bulk channel is not enabled');
|
|
504
|
+
const objId = (0, node_crypto_1.randomUUID)();
|
|
505
|
+
try {
|
|
506
|
+
await this.#objectStore.putBlob({ name: objId }, bytes);
|
|
507
|
+
}
|
|
508
|
+
catch (e) {
|
|
509
|
+
throw new NatsBulkException_js_1.NatsBulkException(`bulk put failed: ${e?.message ?? e}`);
|
|
510
|
+
}
|
|
511
|
+
return objId;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Read an object from the bulk channel's object store. The object is not deleted.
|
|
515
|
+
* @param objId The id returned by {@link NATS.putBulk}.
|
|
516
|
+
* @returns The bytes stored.
|
|
517
|
+
* @throws {NatsBulkException} When the bulk channel is not enabled, the object store rejects the read, or the object
|
|
518
|
+
* does not exist (never stored, expired, or deleted).
|
|
519
|
+
*/
|
|
520
|
+
async getBulk(objId) {
|
|
521
|
+
if (!this.#objectStore)
|
|
522
|
+
throw new NatsBulkException_js_1.NatsBulkException('bulk channel is not enabled');
|
|
523
|
+
let data;
|
|
524
|
+
try {
|
|
525
|
+
data = await this.#objectStore.getBlob(objId);
|
|
526
|
+
}
|
|
527
|
+
catch (e) {
|
|
528
|
+
throw new NatsBulkException_js_1.NatsBulkException(`bulk get failed: ${e?.message ?? e}`);
|
|
529
|
+
}
|
|
530
|
+
if (data === null)
|
|
531
|
+
throw new NatsBulkException_js_1.NatsBulkException(`bulk object not found: ${objId}`);
|
|
532
|
+
return data;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Delete an object from the bulk channel's object store; nothing happens when the bulk channel is not enabled.
|
|
536
|
+
* @param objId The id returned by {@link NATS.putBulk}.
|
|
537
|
+
* @throws {Error} The object store's error when the deletion fails.
|
|
538
|
+
*/
|
|
539
|
+
async deleteBulk(objId) {
|
|
540
|
+
if (!this.#objectStore)
|
|
541
|
+
return;
|
|
542
|
+
await this.#objectStore.delete(objId);
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Read the status of the bulk channel's object store: its bucket, time to live, replicas, storage and size.
|
|
546
|
+
* @returns The status, or `undefined` when the bulk channel is not enabled.
|
|
547
|
+
*/
|
|
548
|
+
async bulkStatus() {
|
|
549
|
+
return this.#objectStore?.status();
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Ask the responders of a subject whether they support the bulk references, before the first large request to it:
|
|
553
|
+
* the responders supporting them answer on the probe subject, the others (an older version, the bulk channel off)
|
|
554
|
+
* have no subscription there (no responders, or no answer in time). The answer is kept for the subject.
|
|
555
|
+
* @param subject The subject of the request.
|
|
556
|
+
* @returns Whether its responders support the bulk references.
|
|
557
|
+
* @private
|
|
558
|
+
*/
|
|
559
|
+
async probeBulk(subject) {
|
|
560
|
+
let supported = false;
|
|
561
|
+
try {
|
|
562
|
+
const response = await this.#conn.request(`${BULK_PROBE}${subject}`, new Uint8Array(0), { timeout: 2000 });
|
|
563
|
+
supported = response.headers?.get(HDR_BULK) === '1';
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
//No responder on the probe subject: not supported
|
|
567
|
+
}
|
|
568
|
+
this.#peerCapabilityCache.set(subject, supported);
|
|
569
|
+
return supported;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Pack a payload to send: encoded, then stored in the object store and sent as a small reference when it is large and the peer supports it
|
|
573
|
+
* @param payload
|
|
574
|
+
* @param peerSupportsBulk whether the peer supports the references (the payload itself is sent otherwise)
|
|
575
|
+
* @private
|
|
576
|
+
*/
|
|
577
|
+
async packPayload(payload, peerSupportsBulk) {
|
|
578
|
+
const bytes = this.codec.encode(payload);
|
|
579
|
+
if (this.bulkEnabled && bytes.length >= this.bulkThreshold && (typeof peerSupportsBulk === 'function' ? await peerSupportsBulk() : peerSupportsBulk)) {
|
|
580
|
+
const objId = await this.putBulk(bytes);
|
|
581
|
+
return { bytes: this.codec.encode({ __ref: objId }), isRef: true, objId };
|
|
582
|
+
}
|
|
583
|
+
return { bytes, isRef: false };
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Unpack received bytes: a reference is read from the object store then decoded (deleted once read, its time to live covers a failure)
|
|
587
|
+
* @param bytes
|
|
588
|
+
* @param isRef
|
|
589
|
+
* @private
|
|
590
|
+
*/
|
|
591
|
+
async unpackPayload(bytes, isRef) {
|
|
592
|
+
if (isRef) {
|
|
593
|
+
const ref = this.codec.decode(bytes);
|
|
594
|
+
const realBytes = await this.getBulk(ref.__ref);
|
|
595
|
+
this.deleteBulk(ref.__ref).catch(() => undefined);
|
|
596
|
+
return this.codec.decode(realBytes);
|
|
597
|
+
}
|
|
598
|
+
return this.codec.decode(bytes);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Emit a service event of this application: the listeners registered for it with the application's id (with
|
|
602
|
+
* {@link NATS.onServiceEvent} or `ServiceProxy.on()`) receive the arguments. The event is published at most once: the
|
|
603
|
+
* applications not listening, or disconnected, miss it.
|
|
604
|
+
* @param eventName The event name.
|
|
605
|
+
* @param args The arguments given to the listeners, encoded with the codec (as one array).
|
|
606
|
+
* @returns The component, for chaining.
|
|
607
|
+
* @throws {Error} The NATS client error `MAX_PAYLOAD_EXCEEDED` when the encoded arguments are larger than the server's
|
|
608
|
+
* `max_payload` (the events are never relayed through the bulk channel), `CONNECTION_CLOSED` once the connection is
|
|
609
|
+
* closed; the codec's error when the arguments cannot be encoded.
|
|
610
|
+
*/
|
|
611
|
+
emitServiceEvent(eventName, ...args) {
|
|
612
|
+
const eventSubject = ServiceEventCodec_js_1.ServiceEventCodec.formatSubject(this.app.appId, eventName);
|
|
613
|
+
this.publish(eventSubject, ServiceEventCodec_js_1.ServiceEventCodec.encode(args));
|
|
614
|
+
return this;
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Listen to a service event of an application. A listener registered again for the same event (by this method or
|
|
618
|
+
* {@link NATS.onceServiceEvent}) is ignored. A listener which throws is reported as an `'error'` event; the rejection of
|
|
619
|
+
* an asynchronous listener is not caught.
|
|
620
|
+
* @param serviceId The id of the application emitting the event (its `id` option).
|
|
621
|
+
* @param eventName The event name.
|
|
622
|
+
* @param listener The listener, receiving the event's arguments.
|
|
623
|
+
* @param onlySingleClientReceived Whether each event goes to one of the listeners registered with `true` for it, across
|
|
624
|
+
* all the applications (a queue group), instead of to each of them; the listeners registered with `false` still receive
|
|
625
|
+
* every event.
|
|
626
|
+
* @returns The component, for chaining.
|
|
627
|
+
* @throws {Error} The NATS client error `CONNECTION_CLOSED` once the connection is closed.
|
|
628
|
+
*/
|
|
629
|
+
onServiceEvent(serviceId, eventName, listener, onlySingleClientReceived = false) {
|
|
630
|
+
const eventSubject = ServiceEventCodec_js_1.ServiceEventCodec.formatSubject(serviceId, eventName);
|
|
631
|
+
if (!this.serviceEventSubscriptionMap.has(eventSubject))
|
|
632
|
+
this.serviceEventSubscriptionMap.set(eventSubject, new Map());
|
|
633
|
+
if (!this.serviceEventSubscriptionMap.get(eventSubject)?.has(listener)) {
|
|
634
|
+
const subscription = this.subscribeTo(eventSubject, (eventData) => {
|
|
635
|
+
//An async listener: its rejection is reported as an error of the component, as a synchronous throw
|
|
636
|
+
const result = listener(...ServiceEventCodec_js_1.ServiceEventCodec.decode(eventData));
|
|
637
|
+
if (result instanceof Promise)
|
|
638
|
+
result.catch((error) => this.emitError(error));
|
|
639
|
+
}, onlySingleClientReceived ? { queue: eventSubject } : void (0), false);
|
|
640
|
+
this.serviceEventSubscriptionMap.get(eventSubject)?.set(listener, subscription);
|
|
641
|
+
}
|
|
642
|
+
return this;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Listen to the next occurrence of a service event of an application only: the listener is removed once called. A
|
|
646
|
+
* listener already registered for the event is ignored.
|
|
647
|
+
* @param serviceId The id of the application emitting the event (its `id` option).
|
|
648
|
+
* @param eventName The event name.
|
|
649
|
+
* @param listener The listener, receiving the event's arguments.
|
|
650
|
+
* @returns The component, for chaining.
|
|
651
|
+
* @throws {Error} The NATS client error `CONNECTION_CLOSED` once the connection is closed.
|
|
652
|
+
*/
|
|
653
|
+
onceServiceEvent(serviceId, eventName, listener) {
|
|
654
|
+
const eventSubject = ServiceEventCodec_js_1.ServiceEventCodec.formatSubject(serviceId, eventName);
|
|
655
|
+
if (!this.serviceEventSubscriptionMap.has(eventSubject))
|
|
656
|
+
this.serviceEventSubscriptionMap.set(eventSubject, new Map());
|
|
657
|
+
if (!this.serviceEventSubscriptionMap.get(eventSubject)?.has(listener)) {
|
|
658
|
+
const subscription = this.subscribeTo(eventSubject, (eventData) => {
|
|
659
|
+
this.serviceEventSubscriptionMap.get(eventSubject)?.delete(listener);
|
|
660
|
+
const result = listener(...ServiceEventCodec_js_1.ServiceEventCodec.decode(eventData));
|
|
661
|
+
if (result instanceof Promise)
|
|
662
|
+
result.catch((error) => this.emitError(error));
|
|
663
|
+
}, { max: 1 }, false);
|
|
664
|
+
this.serviceEventSubscriptionMap.get(eventSubject)?.set(listener, subscription);
|
|
665
|
+
}
|
|
666
|
+
return this;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Stop listening to a service event of an application.
|
|
670
|
+
* @param serviceId The id of the application emitting the event.
|
|
671
|
+
* @param eventName The event name.
|
|
672
|
+
* @param listener The listener to remove; without it, every listener registered on this component for the event is
|
|
673
|
+
* removed (those of the service proxies using the component included).
|
|
674
|
+
* @returns The component, for chaining.
|
|
675
|
+
*/
|
|
676
|
+
offServiceEvent(serviceId, eventName, listener) {
|
|
677
|
+
const eventSubject = ServiceEventCodec_js_1.ServiceEventCodec.formatSubject(serviceId, eventName);
|
|
678
|
+
if (listener) {
|
|
679
|
+
this.serviceEventSubscriptionMap.get(eventSubject)?.get(listener)?.unsubscribe();
|
|
680
|
+
this.serviceEventSubscriptionMap.get(eventSubject)?.delete(listener);
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
this.serviceEventSubscriptionMap.get(eventSubject)?.forEach((subscription) => subscription.unsubscribe());
|
|
684
|
+
this.serviceEventSubscriptionMap.delete(eventSubject);
|
|
685
|
+
}
|
|
686
|
+
return this;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
exports.NATS = NATS;
|
|
690
|
+
__decorate([
|
|
691
|
+
(0, di_1.Inject)(core_1.Application),
|
|
692
|
+
__metadata("design:type", core_1.Application
|
|
693
|
+
/**
|
|
694
|
+
* The NATS server, or the servers of a cluster: `host:port` or a URL (`nats://user:pass@host:4222`).
|
|
695
|
+
*/
|
|
696
|
+
)
|
|
697
|
+
], NATS.prototype, "app", void 0);
|
|
698
|
+
__decorate([
|
|
699
|
+
(0, di_1.Configurable)(core_1.DTO.Alternatives(core_1.DTO.String(), core_1.DTO.Array(core_1.DTO.String())).required()),
|
|
700
|
+
__metadata("design:type", Object)
|
|
701
|
+
], NATS.prototype, "servers", void 0);
|
|
702
|
+
__decorate([
|
|
703
|
+
(0, di_1.Configurable)(core_1.DTO.Object({
|
|
704
|
+
encode: core_1.DTO.Function().arity(1).required(),
|
|
705
|
+
decode: core_1.DTO.Function().arity(1).required()
|
|
706
|
+
}).optional().default((0, MessagePackCodec_js_1.messagePackCodec)())),
|
|
707
|
+
__metadata("design:type", Object)
|
|
708
|
+
], NATS.prototype, "codec", void 0);
|
|
709
|
+
__decorate([
|
|
710
|
+
(0, di_1.Configurable)(core_1.DTO.Number().integer().optional()),
|
|
711
|
+
__metadata("design:type", Number)
|
|
712
|
+
], NATS.prototype, "timeout", void 0);
|
|
713
|
+
__decorate([
|
|
714
|
+
(0, di_1.Configurable)(core_1.DTO.String().optional()),
|
|
715
|
+
__metadata("design:type", String)
|
|
716
|
+
], NATS.prototype, "token", void 0);
|
|
717
|
+
__decorate([
|
|
718
|
+
(0, di_1.Configurable)(core_1.DTO.String().optional()),
|
|
719
|
+
__metadata("design:type", String)
|
|
720
|
+
], NATS.prototype, "user", void 0);
|
|
721
|
+
__decorate([
|
|
722
|
+
(0, di_1.Configurable)(core_1.DTO.String().optional()),
|
|
723
|
+
__metadata("design:type", String)
|
|
724
|
+
], NATS.prototype, "pass", void 0);
|
|
725
|
+
__decorate([
|
|
726
|
+
(0, di_1.Configurable)(core_1.DTO.Boolean().optional()),
|
|
727
|
+
__metadata("design:type", Boolean)
|
|
728
|
+
], NATS.prototype, "debug", void 0);
|
|
729
|
+
__decorate([
|
|
730
|
+
(0, di_1.Configurable)(core_1.DTO.Number().optional()),
|
|
731
|
+
__metadata("design:type", Number)
|
|
732
|
+
], NATS.prototype, "maxPingOut", void 0);
|
|
733
|
+
__decorate([
|
|
734
|
+
(0, di_1.Configurable)(core_1.DTO.Number().optional().default(10)),
|
|
735
|
+
__metadata("design:type", Number)
|
|
736
|
+
], NATS.prototype, "maxReconnectAttempts", void 0);
|
|
737
|
+
__decorate([
|
|
738
|
+
(0, di_1.Configurable)(core_1.DTO.String().optional()),
|
|
739
|
+
__metadata("design:type", String)
|
|
740
|
+
], NATS.prototype, "name", void 0);
|
|
741
|
+
__decorate([
|
|
742
|
+
(0, di_1.Configurable)(core_1.DTO.Boolean().optional()),
|
|
743
|
+
__metadata("design:type", Boolean)
|
|
744
|
+
], NATS.prototype, "noEcho", void 0);
|
|
745
|
+
__decorate([
|
|
746
|
+
(0, di_1.Configurable)(core_1.DTO.Boolean().optional()),
|
|
747
|
+
__metadata("design:type", Boolean)
|
|
748
|
+
], NATS.prototype, "noRandomize", void 0);
|
|
749
|
+
__decorate([
|
|
750
|
+
(0, di_1.Configurable)(core_1.DTO.Number().optional().default(1000 * 60 * 2)),
|
|
751
|
+
__metadata("design:type", Number)
|
|
752
|
+
], NATS.prototype, "pingInterval", void 0);
|
|
753
|
+
__decorate([
|
|
754
|
+
(0, di_1.Configurable)(core_1.DTO.Boolean().optional().default(true)),
|
|
755
|
+
__metadata("design:type", Boolean)
|
|
756
|
+
], NATS.prototype, "reconnect", void 0);
|
|
757
|
+
__decorate([
|
|
758
|
+
(0, di_1.Configurable)(core_1.DTO.Boolean().optional().default(true)),
|
|
759
|
+
__metadata("design:type", Boolean)
|
|
760
|
+
], NATS.prototype, "bulk", void 0);
|
|
761
|
+
__decorate([
|
|
762
|
+
(0, di_1.Configurable)(core_1.DTO.String().optional()),
|
|
763
|
+
__metadata("design:type", String)
|
|
764
|
+
], NATS.prototype, "bulkBucket", void 0);
|
|
765
|
+
__decorate([
|
|
766
|
+
(0, di_1.Configurable)(core_1.DTO.Number().integer().optional().default(5 * 60 * 1000)),
|
|
767
|
+
__metadata("design:type", Number)
|
|
768
|
+
], NATS.prototype, "bulkTTL", void 0);
|
|
769
|
+
__decorate([
|
|
770
|
+
(0, di_1.Configurable)(core_1.DTO.Number().integer().optional()),
|
|
771
|
+
__metadata("design:type", Number)
|
|
772
|
+
], NATS.prototype, "bulkReplicas", void 0);
|