@kronos-ts/rabbitmq 0.3.2 → 0.3.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kronos-ts/rabbitmq",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "RabbitMQ extension for Kronos — distributed command and query transport.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@kronos-ts/common": "0.1.
|
|
56
|
-
"@kronos-ts/app": "0.
|
|
57
|
-
"@kronos-ts/eventsourcing": "0.
|
|
58
|
-
"@kronos-ts/messaging": "0.
|
|
59
|
-
"@kronos-ts/modelling": "0.
|
|
55
|
+
"@kronos-ts/common": "0.1.1",
|
|
56
|
+
"@kronos-ts/app": "0.3.3",
|
|
57
|
+
"@kronos-ts/eventsourcing": "0.2.0",
|
|
58
|
+
"@kronos-ts/messaging": "0.5.0",
|
|
59
|
+
"@kronos-ts/modelling": "0.2.3"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"amqplib": ">=0.10.0"
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { AmqpConnection } from "./connection.js";
|
|
2
|
-
import type { RabbitMqResolvedConfig } from "./rabbitmq.js";
|
|
3
|
-
/**
|
|
4
|
-
* Wire envelope for subscription-query update broadcasts.
|
|
5
|
-
*
|
|
6
|
-
* `kind: "update"` carries a payload to deliver to all matching local subscribers.
|
|
7
|
-
* `kind: "complete"` / `"completeExceptionally"` signal end-of-stream for all
|
|
8
|
-
* subscribers of the given query name on every process.
|
|
9
|
-
*
|
|
10
|
-
* `senderId` identifies the publishing instance so a process can drop its own
|
|
11
|
-
* loopback messages (the local query bus has already fanned out locally).
|
|
12
|
-
*/
|
|
13
|
-
export interface RabbitMqQueryUpdateEnvelope {
|
|
14
|
-
readonly kind: "update" | "complete" | "completeExceptionally";
|
|
15
|
-
readonly senderId: string;
|
|
16
|
-
readonly queryName: string;
|
|
17
|
-
readonly update?: unknown;
|
|
18
|
-
readonly error?: {
|
|
19
|
-
readonly name?: string;
|
|
20
|
-
readonly message: string;
|
|
21
|
-
readonly stack?: string;
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Serialized form of a structured `payloadEquals` filter. When present,
|
|
25
|
-
* receivers apply it against each local subscriber's stored query payload
|
|
26
|
-
* before delivering. Function filters do not serialize and therefore arrive
|
|
27
|
-
* without this field, causing the receiver to deliver to all local
|
|
28
|
-
* subscribers of {@link queryName}.
|
|
29
|
-
*/
|
|
30
|
-
readonly payloadEquals?: Record<string, unknown>;
|
|
31
|
-
}
|
|
32
|
-
export interface RabbitMqQueryUpdatesTransport {
|
|
33
|
-
/** Publish an update / complete envelope to every subscribed instance. */
|
|
34
|
-
publish(envelope: RabbitMqQueryUpdateEnvelope): Promise<void>;
|
|
35
|
-
/** Bind this instance's queue to the routing key for the query name. Idempotent. */
|
|
36
|
-
bindQueryName(queryName: string): Promise<void>;
|
|
37
|
-
/** Unbind this instance's queue from the routing key. Idempotent. */
|
|
38
|
-
unbindQueryName(queryName: string): Promise<void>;
|
|
39
|
-
/** Set the in-process handler invoked when an inbound update arrives. */
|
|
40
|
-
setHandler(handler: (envelope: RabbitMqQueryUpdateEnvelope) => void): void;
|
|
41
|
-
/** Stable identifier for this publisher; appears as `senderId` on outbound envelopes. */
|
|
42
|
-
readonly senderId: string;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* AMQP broadcast transport for subscription-query updates.
|
|
46
|
-
*
|
|
47
|
-
* Topology: a topic exchange (`<prefix>.query-updates`) and one exclusive
|
|
48
|
-
* auto-delete queue per instance (`<prefix>.query-updates.<service>.<instance>`).
|
|
49
|
-
* The bus dynamically binds the queue to a routing key per active query name.
|
|
50
|
-
*
|
|
51
|
-
* Consume mode is no-ack: updates are best-effort. Losing one update means a
|
|
52
|
-
* subscriber will see a stale view until the next emit — same recovery model
|
|
53
|
-
* as Axon Server when a broker round-trip is dropped.
|
|
54
|
-
*/
|
|
55
|
-
export declare class AmqpRabbitMqQueryUpdatesTransport implements RabbitMqQueryUpdatesTransport {
|
|
56
|
-
private readonly config;
|
|
57
|
-
private readonly connection;
|
|
58
|
-
private channel;
|
|
59
|
-
private connectPromise;
|
|
60
|
-
private closed;
|
|
61
|
-
private handler;
|
|
62
|
-
private readonly boundKeys;
|
|
63
|
-
readonly senderId: string;
|
|
64
|
-
constructor(config: RabbitMqResolvedConfig, connection: AmqpConnection);
|
|
65
|
-
connect(): Promise<void>;
|
|
66
|
-
private doConnect;
|
|
67
|
-
close(): Promise<void>;
|
|
68
|
-
publish(envelope: RabbitMqQueryUpdateEnvelope): Promise<void>;
|
|
69
|
-
bindQueryName(queryName: string): Promise<void>;
|
|
70
|
-
unbindQueryName(queryName: string): Promise<void>;
|
|
71
|
-
setHandler(handler: (envelope: RabbitMqQueryUpdateEnvelope) => void): void;
|
|
72
|
-
private handleInbound;
|
|
73
|
-
private requireChannel;
|
|
74
|
-
}
|
|
75
|
-
//# sourceMappingURL=amqp-query-updates-transport.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"amqp-query-updates-transport.d.ts","sourceRoot":"","sources":["../src/amqp-query-updates-transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAE3D;;;;;;;;;GASG;AACH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,uBAAuB,CAAA;IAC9D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KACxB,CAAA;IACD;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjD;AAED,MAAM,WAAW,6BAA6B;IAC5C,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7D,oFAAoF;IACpF,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,qEAAqE;IACrE,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,yEAAyE;IACzE,UAAU,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,2BAA2B,KAAK,IAAI,GAAG,IAAI,CAAA;IAC1E,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,iCAAkC,YAAW,6BAA6B;IAUnF,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAV7B,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAA+D;IAC9E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;IAE9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;gBAGN,MAAM,EAAE,sBAAsB,EAC9B,UAAU,EAAE,cAAc;IAKvC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAMhB,SAAS;IA6BjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,OAAO,CAAC,QAAQ,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAa7D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc/C,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcvD,UAAU,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,2BAA2B,KAAK,IAAI,GAAG,IAAI;IAI1E,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,cAAc;CAIvB"}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AMQP broadcast transport for subscription-query updates.
|
|
3
|
-
*
|
|
4
|
-
* Topology: a topic exchange (`<prefix>.query-updates`) and one exclusive
|
|
5
|
-
* auto-delete queue per instance (`<prefix>.query-updates.<service>.<instance>`).
|
|
6
|
-
* The bus dynamically binds the queue to a routing key per active query name.
|
|
7
|
-
*
|
|
8
|
-
* Consume mode is no-ack: updates are best-effort. Losing one update means a
|
|
9
|
-
* subscriber will see a stale view until the next emit — same recovery model
|
|
10
|
-
* as Axon Server when a broker round-trip is dropped.
|
|
11
|
-
*/
|
|
12
|
-
export class AmqpRabbitMqQueryUpdatesTransport {
|
|
13
|
-
config;
|
|
14
|
-
connection;
|
|
15
|
-
channel;
|
|
16
|
-
connectPromise;
|
|
17
|
-
closed = false;
|
|
18
|
-
handler;
|
|
19
|
-
boundKeys = new Set();
|
|
20
|
-
senderId;
|
|
21
|
-
constructor(config, connection) {
|
|
22
|
-
this.config = config;
|
|
23
|
-
this.connection = connection;
|
|
24
|
-
this.senderId = `${config.identity.serviceName}.${config.identity.instanceId}`;
|
|
25
|
-
}
|
|
26
|
-
async connect() {
|
|
27
|
-
if (this.connectPromise)
|
|
28
|
-
return this.connectPromise;
|
|
29
|
-
this.connectPromise = this.doConnect();
|
|
30
|
-
return this.connectPromise;
|
|
31
|
-
}
|
|
32
|
-
async doConnect() {
|
|
33
|
-
this.channel = await this.connection.channel();
|
|
34
|
-
await this.channel.assertExchange(this.config.topology.queryUpdatesExchange, "topic", {
|
|
35
|
-
durable: true,
|
|
36
|
-
});
|
|
37
|
-
await this.channel.assertQueue(this.config.topology.queryUpdatesQueue(), {
|
|
38
|
-
durable: false,
|
|
39
|
-
exclusive: true,
|
|
40
|
-
autoDelete: true,
|
|
41
|
-
});
|
|
42
|
-
await this.channel.consume(this.config.topology.queryUpdatesQueue(), (msg) => this.handleInbound(msg), { noAck: true });
|
|
43
|
-
// Rebind any routing keys requested before connect resolved.
|
|
44
|
-
for (const key of this.boundKeys) {
|
|
45
|
-
await this.channel.bindQueue(this.config.topology.queryUpdatesQueue(), this.config.topology.queryUpdatesExchange, key);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
async close() {
|
|
49
|
-
this.closed = true;
|
|
50
|
-
await this.channel?.close().catch(() => { });
|
|
51
|
-
}
|
|
52
|
-
async publish(envelope) {
|
|
53
|
-
await this.connect();
|
|
54
|
-
if (this.closed)
|
|
55
|
-
return;
|
|
56
|
-
const channel = this.requireChannel();
|
|
57
|
-
const routingKey = this.config.topology.queryUpdatesRoutingKey(envelope.queryName);
|
|
58
|
-
channel.publish(this.config.topology.queryUpdatesExchange, routingKey, Buffer.from(JSON.stringify(envelope)), { contentType: "application/json", persistent: false });
|
|
59
|
-
}
|
|
60
|
-
async bindQueryName(queryName) {
|
|
61
|
-
const routingKey = this.config.topology.queryUpdatesRoutingKey(queryName);
|
|
62
|
-
if (this.boundKeys.has(routingKey))
|
|
63
|
-
return;
|
|
64
|
-
this.boundKeys.add(routingKey);
|
|
65
|
-
await this.connect();
|
|
66
|
-
if (this.closed)
|
|
67
|
-
return;
|
|
68
|
-
const channel = this.requireChannel();
|
|
69
|
-
await channel.bindQueue(this.config.topology.queryUpdatesQueue(), this.config.topology.queryUpdatesExchange, routingKey);
|
|
70
|
-
}
|
|
71
|
-
async unbindQueryName(queryName) {
|
|
72
|
-
const routingKey = this.config.topology.queryUpdatesRoutingKey(queryName);
|
|
73
|
-
if (!this.boundKeys.has(routingKey))
|
|
74
|
-
return;
|
|
75
|
-
this.boundKeys.delete(routingKey);
|
|
76
|
-
if (this.closed)
|
|
77
|
-
return;
|
|
78
|
-
const channel = this.channel;
|
|
79
|
-
if (!channel)
|
|
80
|
-
return;
|
|
81
|
-
await channel.unbindQueue(this.config.topology.queryUpdatesQueue(), this.config.topology.queryUpdatesExchange, routingKey);
|
|
82
|
-
}
|
|
83
|
-
setHandler(handler) {
|
|
84
|
-
this.handler = handler;
|
|
85
|
-
}
|
|
86
|
-
handleInbound(msg) {
|
|
87
|
-
if (!msg)
|
|
88
|
-
return;
|
|
89
|
-
if (!this.handler)
|
|
90
|
-
return;
|
|
91
|
-
try {
|
|
92
|
-
const envelope = JSON.parse(msg.content.toString("utf8"));
|
|
93
|
-
this.handler(envelope);
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
// Malformed envelopes are dropped — broadcast is best-effort.
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
requireChannel() {
|
|
100
|
-
if (!this.channel)
|
|
101
|
-
throw new Error("RabbitMQ query-updates transport is not connected");
|
|
102
|
-
return this.channel;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
//# sourceMappingURL=amqp-query-updates-transport.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"amqp-query-updates-transport.js","sourceRoot":"","sources":["../src/amqp-query-updates-transport.ts"],"names":[],"mappings":"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"}
|