@hotmeshio/long-tail 0.26.0 → 0.27.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/build/index.d.ts +4 -0
- package/build/index.js +30 -8
- package/build/lib/events/nats.d.ts +11 -0
- package/build/lib/events/nats.js +33 -2
- package/build/lib/machines/index.d.ts +1 -0
- package/build/lib/machines/index.js +1 -0
- package/build/lib/machines/protocol/codec.d.ts +16 -0
- package/build/lib/machines/protocol/codec.js +1 -0
- package/build/lib/machines/protocol/contract.js +10 -1
- package/build/lib/machines/site/edge-account.d.ts +11 -0
- package/build/lib/machines/site/edge-account.js +24 -0
- package/build/lib/machines/site/site-conf.d.ts +7 -4
- package/build/lib/machines/site/site-conf.js +13 -6
- package/build/lib/machines/transport/delivery.d.ts +3 -4
- package/build/lib/machines/transport/delivery.js +3 -8
- package/build/lib/machines/transport/index.d.ts +2 -0
- package/build/lib/machines/transport/index.js +2 -0
- package/build/lib/machines/transport/nats-bus.d.ts +7 -13
- package/build/lib/machines/transport/nats-bus.js +23 -43
- package/build/lib/machines/transport/nats-feed.js +2 -2
- package/build/lib/machines/transport/pubsub-feed.d.ts +17 -0
- package/build/lib/machines/transport/pubsub-feed.js +39 -0
- package/build/lib/machines/transport/stream-bus.d.ts +40 -0
- package/build/lib/machines/transport/stream-bus.js +57 -0
- package/build/lib/machines/transport/streams.d.ts +10 -14
- package/build/lib/machines/transport/streams.js +18 -61
- package/build/lib/messaging/config.d.ts +25 -0
- package/build/lib/messaging/config.js +55 -0
- package/build/lib/messaging/constants.d.ts +26 -0
- package/build/lib/messaging/constants.js +26 -0
- package/build/lib/messaging/index.d.ts +24 -0
- package/build/lib/messaging/index.js +65 -0
- package/build/lib/messaging/memory.d.ts +16 -0
- package/build/lib/messaging/memory.js +147 -0
- package/build/lib/messaging/nats/permissions.d.ts +13 -0
- package/build/lib/messaging/nats/permissions.js +22 -0
- package/build/lib/messaging/nats/provider.d.ts +30 -0
- package/build/lib/messaging/nats/provider.js +77 -0
- package/build/lib/messaging/nats/render-conf.d.ts +68 -0
- package/build/lib/messaging/nats/render-conf.js +58 -0
- package/build/lib/messaging/nats/streams.d.ts +26 -0
- package/build/lib/messaging/nats/streams.js +97 -0
- package/build/lib/messaging/subjects.d.ts +8 -0
- package/build/lib/messaging/subjects.js +25 -0
- package/build/scripts/bambu-boat.d.ts +17 -0
- package/build/scripts/bambu-boat.js +134 -0
- package/build/scripts/bambu-print.d.ts +18 -0
- package/build/scripts/bambu-print.js +114 -0
- package/build/services/machines/config.d.ts +6 -0
- package/build/services/machines/config.js +16 -6
- package/build/services/machines/contracts.d.ts +19 -2
- package/build/services/machines/contracts.js +12 -3
- package/build/services/machines/gateway/actions.d.ts +1 -1
- package/build/services/machines/gateway/dispatcher.d.ts +16 -3
- package/build/services/machines/gateway/dispatcher.js +50 -10
- package/build/services/machines/gateway/executor.d.ts +7 -2
- package/build/services/machines/gateway/executor.js +35 -9
- package/build/services/machines/gateway/gateway.d.ts +6 -0
- package/build/services/machines/gateway/gateway.js +33 -10
- package/build/services/machines/gateway/lifecycle.d.ts +21 -0
- package/build/services/machines/gateway/lifecycle.js +89 -0
- package/build/services/machines/gateway/pacing.d.ts +14 -0
- package/build/services/machines/gateway/pacing.js +26 -2
- package/build/services/machines/gateway/plan-escalation.js +13 -4
- package/build/services/machines/gateway/plan-machine.d.ts +5 -4
- package/build/services/machines/gateway/plan-machine.js +55 -14
- package/build/services/machines/gateway/reconcile.d.ts +2 -0
- package/build/services/machines/gateway/reconcile.js +13 -0
- package/build/services/machines/gateway/state.d.ts +21 -1
- package/build/services/machines/gateway/state.js +16 -3
- package/build/services/machines/leadership.d.ts +17 -4
- package/build/services/machines/leadership.js +43 -18
- package/build/services/machines/rows.js +17 -2
- package/build/services/machines/select.d.ts +9 -2
- package/build/services/machines/select.js +20 -7
- package/build/services/machines/surface.js +8 -2
- package/build/services/machines/testing/fake-long-tail.d.ts +1 -0
- package/build/services/machines/testing/fake-long-tail.js +6 -3
- package/build/start/adapters.js +11 -2
- package/build/start/index.js +7 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/index.d.ts +1 -0
- package/build/types/machines.d.ts +2 -1
- package/build/types/messaging.d.ts +72 -0
- package/build/types/messaging.js +11 -0
- package/build/types/startup.d.ts +26 -1
- package/docs/events.md +50 -4
- package/docs/machines.md +38 -9
- package/package.json +3 -1
- package/build/lib/machines/transport/nats-durable.d.ts +0 -11
- package/build/lib/machines/transport/nats-durable.js +0 -28
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Long Tail's escalation events for a gateway running outside Long Tail, over
|
|
3
|
+
* Long Tail's messaging pub/sub. Nothing is replayed; a not-yet-actionable
|
|
4
|
+
* event is retried in this process, and the gateway reconciles open rows when
|
|
5
|
+
* it starts leading a line.
|
|
6
|
+
*/
|
|
7
|
+
import type { LTPubSub } from '../../../types/messaging';
|
|
8
|
+
import { type Disposition, type EscalationEvent, type EscalationFeed } from './delivery';
|
|
9
|
+
export declare class PubSubEscalationFeed implements EscalationFeed {
|
|
10
|
+
private readonly pubsub;
|
|
11
|
+
private sub?;
|
|
12
|
+
private readonly redelivery;
|
|
13
|
+
constructor(pubsub: LTPubSub, nakDelayMs?: number);
|
|
14
|
+
connect(): Promise<void>;
|
|
15
|
+
consume(handler: (event: EscalationEvent) => Promise<Disposition>): Promise<void>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Long Tail's escalation events for a gateway running outside Long Tail, over
|
|
4
|
+
* Long Tail's messaging pub/sub. Nothing is replayed; a not-yet-actionable
|
|
5
|
+
* event is retried in this process, and the gateway reconciles open rows when
|
|
6
|
+
* it starts leading a line.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.PubSubEscalationFeed = void 0;
|
|
10
|
+
const constants_1 = require("../../messaging/constants");
|
|
11
|
+
const serial_1 = require("./serial");
|
|
12
|
+
const delivery_1 = require("./delivery");
|
|
13
|
+
class PubSubEscalationFeed {
|
|
14
|
+
constructor(pubsub, nakDelayMs = 1_000) {
|
|
15
|
+
this.pubsub = pubsub;
|
|
16
|
+
this.redelivery = new serial_1.LocalRedelivery(new serial_1.SerialQueue(), nakDelayMs);
|
|
17
|
+
}
|
|
18
|
+
async connect() {
|
|
19
|
+
// The shared connection belongs to Long Tail's messaging registry.
|
|
20
|
+
}
|
|
21
|
+
async consume(handler) {
|
|
22
|
+
this.sub = this.pubsub.subscribe(constants_1.LT_SUBJECTS.ESCALATIONS, (m) => {
|
|
23
|
+
let event = null;
|
|
24
|
+
try {
|
|
25
|
+
event = (0, delivery_1.toEscalationEvent)(JSON.parse(m.payload));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
event = null;
|
|
29
|
+
}
|
|
30
|
+
if (event)
|
|
31
|
+
void this.redelivery.deliver(() => handler(event));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async close() {
|
|
35
|
+
await this.sub?.close();
|
|
36
|
+
await this.redelivery.stop();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.PubSubEscalationFeed = PubSubEscalationFeed;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gateway on Long Tail's messaging capability. Machine events come from the
|
|
3
|
+
* line's durable consumer on LINE_EVENTS (replayed after a restart); telemetry
|
|
4
|
+
* is read live, since it only proves a machine is alive. Commands leave through
|
|
5
|
+
* an MQTT session at QoS 1 on the site broker, which queues them for a machine
|
|
6
|
+
* that is away; retained machine state is read there too.
|
|
7
|
+
*/
|
|
8
|
+
import type { LTPubSub, LTStreams } from '../../../types/messaging';
|
|
9
|
+
import type { MqttEndpoint } from '../../../services/machines/config';
|
|
10
|
+
import { type StreamCodec } from './streams';
|
|
11
|
+
import type { Disposition, InboundMessage, MachineBus } from './delivery';
|
|
12
|
+
export interface StreamBusOptions {
|
|
13
|
+
streams: LTStreams;
|
|
14
|
+
pubsub: LTPubSub;
|
|
15
|
+
mqtt: MqttEndpoint;
|
|
16
|
+
/** The gateway's broker identity; also its MQTT client id and the root of its consumer's name. */
|
|
17
|
+
controllerId: string;
|
|
18
|
+
siteId: string;
|
|
19
|
+
lineId: string;
|
|
20
|
+
/** The codec's topics: which subjects are durable events and which are live telemetry. */
|
|
21
|
+
codec: StreamCodec;
|
|
22
|
+
nakDelayMs?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare class StreamMachineBus implements MachineBus {
|
|
25
|
+
private readonly opts;
|
|
26
|
+
private session?;
|
|
27
|
+
private subs;
|
|
28
|
+
/** Events and telemetry reach the gateway one at a time, in arrival order. */
|
|
29
|
+
private readonly queue;
|
|
30
|
+
constructor(opts: StreamBusOptions);
|
|
31
|
+
get durableName(): string;
|
|
32
|
+
connect(): Promise<void>;
|
|
33
|
+
consume(handler: (msg: InboundMessage) => Promise<Disposition>): Promise<void>;
|
|
34
|
+
publish(topic: string, payload: string, opts?: {
|
|
35
|
+
retain?: boolean;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
retained(filter: string): Promise<InboundMessage[]>;
|
|
38
|
+
close(): Promise<void>;
|
|
39
|
+
private mqtt;
|
|
40
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The gateway on Long Tail's messaging capability. Machine events come from the
|
|
4
|
+
* line's durable consumer on LINE_EVENTS (replayed after a restart); telemetry
|
|
5
|
+
* is read live, since it only proves a machine is alive. Commands leave through
|
|
6
|
+
* an MQTT session at QoS 1 on the site broker, which queues them for a machine
|
|
7
|
+
* that is away; retained machine state is read there too.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.StreamMachineBus = void 0;
|
|
11
|
+
const protocol_1 = require("../protocol");
|
|
12
|
+
const mqtt_session_1 = require("./mqtt-session");
|
|
13
|
+
const serial_1 = require("./serial");
|
|
14
|
+
const streams_1 = require("./streams");
|
|
15
|
+
class StreamMachineBus {
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
this.opts = opts;
|
|
18
|
+
this.subs = [];
|
|
19
|
+
/** Events and telemetry reach the gateway one at a time, in arrival order. */
|
|
20
|
+
this.queue = new serial_1.SerialQueue();
|
|
21
|
+
}
|
|
22
|
+
get durableName() {
|
|
23
|
+
return `${this.opts.controllerId}-events`;
|
|
24
|
+
}
|
|
25
|
+
async connect() {
|
|
26
|
+
const { streams, codec, siteId, lineId, controllerId } = this.opts;
|
|
27
|
+
for (const spec of (0, streams_1.lineStreamSpecs)(codec))
|
|
28
|
+
await streams.ensureStream(spec);
|
|
29
|
+
await streams.ensureConsumer(streams_1.LINE_STREAM.EVENTS, (0, streams_1.lineEventConsumerSpec)(codec, siteId, lineId, this.durableName));
|
|
30
|
+
this.session = await mqtt_session_1.MqttSession.open(this.opts.mqtt, controllerId);
|
|
31
|
+
}
|
|
32
|
+
async consume(handler) {
|
|
33
|
+
const { streams, pubsub, codec, siteId, lineId, nakDelayMs = 1_000 } = this.opts;
|
|
34
|
+
this.subs.push(await streams.consume(streams_1.LINE_STREAM.EVENTS, this.durableName, (m) => this.queue.run(() => handler({ topic: (0, protocol_1.toTopic)(m.subject), payload: m.payload, deliveries: m.deliveries })), { nakDelayMs }));
|
|
35
|
+
for (const subject of (0, streams_1.telemetrySubjects)(codec, siteId, lineId)) {
|
|
36
|
+
this.subs.push(pubsub.subscribe(subject, (m) => void this.queue.run(() => handler({ topic: (0, protocol_1.toTopic)(m.subject), payload: m.payload, deliveries: 1 }))));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async publish(topic, payload, opts = {}) {
|
|
40
|
+
await this.mqtt().publish(topic, payload, 1, opts.retain ?? false);
|
|
41
|
+
}
|
|
42
|
+
retained(filter) {
|
|
43
|
+
return this.mqtt().retained(filter);
|
|
44
|
+
}
|
|
45
|
+
async close() {
|
|
46
|
+
for (const sub of this.subs.splice(0))
|
|
47
|
+
await sub.close();
|
|
48
|
+
await this.queue.drained();
|
|
49
|
+
await this.session?.close(true);
|
|
50
|
+
}
|
|
51
|
+
mqtt() {
|
|
52
|
+
if (!this.session)
|
|
53
|
+
throw new Error('stream bus used before connect()');
|
|
54
|
+
return this.session;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.StreamMachineBus = StreamMachineBus;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
4
|
-
* a restart or a link outage replays instead of losing.
|
|
2
|
+
* The line's durable log, as specs for the messaging capability. Machine
|
|
3
|
+
* events and telemetry each land in a stream; the gateway reads events through
|
|
4
|
+
* a durable consumer, so a restart or a link outage replays instead of losing.
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
6
|
+
import type { ConsumerSpec, StreamSpec } from '../../../types/messaging';
|
|
7
7
|
import { type LineCodec } from '../protocol/codec';
|
|
8
8
|
export declare const LINE_STREAM: {
|
|
9
9
|
readonly EVENTS: "LINE_EVENTS";
|
|
@@ -11,18 +11,14 @@ export declare const LINE_STREAM: {
|
|
|
11
11
|
};
|
|
12
12
|
/** What the streams need from a codec: its root, channels and topic layout. */
|
|
13
13
|
export type StreamCodec = Pick<LineCodec, 'root' | 'channels' | 'topicFor'>;
|
|
14
|
-
/** An MQTT filter as a
|
|
14
|
+
/** An MQTT filter as a subject: levels become tokens, `+` becomes `*`, `#` becomes `>`. */
|
|
15
15
|
export declare function filterToSubject(filter: string): string;
|
|
16
|
-
/** The
|
|
17
|
-
export declare function
|
|
16
|
+
/** The line streams, for every line under the codec's root; a root alone means the generic layout. */
|
|
17
|
+
export declare function lineStreamSpecs(codec: StreamCodec | string): StreamSpec[];
|
|
18
18
|
/** The telemetry subjects for one line, read live rather than replayed. */
|
|
19
19
|
export declare function telemetrySubjects(codec: StreamCodec | string, siteId: string, lineId: string): string[];
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
* are reconciled at boot); after that its position survives restarts.
|
|
21
|
+
* One line's event consumer. It starts at new messages the first time (open
|
|
22
|
+
* rows are reconciled at boot); after that its position survives restarts.
|
|
23
23
|
*/
|
|
24
|
-
export declare function
|
|
25
|
-
export declare function lineEventConsumer(codec: StreamCodec | string, siteId: string, lineId: string, name: string): Partial<ConsumerConfig>;
|
|
26
|
-
/** Create or update each stream, then the consumer. Safe on every boot. */
|
|
27
|
-
export declare function ensureStreams(jsm: JetStreamManager, streams: Partial<StreamConfig>[]): Promise<void>;
|
|
28
|
-
export declare function ensureConsumer(jsm: JetStreamManager, stream: string, config: Partial<ConsumerConfig>): Promise<void>;
|
|
24
|
+
export declare function lineEventConsumerSpec(codec: StreamCodec | string, siteId: string, lineId: string, name: string): ConsumerSpec;
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* The
|
|
4
|
-
*
|
|
5
|
-
* a restart or a link outage replays instead of losing.
|
|
3
|
+
* The line's durable log, as specs for the messaging capability. Machine
|
|
4
|
+
* events and telemetry each land in a stream; the gateway reads events through
|
|
5
|
+
* a durable consumer, so a restart or a link outage replays instead of losing.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.LINE_STREAM = void 0;
|
|
9
9
|
exports.filterToSubject = filterToSubject;
|
|
10
|
-
exports.
|
|
10
|
+
exports.lineStreamSpecs = lineStreamSpecs;
|
|
11
11
|
exports.telemetrySubjects = telemetrySubjects;
|
|
12
|
-
exports.
|
|
13
|
-
exports.lineEventConsumer = lineEventConsumer;
|
|
14
|
-
exports.ensureStreams = ensureStreams;
|
|
15
|
-
exports.ensureConsumer = ensureConsumer;
|
|
16
|
-
const nats_1 = require("nats");
|
|
12
|
+
exports.lineEventConsumerSpec = lineEventConsumerSpec;
|
|
17
13
|
const codec_1 = require("../protocol/codec");
|
|
18
14
|
exports.LINE_STREAM = {
|
|
19
15
|
EVENTS: 'LINE_EVENTS',
|
|
@@ -22,71 +18,32 @@ exports.LINE_STREAM = {
|
|
|
22
18
|
const DAY_MS = 86_400_000;
|
|
23
19
|
const ACK_WAIT_MS = 30_000;
|
|
24
20
|
const GENERIC = (root) => ({ root, channels: codec_1.GENERIC_CHANNELS });
|
|
25
|
-
|
|
21
|
+
const codecOf = (codec) => (typeof codec === 'string' ? GENERIC(codec) : codec);
|
|
22
|
+
/** An MQTT filter as a subject: levels become tokens, `+` becomes `*`, `#` becomes `>`. */
|
|
26
23
|
function filterToSubject(filter) {
|
|
27
24
|
return filter.split('/').map((level) => (level === '+' ? '*' : level === '#' ? '>' : level)).join('.');
|
|
28
25
|
}
|
|
29
26
|
function subjectsFor(codec, channels, siteId, lineId) {
|
|
30
27
|
return channels.map((c) => filterToSubject((0, codec_1.topicOf)(codec, { siteId, lineId }, c)));
|
|
31
28
|
}
|
|
32
|
-
/** The
|
|
33
|
-
function
|
|
34
|
-
const c =
|
|
29
|
+
/** The line streams, for every line under the codec's root; a root alone means the generic layout. */
|
|
30
|
+
function lineStreamSpecs(codec) {
|
|
31
|
+
const c = codecOf(codec);
|
|
35
32
|
return [
|
|
36
|
-
{ name: exports.LINE_STREAM.EVENTS, subjects: subjectsFor(c, c.channels.events),
|
|
37
|
-
{ name: exports.LINE_STREAM.TELEMETRY, subjects: subjectsFor(c, c.channels.telemetry),
|
|
33
|
+
{ name: exports.LINE_STREAM.EVENTS, subjects: subjectsFor(c, c.channels.events), maxAgeMs: 30 * DAY_MS },
|
|
34
|
+
{ name: exports.LINE_STREAM.TELEMETRY, subjects: subjectsFor(c, c.channels.telemetry), maxAgeMs: 3 * DAY_MS },
|
|
38
35
|
];
|
|
39
36
|
}
|
|
40
37
|
/** The telemetry subjects for one line, read live rather than replayed. */
|
|
41
38
|
function telemetrySubjects(codec, siteId, lineId) {
|
|
42
|
-
const c =
|
|
39
|
+
const c = codecOf(codec);
|
|
43
40
|
return subjectsFor(c, c.channels.telemetry, siteId, lineId);
|
|
44
41
|
}
|
|
45
42
|
/**
|
|
46
|
-
*
|
|
47
|
-
* are reconciled at boot); after that its position survives restarts.
|
|
43
|
+
* One line's event consumer. It starts at new messages the first time (open
|
|
44
|
+
* rows are reconciled at boot); after that its position survives restarts.
|
|
48
45
|
*/
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
ack_policy: nats_1.AckPolicy.Explicit,
|
|
53
|
-
deliver_policy: nats_1.DeliverPolicy.New,
|
|
54
|
-
ack_wait: (0, nats_1.nanos)(ACK_WAIT_MS),
|
|
55
|
-
filter_subjects: filterSubjects,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function lineEventConsumer(codec, siteId, lineId, name) {
|
|
59
|
-
const c = typeof codec === 'string' ? GENERIC(codec) : codec;
|
|
60
|
-
return durableConsumer(name, subjectsFor(c, c.channels.events, siteId, lineId));
|
|
61
|
-
}
|
|
62
|
-
/** Create or update each stream, then the consumer. Safe on every boot. */
|
|
63
|
-
async function ensureStreams(jsm, streams) {
|
|
64
|
-
for (const config of streams) {
|
|
65
|
-
try {
|
|
66
|
-
await jsm.streams.info(config.name);
|
|
67
|
-
await jsm.streams.update(config.name, config);
|
|
68
|
-
}
|
|
69
|
-
catch (err) {
|
|
70
|
-
if (!/stream not found/i.test(err?.message ?? ''))
|
|
71
|
-
throw err;
|
|
72
|
-
await jsm.streams.add(config);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async function ensureConsumer(jsm, stream, config) {
|
|
77
|
-
let existing;
|
|
78
|
-
try {
|
|
79
|
-
existing = await jsm.consumers.info(stream, config.durable_name);
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
if (!/consumer not found/i.test(err?.message ?? ''))
|
|
83
|
-
throw err;
|
|
84
|
-
await jsm.consumers.add(stream, config);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
// The consumer keeps its position; only its subjects follow the codec's channels.
|
|
88
|
-
const want = [...(config.filter_subjects ?? [])].sort();
|
|
89
|
-
const have = [...(existing.config.filter_subjects ?? [])].sort();
|
|
90
|
-
if (want.join('|') !== have.join('|'))
|
|
91
|
-
await jsm.consumers.update(stream, config.durable_name, { filter_subjects: config.filter_subjects });
|
|
46
|
+
function lineEventConsumerSpec(codec, siteId, lineId, name) {
|
|
47
|
+
const c = codecOf(codec);
|
|
48
|
+
return { name, filterSubjects: subjectsFor(c, c.channels.events, siteId, lineId), ackWaitMs: ACK_WAIT_MS, startAt: 'new' };
|
|
92
49
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which messaging provider a deployment runs, from start() or the environment.
|
|
3
|
+
* Precedence: an explicit provider, then `messaging.nats` or `messaging.memory`,
|
|
4
|
+
* then LT_MESSAGING with LT_NATS_*, then `events.nats` (the same NATS, configured
|
|
5
|
+
* as events always have been). With none, Long Tail runs without messaging.
|
|
6
|
+
*/
|
|
7
|
+
import type { LTMessagingProvider } from '../../types/messaging';
|
|
8
|
+
import type { LTStartConfig } from '../../types/startup';
|
|
9
|
+
export declare const MESSAGING_KIND: {
|
|
10
|
+
readonly NATS: "nats";
|
|
11
|
+
readonly MEMORY: "memory";
|
|
12
|
+
};
|
|
13
|
+
/** What browsers need to reach the provider's events: a public WebSocket URL, a proxy target, a credential. */
|
|
14
|
+
export interface MessagingWs {
|
|
15
|
+
url?: string;
|
|
16
|
+
proxy?: string;
|
|
17
|
+
token?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ResolvedMessaging {
|
|
20
|
+
provider: LTMessagingProvider | null;
|
|
21
|
+
ws: MessagingWs;
|
|
22
|
+
}
|
|
23
|
+
type Env = Record<string, string | undefined>;
|
|
24
|
+
export declare function resolveMessaging(start: Pick<LTStartConfig, 'messaging' | 'events'>, env?: Env): ResolvedMessaging;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Which messaging provider a deployment runs, from start() or the environment.
|
|
4
|
+
* Precedence: an explicit provider, then `messaging.nats` or `messaging.memory`,
|
|
5
|
+
* then LT_MESSAGING with LT_NATS_*, then `events.nats` (the same NATS, configured
|
|
6
|
+
* as events always have been). With none, Long Tail runs without messaging.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MESSAGING_KIND = void 0;
|
|
10
|
+
exports.resolveMessaging = resolveMessaging;
|
|
11
|
+
const config_1 = require("../../modules/config");
|
|
12
|
+
const memory_1 = require("./memory");
|
|
13
|
+
const provider_1 = require("./nats/provider");
|
|
14
|
+
exports.MESSAGING_KIND = { NATS: 'nats', MEMORY: 'memory' };
|
|
15
|
+
const NONE = { provider: null, ws: {} };
|
|
16
|
+
function fromEnv(env) {
|
|
17
|
+
const kind = env.LT_MESSAGING;
|
|
18
|
+
if (!kind)
|
|
19
|
+
return null;
|
|
20
|
+
if (kind === exports.MESSAGING_KIND.MEMORY)
|
|
21
|
+
return { provider: new memory_1.MemoryMessagingProvider(), ws: {} };
|
|
22
|
+
if (kind !== exports.MESSAGING_KIND.NATS)
|
|
23
|
+
throw new Error(`LT_MESSAGING="${kind}" is not one of ${Object.values(exports.MESSAGING_KIND).join(', ')}`);
|
|
24
|
+
const url = env.LT_NATS_URL;
|
|
25
|
+
if (!url)
|
|
26
|
+
throw new Error('LT_MESSAGING=nats needs LT_NATS_URL');
|
|
27
|
+
const token = env.LT_NATS_TOKEN || undefined;
|
|
28
|
+
return {
|
|
29
|
+
provider: new provider_1.NatsMessagingProvider({ url, token, user: env.LT_NATS_USER || undefined, password: env.LT_NATS_PASSWORD || undefined, name: 'long-tail' }),
|
|
30
|
+
ws: { url: env.LT_NATS_WS_URL || undefined, proxy: env.LT_NATS_WS_PROXY || undefined, token },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function resolveMessaging(start, env = process.env) {
|
|
34
|
+
const m = start.messaging;
|
|
35
|
+
if (m?.provider)
|
|
36
|
+
return { provider: m.provider, ws: m.nats?.ws ?? {} };
|
|
37
|
+
if (m?.nats) {
|
|
38
|
+
const { ws, ...nats } = m.nats;
|
|
39
|
+
return { provider: new provider_1.NatsMessagingProvider({ name: 'long-tail', ...nats }), ws: { ...ws, token: ws?.token ?? nats.token } };
|
|
40
|
+
}
|
|
41
|
+
if (m?.memory)
|
|
42
|
+
return { provider: new memory_1.MemoryMessagingProvider(), ws: {} };
|
|
43
|
+
const configured = fromEnv(env);
|
|
44
|
+
if (configured)
|
|
45
|
+
return configured;
|
|
46
|
+
const legacy = start.events?.nats;
|
|
47
|
+
if (legacy) {
|
|
48
|
+
const token = legacy.token || config_1.config.NATS_TOKEN || undefined;
|
|
49
|
+
return {
|
|
50
|
+
provider: new provider_1.NatsMessagingProvider({ url: legacy.url || config_1.config.NATS_URL, token, name: 'long-tail' }),
|
|
51
|
+
ws: { url: legacy.wsUrl || config_1.config.NATS_WS_URL || undefined, proxy: legacy.wsProxy, token },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return NONE;
|
|
55
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Deliveries, log events and subjects for the messaging capability. */
|
|
2
|
+
/** What a consumer does with a stream message. */
|
|
3
|
+
export declare const DELIVERY: {
|
|
4
|
+
/** Handled; do not deliver again. */
|
|
5
|
+
readonly ACK: "ack";
|
|
6
|
+
/** Not yet actionable; deliver again after a delay. */
|
|
7
|
+
readonly NAK: "nak";
|
|
8
|
+
/** Never actionable; drop it. */
|
|
9
|
+
readonly TERM: "term";
|
|
10
|
+
};
|
|
11
|
+
/** Where a new consumer starts: only messages appended after it, or the whole retained history. */
|
|
12
|
+
export declare const START_AT: {
|
|
13
|
+
readonly NEW: "new";
|
|
14
|
+
readonly ALL: "all";
|
|
15
|
+
};
|
|
16
|
+
export declare const MESSAGING_LOG: {
|
|
17
|
+
readonly CONNECTED: "messaging.provider.connected";
|
|
18
|
+
readonly DISCONNECTED: "messaging.provider.disconnected";
|
|
19
|
+
readonly CONNECT_FAILED: "messaging.provider.connect_failed";
|
|
20
|
+
readonly STREAMS_UNAVAILABLE: "messaging.provider.streams_unavailable";
|
|
21
|
+
};
|
|
22
|
+
/** Subjects Long Tail itself publishes under. */
|
|
23
|
+
export declare const LT_SUBJECTS: {
|
|
24
|
+
readonly EVENTS: "lt.events";
|
|
25
|
+
readonly ESCALATIONS: "lt.events.system.escalation.>";
|
|
26
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Deliveries, log events and subjects for the messaging capability. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.LT_SUBJECTS = exports.MESSAGING_LOG = exports.START_AT = exports.DELIVERY = void 0;
|
|
5
|
+
/** What a consumer does with a stream message. */
|
|
6
|
+
exports.DELIVERY = {
|
|
7
|
+
/** Handled; do not deliver again. */
|
|
8
|
+
ACK: 'ack',
|
|
9
|
+
/** Not yet actionable; deliver again after a delay. */
|
|
10
|
+
NAK: 'nak',
|
|
11
|
+
/** Never actionable; drop it. */
|
|
12
|
+
TERM: 'term',
|
|
13
|
+
};
|
|
14
|
+
/** Where a new consumer starts: only messages appended after it, or the whole retained history. */
|
|
15
|
+
exports.START_AT = { NEW: 'new', ALL: 'all' };
|
|
16
|
+
exports.MESSAGING_LOG = {
|
|
17
|
+
CONNECTED: 'messaging.provider.connected',
|
|
18
|
+
DISCONNECTED: 'messaging.provider.disconnected',
|
|
19
|
+
CONNECT_FAILED: 'messaging.provider.connect_failed',
|
|
20
|
+
STREAMS_UNAVAILABLE: 'messaging.provider.streams_unavailable',
|
|
21
|
+
};
|
|
22
|
+
/** Subjects Long Tail itself publishes under. */
|
|
23
|
+
exports.LT_SUBJECTS = {
|
|
24
|
+
EVENTS: 'lt.events',
|
|
25
|
+
ESCALATIONS: 'lt.events.system.escalation.>',
|
|
26
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The messaging registry: one provider, registered at startup and shared by
|
|
3
|
+
* every part of Long Tail that needs pub/sub or durable streams (events, the
|
|
4
|
+
* machine surface). Asking for messaging that is not configured fails loudly,
|
|
5
|
+
* because a caller that gets nothing back would believe it was handled.
|
|
6
|
+
*/
|
|
7
|
+
import type { LTMessagingProvider, LTPubSub, LTStreams } from '../../types/messaging';
|
|
8
|
+
declare class LTMessagingRegistry {
|
|
9
|
+
private current;
|
|
10
|
+
/** Register the provider; replaces any registered before. Call before connect(). */
|
|
11
|
+
register(provider: LTMessagingProvider): void;
|
|
12
|
+
get hasProvider(): boolean;
|
|
13
|
+
get provider(): LTMessagingProvider;
|
|
14
|
+
get pubsub(): LTPubSub;
|
|
15
|
+
get streams(): LTStreams;
|
|
16
|
+
connect(): Promise<void>;
|
|
17
|
+
disconnect(): Promise<void>;
|
|
18
|
+
/** Forget the provider (tests). */
|
|
19
|
+
clear(): void;
|
|
20
|
+
}
|
|
21
|
+
export declare const messagingRegistry: LTMessagingRegistry;
|
|
22
|
+
export { MemoryMessagingProvider } from './memory';
|
|
23
|
+
export { subjectMatches, isSubject } from './subjects';
|
|
24
|
+
export { DELIVERY, START_AT, MESSAGING_LOG, LT_SUBJECTS } from './constants';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The messaging registry: one provider, registered at startup and shared by
|
|
4
|
+
* every part of Long Tail that needs pub/sub or durable streams (events, the
|
|
5
|
+
* machine surface). Asking for messaging that is not configured fails loudly,
|
|
6
|
+
* because a caller that gets nothing back would believe it was handled.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.LT_SUBJECTS = exports.MESSAGING_LOG = exports.START_AT = exports.DELIVERY = exports.isSubject = exports.subjectMatches = exports.MemoryMessagingProvider = exports.messagingRegistry = void 0;
|
|
10
|
+
const logger_1 = require("../logger");
|
|
11
|
+
const constants_1 = require("./constants");
|
|
12
|
+
const NOT_CONFIGURED = 'No messaging provider is configured: set LT_MESSAGING=nats with LT_NATS_URL, or pass start({ messaging })';
|
|
13
|
+
class LTMessagingRegistry {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.current = null;
|
|
16
|
+
}
|
|
17
|
+
/** Register the provider; replaces any registered before. Call before connect(). */
|
|
18
|
+
register(provider) {
|
|
19
|
+
this.current = provider;
|
|
20
|
+
}
|
|
21
|
+
get hasProvider() {
|
|
22
|
+
return this.current !== null;
|
|
23
|
+
}
|
|
24
|
+
get provider() {
|
|
25
|
+
if (!this.current)
|
|
26
|
+
throw new Error(NOT_CONFIGURED);
|
|
27
|
+
return this.current;
|
|
28
|
+
}
|
|
29
|
+
get pubsub() {
|
|
30
|
+
return this.provider.pubsub;
|
|
31
|
+
}
|
|
32
|
+
get streams() {
|
|
33
|
+
const streams = this.provider.streams;
|
|
34
|
+
if (!streams)
|
|
35
|
+
throw new Error(`Messaging provider ${this.provider.name} has no durable streams; enable JetStream on its NATS server`);
|
|
36
|
+
return streams;
|
|
37
|
+
}
|
|
38
|
+
async connect() {
|
|
39
|
+
if (!this.current || this.current.connected)
|
|
40
|
+
return;
|
|
41
|
+
await this.current.connect();
|
|
42
|
+
logger_1.loggerRegistry.info(`${constants_1.MESSAGING_LOG.CONNECTED} provider=${this.current.name} streams=${this.current.streams ? 'yes' : 'no'}`);
|
|
43
|
+
}
|
|
44
|
+
async disconnect() {
|
|
45
|
+
if (!this.current?.connected)
|
|
46
|
+
return;
|
|
47
|
+
await this.current.disconnect();
|
|
48
|
+
logger_1.loggerRegistry.info(`${constants_1.MESSAGING_LOG.DISCONNECTED} provider=${this.current.name}`);
|
|
49
|
+
}
|
|
50
|
+
/** Forget the provider (tests). */
|
|
51
|
+
clear() {
|
|
52
|
+
this.current = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.messagingRegistry = new LTMessagingRegistry();
|
|
56
|
+
var memory_1 = require("./memory");
|
|
57
|
+
Object.defineProperty(exports, "MemoryMessagingProvider", { enumerable: true, get: function () { return memory_1.MemoryMessagingProvider; } });
|
|
58
|
+
var subjects_1 = require("./subjects");
|
|
59
|
+
Object.defineProperty(exports, "subjectMatches", { enumerable: true, get: function () { return subjects_1.subjectMatches; } });
|
|
60
|
+
Object.defineProperty(exports, "isSubject", { enumerable: true, get: function () { return subjects_1.isSubject; } });
|
|
61
|
+
var constants_2 = require("./constants");
|
|
62
|
+
Object.defineProperty(exports, "DELIVERY", { enumerable: true, get: function () { return constants_2.DELIVERY; } });
|
|
63
|
+
Object.defineProperty(exports, "START_AT", { enumerable: true, get: function () { return constants_2.START_AT; } });
|
|
64
|
+
Object.defineProperty(exports, "MESSAGING_LOG", { enumerable: true, get: function () { return constants_2.MESSAGING_LOG; } });
|
|
65
|
+
Object.defineProperty(exports, "LT_SUBJECTS", { enumerable: true, get: function () { return constants_2.LT_SUBJECTS; } });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messaging in memory, for tests and single-process development. Pub/sub fans
|
|
3
|
+
* out to every matching subscriber. Streams keep each message until it ages
|
|
4
|
+
* out, and deliver to each consumer one at a time in order; a nak'd message
|
|
5
|
+
* comes back after its delay while later messages go on, as JetStream does.
|
|
6
|
+
*/
|
|
7
|
+
import type { LTMessagingProvider, LTPubSub, LTStreams } from '../../types/messaging';
|
|
8
|
+
export declare class MemoryMessagingProvider implements LTMessagingProvider {
|
|
9
|
+
readonly name = "memory";
|
|
10
|
+
readonly pubsub: LTPubSub;
|
|
11
|
+
readonly streams: LTStreams;
|
|
12
|
+
private live;
|
|
13
|
+
get connected(): boolean;
|
|
14
|
+
connect(): Promise<void>;
|
|
15
|
+
disconnect(): Promise<void>;
|
|
16
|
+
}
|