@hotmeshio/long-tail 0.25.4 → 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.
Files changed (173) hide show
  1. package/README.md +1 -0
  2. package/build/index.d.ts +5 -0
  3. package/build/index.js +36 -7
  4. package/build/lib/events/nats.d.ts +11 -0
  5. package/build/lib/events/nats.js +33 -2
  6. package/build/lib/machines/index.d.ts +6 -0
  7. package/build/lib/machines/index.js +22 -0
  8. package/build/lib/machines/line.d.ts +44 -0
  9. package/build/lib/machines/line.js +114 -0
  10. package/build/lib/machines/protocol/codec.d.ts +108 -0
  11. package/build/lib/machines/protocol/codec.js +80 -0
  12. package/build/lib/machines/protocol/constants.d.ts +120 -0
  13. package/build/lib/machines/protocol/constants.js +97 -0
  14. package/build/lib/machines/protocol/contract.d.ts +19 -0
  15. package/build/lib/machines/protocol/contract.js +141 -0
  16. package/build/lib/machines/protocol/envelope.d.ts +14 -0
  17. package/build/lib/machines/protocol/envelope.js +43 -0
  18. package/build/lib/machines/protocol/index.d.ts +5 -0
  19. package/build/lib/machines/protocol/index.js +21 -0
  20. package/build/lib/machines/protocol/topics.d.ts +33 -0
  21. package/build/lib/machines/protocol/topics.js +79 -0
  22. package/build/lib/machines/site/edge-account.d.ts +11 -0
  23. package/build/lib/machines/site/edge-account.js +24 -0
  24. package/build/lib/machines/site/mosquitto-conf.d.ts +27 -0
  25. package/build/lib/machines/site/mosquitto-conf.js +81 -0
  26. package/build/lib/machines/site/site-conf.d.ts +23 -0
  27. package/build/lib/machines/site/site-conf.js +62 -0
  28. package/build/lib/machines/transport/callback-feed.d.ts +18 -0
  29. package/build/lib/machines/transport/callback-feed.js +31 -0
  30. package/build/lib/machines/transport/delivery.d.ts +20 -0
  31. package/build/lib/machines/transport/delivery.js +37 -0
  32. package/build/lib/machines/transport/index.d.ts +13 -0
  33. package/build/lib/machines/transport/index.js +29 -0
  34. package/build/lib/machines/transport/memory-broker.d.ts +35 -0
  35. package/build/lib/machines/transport/memory-broker.js +145 -0
  36. package/build/lib/machines/transport/memory.d.ts +40 -0
  37. package/build/lib/machines/transport/memory.js +79 -0
  38. package/build/lib/machines/transport/mqtt-bus.d.ts +24 -0
  39. package/build/lib/machines/transport/mqtt-bus.js +43 -0
  40. package/build/lib/machines/transport/mqtt-link.d.ts +16 -0
  41. package/build/lib/machines/transport/mqtt-link.js +37 -0
  42. package/build/lib/machines/transport/mqtt-session.d.ts +34 -0
  43. package/build/lib/machines/transport/mqtt-session.js +127 -0
  44. package/build/lib/machines/transport/nats-bus.d.ts +36 -0
  45. package/build/lib/machines/transport/nats-bus.js +47 -0
  46. package/build/lib/machines/transport/nats-feed.d.ts +26 -0
  47. package/build/lib/machines/transport/nats-feed.js +49 -0
  48. package/build/lib/machines/transport/pubsub-feed.d.ts +17 -0
  49. package/build/lib/machines/transport/pubsub-feed.js +39 -0
  50. package/build/lib/machines/transport/serial.d.ts +21 -0
  51. package/build/lib/machines/transport/serial.js +54 -0
  52. package/build/lib/machines/transport/stream-bus.d.ts +40 -0
  53. package/build/lib/machines/transport/stream-bus.js +57 -0
  54. package/build/lib/machines/transport/streams.d.ts +24 -0
  55. package/build/lib/machines/transport/streams.js +49 -0
  56. package/build/lib/messaging/config.d.ts +25 -0
  57. package/build/lib/messaging/config.js +55 -0
  58. package/build/lib/messaging/constants.d.ts +26 -0
  59. package/build/lib/messaging/constants.js +26 -0
  60. package/build/lib/messaging/index.d.ts +24 -0
  61. package/build/lib/messaging/index.js +65 -0
  62. package/build/lib/messaging/memory.d.ts +16 -0
  63. package/build/lib/messaging/memory.js +147 -0
  64. package/build/lib/messaging/nats/permissions.d.ts +13 -0
  65. package/build/lib/messaging/nats/permissions.js +22 -0
  66. package/build/lib/messaging/nats/provider.d.ts +30 -0
  67. package/build/lib/messaging/nats/provider.js +77 -0
  68. package/build/lib/messaging/nats/render-conf.d.ts +68 -0
  69. package/build/lib/messaging/nats/render-conf.js +58 -0
  70. package/build/lib/messaging/nats/streams.d.ts +26 -0
  71. package/build/lib/messaging/nats/streams.js +97 -0
  72. package/build/lib/messaging/subjects.d.ts +8 -0
  73. package/build/lib/messaging/subjects.js +25 -0
  74. package/build/scripts/bambu-boat.d.ts +17 -0
  75. package/build/scripts/bambu-boat.js +134 -0
  76. package/build/scripts/bambu-print.d.ts +18 -0
  77. package/build/scripts/bambu-print.js +114 -0
  78. package/build/scripts/machine-line.d.ts +33 -0
  79. package/build/scripts/machine-line.js +301 -0
  80. package/build/services/machines/commands.d.ts +29 -0
  81. package/build/services/machines/commands.js +37 -0
  82. package/build/services/machines/config.d.ts +49 -0
  83. package/build/services/machines/config.js +94 -0
  84. package/build/services/machines/contracts.d.ts +210 -0
  85. package/build/services/machines/contracts.js +124 -0
  86. package/build/services/machines/forms.d.ts +55 -0
  87. package/build/services/machines/forms.js +37 -0
  88. package/build/services/machines/gateway/actions.d.ts +111 -0
  89. package/build/services/machines/gateway/actions.js +8 -0
  90. package/build/services/machines/gateway/address-book.d.ts +16 -0
  91. package/build/services/machines/gateway/address-book.js +24 -0
  92. package/build/services/machines/gateway/admission.d.ts +35 -0
  93. package/build/services/machines/gateway/admission.js +66 -0
  94. package/build/services/machines/gateway/api-escalations.d.ts +23 -0
  95. package/build/services/machines/gateway/api-escalations.js +101 -0
  96. package/build/services/machines/gateway/dispatcher.d.ts +76 -0
  97. package/build/services/machines/gateway/dispatcher.js +197 -0
  98. package/build/services/machines/gateway/escalations.d.ts +51 -0
  99. package/build/services/machines/gateway/escalations.js +40 -0
  100. package/build/services/machines/gateway/executor.d.ts +60 -0
  101. package/build/services/machines/gateway/executor.js +221 -0
  102. package/build/services/machines/gateway/gateway.d.ts +88 -0
  103. package/build/services/machines/gateway/gateway.js +270 -0
  104. package/build/services/machines/gateway/index.d.ts +10 -0
  105. package/build/services/machines/gateway/index.js +26 -0
  106. package/build/services/machines/gateway/jobs.d.ts +16 -0
  107. package/build/services/machines/gateway/jobs.js +24 -0
  108. package/build/services/machines/gateway/lifecycle.d.ts +21 -0
  109. package/build/services/machines/gateway/lifecycle.js +89 -0
  110. package/build/services/machines/gateway/log.d.ts +41 -0
  111. package/build/services/machines/gateway/log.js +42 -0
  112. package/build/services/machines/gateway/lt-http.d.ts +51 -0
  113. package/build/services/machines/gateway/lt-http.js +120 -0
  114. package/build/services/machines/gateway/pacing.d.ts +43 -0
  115. package/build/services/machines/gateway/pacing.js +158 -0
  116. package/build/services/machines/gateway/plan-escalation.d.ts +19 -0
  117. package/build/services/machines/gateway/plan-escalation.js +210 -0
  118. package/build/services/machines/gateway/plan-machine.d.ts +17 -0
  119. package/build/services/machines/gateway/plan-machine.js +283 -0
  120. package/build/services/machines/gateway/plan-shared.d.ts +18 -0
  121. package/build/services/machines/gateway/plan-shared.js +36 -0
  122. package/build/services/machines/gateway/provision.d.ts +22 -0
  123. package/build/services/machines/gateway/provision.js +79 -0
  124. package/build/services/machines/gateway/receipts.d.ts +33 -0
  125. package/build/services/machines/gateway/receipts.js +65 -0
  126. package/build/services/machines/gateway/reconcile.d.ts +35 -0
  127. package/build/services/machines/gateway/reconcile.js +99 -0
  128. package/build/services/machines/gateway/registration.d.ts +38 -0
  129. package/build/services/machines/gateway/registration.js +121 -0
  130. package/build/services/machines/gateway/reply-waiter.d.ts +24 -0
  131. package/build/services/machines/gateway/reply-waiter.js +65 -0
  132. package/build/services/machines/gateway/state.d.ts +145 -0
  133. package/build/services/machines/gateway/state.js +241 -0
  134. package/build/services/machines/gateway/trace.d.ts +16 -0
  135. package/build/services/machines/gateway/trace.js +43 -0
  136. package/build/services/machines/identities.d.ts +25 -0
  137. package/build/services/machines/identities.js +77 -0
  138. package/build/services/machines/index.d.ts +11 -0
  139. package/build/services/machines/index.js +27 -0
  140. package/build/services/machines/leadership.d.ts +35 -0
  141. package/build/services/machines/leadership.js +100 -0
  142. package/build/services/machines/roles.d.ts +13 -0
  143. package/build/services/machines/roles.js +62 -0
  144. package/build/services/machines/rows.d.ts +30 -0
  145. package/build/services/machines/rows.js +71 -0
  146. package/build/services/machines/select.d.ts +25 -0
  147. package/build/services/machines/select.js +76 -0
  148. package/build/services/machines/surface.d.ts +33 -0
  149. package/build/services/machines/surface.js +104 -0
  150. package/build/services/machines/testing/fake-long-tail.d.ts +79 -0
  151. package/build/services/machines/testing/fake-long-tail.js +158 -0
  152. package/build/services/machines/testing/index.d.ts +3 -0
  153. package/build/services/machines/testing/index.js +19 -0
  154. package/build/services/machines/testing/recording-log.d.ts +14 -0
  155. package/build/services/machines/testing/recording-log.js +22 -0
  156. package/build/services/machines/testing/wait.d.ts +3 -0
  157. package/build/services/machines/testing/wait.js +17 -0
  158. package/build/services/mcp/db-server/schemas.d.ts +2 -2
  159. package/build/start/adapters.js +11 -2
  160. package/build/start/index.js +20 -1
  161. package/build/system/mcp-servers/admin/schemas.d.ts +221 -221
  162. package/build/system/mcp-servers/db-query/schemas.d.ts +2 -2
  163. package/build/system/mcp-servers/human-queue-schemas.d.ts +6 -6
  164. package/build/tsconfig.tsbuildinfo +1 -1
  165. package/build/types/index.d.ts +3 -1
  166. package/build/types/machines.d.ts +167 -0
  167. package/build/types/machines.js +7 -0
  168. package/build/types/messaging.d.ts +72 -0
  169. package/build/types/messaging.js +11 -0
  170. package/build/types/startup.d.ts +49 -1
  171. package/docs/events.md +50 -4
  172. package/docs/machines.md +322 -0
  173. package/package.json +13 -1
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /**
3
+ * The gateway over plain MQTT, for any broker. The persistent session keeps
4
+ * QoS 1 messages while the gateway is away; there is no stream to replay
5
+ * beyond that, and a message that is not yet actionable is retried in this
6
+ * process.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.MqttMachineBus = void 0;
10
+ const mqtt_session_1 = require("./mqtt-session");
11
+ const serial_1 = require("./serial");
12
+ class MqttMachineBus {
13
+ constructor(endpoint, clientId, filter, nakDelayMs = 1_000) {
14
+ this.endpoint = endpoint;
15
+ this.clientId = clientId;
16
+ this.filter = filter;
17
+ this.redelivery = new serial_1.LocalRedelivery(new serial_1.SerialQueue(), nakDelayMs);
18
+ }
19
+ async connect() {
20
+ this.session = await mqtt_session_1.MqttSession.open(this.endpoint, this.clientId);
21
+ }
22
+ async consume(handler) {
23
+ await this.connected().subscribe(this.filter, (msg) => {
24
+ void this.redelivery.deliver((deliveries) => handler({ ...msg, deliveries }));
25
+ });
26
+ }
27
+ async publish(topic, payload, opts = {}) {
28
+ await this.connected().publish(topic, payload, 1, opts.retain ?? false);
29
+ }
30
+ retained(filter) {
31
+ return this.connected().retained(filter);
32
+ }
33
+ async close() {
34
+ await this.redelivery.stop();
35
+ await this.session?.close(true);
36
+ }
37
+ connected() {
38
+ if (!this.session)
39
+ throw new Error('MQTT bus used before connect()');
40
+ return this.session;
41
+ }
42
+ }
43
+ exports.MqttMachineBus = MqttMachineBus;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The machine side over MQTT: one persistent session per machine, logged in
3
+ * with its own credentials, with a will that marks it offline.
4
+ */
5
+ import type { MqttEndpoint } from '../../../services/machines/config';
6
+ import type { InboundMessage, LinkConnectOptions, MachineLink, PublishOptions } from './delivery';
7
+ export declare class MqttMachineLink implements MachineLink {
8
+ private readonly endpoint;
9
+ private session?;
10
+ constructor(endpoint: MqttEndpoint);
11
+ get connected(): boolean;
12
+ connect(opts: LinkConnectOptions): Promise<void>;
13
+ publish(topic: string, payload: string, opts: PublishOptions): Promise<void>;
14
+ subscribe(filter: string, handler: (msg: InboundMessage) => void): Promise<void>;
15
+ disconnect(graceful: boolean): Promise<void>;
16
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /**
3
+ * The machine side over MQTT: one persistent session per machine, logged in
4
+ * with its own credentials, with a will that marks it offline.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.MqttMachineLink = void 0;
8
+ const mqtt_session_1 = require("./mqtt-session");
9
+ class MqttMachineLink {
10
+ constructor(endpoint) {
11
+ this.endpoint = endpoint;
12
+ }
13
+ get connected() {
14
+ return !!this.session?.connected;
15
+ }
16
+ async connect(opts) {
17
+ if (this.session)
18
+ await this.session.close(false);
19
+ this.session = await mqtt_session_1.MqttSession.open(this.endpoint, opts.clientId, { will: opts.will, onRestored: opts.onRestored });
20
+ }
21
+ async publish(topic, payload, opts) {
22
+ if (!this.session)
23
+ throw new Error(`publish before connect: ${topic}`);
24
+ await this.session.publish(topic, payload, opts.qos, opts.retain ?? false);
25
+ }
26
+ async subscribe(filter, handler) {
27
+ if (!this.session)
28
+ throw new Error('subscribe before connect');
29
+ await this.session.subscribe(filter, handler);
30
+ }
31
+ async disconnect(graceful) {
32
+ const session = this.session;
33
+ this.session = undefined;
34
+ await session?.close(graceful);
35
+ }
36
+ }
37
+ exports.MqttMachineLink = MqttMachineLink;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * One MQTT client with a persistent session (clean start off), shared by the
3
+ * machine link and the controller buses. A session resumed after a restart
4
+ * delivers queued QoS 1 messages right after the connect acknowledgement,
5
+ * before anyone has subscribed again; those are held in the process until a
6
+ * handler for their topic registers, so a queued command is not dropped.
7
+ */
8
+ import type { IClientOptions, MqttClient } from 'mqtt';
9
+ import type { MqttEndpoint } from '../../../services/machines/config';
10
+ import { type InboundMessage, type LinkWill } from './delivery';
11
+ type Handler = (msg: InboundMessage) => void;
12
+ export interface SessionOptions {
13
+ will?: LinkWill;
14
+ onRestored?: () => void;
15
+ }
16
+ /** Client options for one identity; `{clientId}` in a path or username picks that identity's credentials. */
17
+ export declare function mqttOptions(endpoint: MqttEndpoint, clientId: string, will?: LinkWill): IClientOptions;
18
+ export declare class MqttSession {
19
+ readonly client: MqttClient;
20
+ private handlers;
21
+ private held;
22
+ private constructor();
23
+ /** Connect and wait for the broker's acknowledgement; a refusal rejects. */
24
+ static open(endpoint: MqttEndpoint, clientId: string, opts?: SessionOptions): Promise<MqttSession>;
25
+ get connected(): boolean;
26
+ subscribe(filter: string, handler: Handler): Promise<void>;
27
+ publish(topic: string, payload: string, qos: 0 | 1, retain?: boolean): Promise<void>;
28
+ /** Retained messages under `filter`, as the broker sends them on subscribe. */
29
+ retained(filter: string, settleMs?: number): Promise<InboundMessage[]>;
30
+ /** Graceful sends DISCONNECT, so the broker drops the will; otherwise the socket just closes. */
31
+ close(graceful: boolean): Promise<void>;
32
+ private route;
33
+ }
34
+ export {};
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ /**
3
+ * One MQTT client with a persistent session (clean start off), shared by the
4
+ * machine link and the controller buses. A session resumed after a restart
5
+ * delivers queued QoS 1 messages right after the connect acknowledgement,
6
+ * before anyone has subscribed again; those are held in the process until a
7
+ * handler for their topic registers, so a queued command is not dropped.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.MqttSession = void 0;
11
+ exports.mqttOptions = mqttOptions;
12
+ const fs_1 = require("fs");
13
+ const delivery_1 = require("./delivery");
14
+ const HELD_LIMIT = 1_000;
15
+ const MQTT_MISSING = 'the MQTT transport needs the "mqtt" package (npm install mqtt)';
16
+ /** mqtt is an optional peer dependency, loaded only when an MQTT transport is used. */
17
+ function loadMqtt() {
18
+ try {
19
+ return require('mqtt');
20
+ }
21
+ catch (err) {
22
+ const error = new Error(MQTT_MISSING);
23
+ error.cause = err;
24
+ throw error;
25
+ }
26
+ }
27
+ const SESSION_EXPIRY_SEC = 86_400;
28
+ const RECONNECT_MS = 1_000;
29
+ const CONNECT_TIMEOUT_MS = 10_000;
30
+ /** Client options for one identity; `{clientId}` in a path or username picks that identity's credentials. */
31
+ function mqttOptions(endpoint, clientId, will) {
32
+ const fill = (value) => value?.split('{clientId}').join(clientId);
33
+ const file = (path) => (path ? (0, fs_1.readFileSync)(fill(path)) : undefined);
34
+ return {
35
+ clientId,
36
+ protocolVersion: endpoint.protocolVersion,
37
+ clean: false,
38
+ ...(endpoint.protocolVersion === 5 ? { properties: { sessionExpiryInterval: SESSION_EXPIRY_SEC } } : {}),
39
+ ca: file(endpoint.caFile),
40
+ cert: file(endpoint.certFile),
41
+ key: file(endpoint.keyFile),
42
+ username: fill(endpoint.username),
43
+ password: endpoint.password,
44
+ reconnectPeriod: RECONNECT_MS,
45
+ connectTimeout: CONNECT_TIMEOUT_MS,
46
+ will: will ? { topic: will.topic, payload: Buffer.from(will.payload), qos: 1, retain: will.retain } : undefined,
47
+ };
48
+ }
49
+ class MqttSession {
50
+ constructor(client) {
51
+ this.client = client;
52
+ this.handlers = [];
53
+ this.held = [];
54
+ client.on('message', (topic, payload, packet) => {
55
+ this.route({ topic, payload: payload.toString(), retained: packet.retain, deliveries: packet.dup ? 2 : 1 });
56
+ });
57
+ }
58
+ /** Connect and wait for the broker's acknowledgement; a refusal rejects. */
59
+ static open(endpoint, clientId, opts = {}) {
60
+ const client = loadMqtt().connect(endpoint.url, mqttOptions(endpoint, clientId, opts.will));
61
+ const session = new MqttSession(client);
62
+ return new Promise((resolve, reject) => {
63
+ const fail = (err) => {
64
+ client.end(true);
65
+ reject(new Error(`MQTT ${clientId} could not connect to ${endpoint.url}: ${err.message}`));
66
+ };
67
+ client.once('error', fail);
68
+ client.once('connect', () => {
69
+ client.off('error', fail);
70
+ client.on('error', () => undefined);
71
+ client.on('connect', () => opts.onRestored?.());
72
+ resolve(session);
73
+ });
74
+ });
75
+ }
76
+ get connected() {
77
+ return this.client.connected;
78
+ }
79
+ async subscribe(filter, handler) {
80
+ this.handlers.push({ filter, handler });
81
+ const ready = this.held.filter((m) => (0, delivery_1.topicMatches)(filter, m.topic));
82
+ this.held = this.held.filter((m) => !(0, delivery_1.topicMatches)(filter, m.topic));
83
+ await this.client.subscribeAsync(filter, { qos: 1 });
84
+ for (const m of ready)
85
+ handler(m);
86
+ }
87
+ async publish(topic, payload, qos, retain = false) {
88
+ await this.client.publishAsync(topic, payload, { qos, retain });
89
+ }
90
+ /** Retained messages under `filter`, as the broker sends them on subscribe. */
91
+ async retained(filter, settleMs = 250) {
92
+ const seen = [];
93
+ const collect = (topic, payload, packet) => {
94
+ if (packet.retain && (0, delivery_1.topicMatches)(filter, topic))
95
+ seen.push({ topic, payload: payload.toString(), retained: true, deliveries: 1 });
96
+ };
97
+ this.client.on('message', collect);
98
+ try {
99
+ await this.client.subscribeAsync(filter, { qos: 1 });
100
+ await new Promise((r) => setTimeout(r, settleMs));
101
+ await this.client.unsubscribeAsync(filter);
102
+ }
103
+ finally {
104
+ this.client.off('message', collect);
105
+ }
106
+ return seen;
107
+ }
108
+ /** Graceful sends DISCONNECT, so the broker drops the will; otherwise the socket just closes. */
109
+ async close(graceful) {
110
+ if (graceful)
111
+ await this.client.endAsync(false);
112
+ else
113
+ this.client.end(true);
114
+ }
115
+ route(msg) {
116
+ const matching = this.handlers.filter((h) => (0, delivery_1.topicMatches)(h.filter, msg.topic));
117
+ if (matching.length) {
118
+ for (const h of matching)
119
+ h.handler(msg);
120
+ return;
121
+ }
122
+ this.held.push(msg);
123
+ if (this.held.length > HELD_LIMIT)
124
+ this.held.shift();
125
+ }
126
+ }
127
+ exports.MqttSession = MqttSession;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The gateway on a NATS server of the site's own (MACHINE_SITE_NATS_*): the
3
+ * stream bus on a NATS messaging provider this bus connects and closes itself.
4
+ * With Long Tail's messaging configured, the stream bus runs on that instead.
5
+ */
6
+ import type { MqttEndpoint } from '../../../services/machines/config';
7
+ import type { StreamCodec } from './streams';
8
+ import type { Disposition, InboundMessage, MachineBus } from './delivery';
9
+ export interface NatsBusOptions {
10
+ natsUrl: string;
11
+ user?: string;
12
+ password?: string;
13
+ mqtt: MqttEndpoint;
14
+ /** The gateway's broker identity; also its MQTT client id. */
15
+ controllerId: string;
16
+ siteId: string;
17
+ lineId: string;
18
+ /** The codec's topics: which subjects are durable events and which are live telemetry. */
19
+ codec: StreamCodec;
20
+ nakDelayMs?: number;
21
+ }
22
+ export declare class NatsMachineBus implements MachineBus {
23
+ private readonly opts;
24
+ private readonly provider;
25
+ private bus?;
26
+ constructor(opts: NatsBusOptions);
27
+ get durableName(): string;
28
+ connect(): Promise<void>;
29
+ consume(handler: (msg: InboundMessage) => Promise<Disposition>): Promise<void>;
30
+ publish(topic: string, payload: string, opts?: {
31
+ retain?: boolean;
32
+ }): Promise<void>;
33
+ retained(filter: string): Promise<InboundMessage[]>;
34
+ close(): Promise<void>;
35
+ private stream;
36
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /**
3
+ * The gateway on a NATS server of the site's own (MACHINE_SITE_NATS_*): the
4
+ * stream bus on a NATS messaging provider this bus connects and closes itself.
5
+ * With Long Tail's messaging configured, the stream bus runs on that instead.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.NatsMachineBus = void 0;
9
+ const provider_1 = require("../../messaging/nats/provider");
10
+ const stream_bus_1 = require("./stream-bus");
11
+ class NatsMachineBus {
12
+ constructor(opts) {
13
+ this.opts = opts;
14
+ this.provider = new provider_1.NatsMessagingProvider({ url: opts.natsUrl, user: opts.user, password: opts.password, name: opts.controllerId });
15
+ }
16
+ get durableName() {
17
+ return `${this.opts.controllerId}-events`;
18
+ }
19
+ async connect() {
20
+ await this.provider.connect();
21
+ const streams = this.provider.streams;
22
+ if (!streams)
23
+ throw new Error(`the site NATS at ${this.opts.natsUrl} has no JetStream for the gateway`);
24
+ const { natsUrl: _url, user: _user, password: _password, ...rest } = this.opts;
25
+ this.bus = new stream_bus_1.StreamMachineBus({ ...rest, streams, pubsub: this.provider.pubsub });
26
+ await this.bus.connect();
27
+ }
28
+ consume(handler) {
29
+ return this.stream().consume(handler);
30
+ }
31
+ publish(topic, payload, opts) {
32
+ return this.stream().publish(topic, payload, opts);
33
+ }
34
+ retained(filter) {
35
+ return this.stream().retained(filter);
36
+ }
37
+ async close() {
38
+ await this.bus?.close();
39
+ await this.provider.disconnect();
40
+ }
41
+ stream() {
42
+ if (!this.bus)
43
+ throw new Error('NATS bus used before connect()');
44
+ return this.bus;
45
+ }
46
+ }
47
+ exports.NatsMachineBus = NatsMachineBus;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Long Tail's escalation events for a gateway running outside Long Tail: a
3
+ * subscription to Long Tail's NATS. Nothing is replayed; a not-yet-actionable
4
+ * event is retried in this process, and the gateway reconciles open rows
5
+ * when it starts leading a line.
6
+ */
7
+ import { type Disposition, type EscalationEvent, type EscalationFeed } from './delivery';
8
+ type Handler = (event: EscalationEvent) => Promise<Disposition>;
9
+ /** Long Tail publishes every LTEvent as `lt.events.<type>`. */
10
+ export declare const LT_ESCALATION_SUBJECTS = "lt.events.system.escalation.>";
11
+ export interface CoreFeedOptions {
12
+ natsUrl: string;
13
+ token?: string;
14
+ nakDelayMs?: number;
15
+ }
16
+ export declare class CoreEscalationFeed implements EscalationFeed {
17
+ private readonly opts;
18
+ private nc?;
19
+ private sub?;
20
+ private readonly redelivery;
21
+ constructor(opts: CoreFeedOptions);
22
+ connect(): Promise<void>;
23
+ consume(handler: Handler): Promise<void>;
24
+ close(): Promise<void>;
25
+ }
26
+ export {};
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * Long Tail's escalation events for a gateway running outside Long Tail: a
4
+ * subscription to Long Tail's NATS. Nothing is replayed; a not-yet-actionable
5
+ * event is retried in this process, and the gateway reconciles open rows
6
+ * when it starts leading a line.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.CoreEscalationFeed = exports.LT_ESCALATION_SUBJECTS = void 0;
10
+ const nats_1 = require("nats");
11
+ const streams_1 = require("../../messaging/nats/streams");
12
+ const serial_1 = require("./serial");
13
+ const delivery_1 = require("./delivery");
14
+ /** Long Tail publishes every LTEvent as `lt.events.<type>`. */
15
+ exports.LT_ESCALATION_SUBJECTS = 'lt.events.system.escalation.>';
16
+ function decode(payload) {
17
+ try {
18
+ return (0, delivery_1.toEscalationEvent)(JSON.parse(payload));
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ class CoreEscalationFeed {
25
+ constructor(opts) {
26
+ this.opts = opts;
27
+ this.redelivery = new serial_1.LocalRedelivery(new serial_1.SerialQueue(), opts.nakDelayMs ?? 1_000);
28
+ }
29
+ async connect() {
30
+ this.nc = await (0, nats_1.connect)({ servers: this.opts.natsUrl, token: this.opts.token, name: 'line-gateway-escalations' });
31
+ }
32
+ async consume(handler) {
33
+ if (!this.nc)
34
+ throw new Error('escalation feed used before connect()');
35
+ this.sub = this.nc.subscribe(exports.LT_ESCALATION_SUBJECTS, {
36
+ callback: (_err, m) => {
37
+ const event = decode((0, streams_1.payloadOf)(m));
38
+ if (event)
39
+ void this.redelivery.deliver(() => handler(event));
40
+ },
41
+ });
42
+ }
43
+ async close() {
44
+ this.sub?.unsubscribe();
45
+ await this.redelivery.stop();
46
+ await this.nc?.drain();
47
+ }
48
+ }
49
+ exports.CoreEscalationFeed = CoreEscalationFeed;
@@ -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,21 @@
1
+ import { type Disposition } from './delivery';
2
+ /** Runs async work one item at a time, in arrival order. */
3
+ export declare class SerialQueue {
4
+ private tail;
5
+ run<T>(work: () => Promise<T>): Promise<T>;
6
+ /** Resolves once everything queued so far has run. */
7
+ drained(): Promise<void>;
8
+ }
9
+ /**
10
+ * Delivery through a SerialQueue that tries a not-yet-actionable item again
11
+ * after a delay, for transports without a broker-side redelivery.
12
+ */
13
+ export declare class LocalRedelivery {
14
+ readonly queue: SerialQueue;
15
+ private readonly delayMs;
16
+ private readonly timers;
17
+ private stopped;
18
+ constructor(queue: SerialQueue, delayMs: number);
19
+ deliver(attempt: (deliveries: number) => Promise<Disposition>, deliveries?: number): Promise<void>;
20
+ stop(): Promise<void>;
21
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalRedelivery = exports.SerialQueue = void 0;
4
+ const delivery_1 = require("./delivery");
5
+ /** Runs async work one item at a time, in arrival order. */
6
+ class SerialQueue {
7
+ constructor() {
8
+ this.tail = Promise.resolve();
9
+ }
10
+ run(work) {
11
+ const next = this.tail.then(work, work);
12
+ this.tail = next.catch(() => undefined);
13
+ return next;
14
+ }
15
+ /** Resolves once everything queued so far has run. */
16
+ drained() {
17
+ return this.tail.then(() => undefined);
18
+ }
19
+ }
20
+ exports.SerialQueue = SerialQueue;
21
+ /**
22
+ * Delivery through a SerialQueue that tries a not-yet-actionable item again
23
+ * after a delay, for transports without a broker-side redelivery.
24
+ */
25
+ class LocalRedelivery {
26
+ constructor(queue, delayMs) {
27
+ this.queue = queue;
28
+ this.delayMs = delayMs;
29
+ this.timers = new Set();
30
+ this.stopped = false;
31
+ }
32
+ deliver(attempt, deliveries = 1) {
33
+ return this.queue.run(async () => {
34
+ if (this.stopped)
35
+ return;
36
+ const disposition = await attempt(deliveries).catch(() => delivery_1.DISPOSITION.NAK);
37
+ if (disposition !== delivery_1.DISPOSITION.NAK)
38
+ return;
39
+ const timer = setTimeout(() => {
40
+ this.timers.delete(timer);
41
+ void this.deliver(attempt, deliveries + 1);
42
+ }, this.delayMs);
43
+ this.timers.add(timer);
44
+ });
45
+ }
46
+ async stop() {
47
+ this.stopped = true;
48
+ for (const t of this.timers)
49
+ clearTimeout(t);
50
+ this.timers.clear();
51
+ await this.queue.drained();
52
+ }
53
+ }
54
+ exports.LocalRedelivery = LocalRedelivery;
@@ -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;