@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.
- package/README.md +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +36 -7
- package/build/lib/events/nats.d.ts +11 -0
- package/build/lib/events/nats.js +33 -2
- package/build/lib/machines/index.d.ts +6 -0
- package/build/lib/machines/index.js +22 -0
- package/build/lib/machines/line.d.ts +44 -0
- package/build/lib/machines/line.js +114 -0
- package/build/lib/machines/protocol/codec.d.ts +108 -0
- package/build/lib/machines/protocol/codec.js +80 -0
- package/build/lib/machines/protocol/constants.d.ts +120 -0
- package/build/lib/machines/protocol/constants.js +97 -0
- package/build/lib/machines/protocol/contract.d.ts +19 -0
- package/build/lib/machines/protocol/contract.js +141 -0
- package/build/lib/machines/protocol/envelope.d.ts +14 -0
- package/build/lib/machines/protocol/envelope.js +43 -0
- package/build/lib/machines/protocol/index.d.ts +5 -0
- package/build/lib/machines/protocol/index.js +21 -0
- package/build/lib/machines/protocol/topics.d.ts +33 -0
- package/build/lib/machines/protocol/topics.js +79 -0
- 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/mosquitto-conf.d.ts +27 -0
- package/build/lib/machines/site/mosquitto-conf.js +81 -0
- package/build/lib/machines/site/site-conf.d.ts +23 -0
- package/build/lib/machines/site/site-conf.js +62 -0
- package/build/lib/machines/transport/callback-feed.d.ts +18 -0
- package/build/lib/machines/transport/callback-feed.js +31 -0
- package/build/lib/machines/transport/delivery.d.ts +20 -0
- package/build/lib/machines/transport/delivery.js +37 -0
- package/build/lib/machines/transport/index.d.ts +13 -0
- package/build/lib/machines/transport/index.js +29 -0
- package/build/lib/machines/transport/memory-broker.d.ts +35 -0
- package/build/lib/machines/transport/memory-broker.js +145 -0
- package/build/lib/machines/transport/memory.d.ts +40 -0
- package/build/lib/machines/transport/memory.js +79 -0
- package/build/lib/machines/transport/mqtt-bus.d.ts +24 -0
- package/build/lib/machines/transport/mqtt-bus.js +43 -0
- package/build/lib/machines/transport/mqtt-link.d.ts +16 -0
- package/build/lib/machines/transport/mqtt-link.js +37 -0
- package/build/lib/machines/transport/mqtt-session.d.ts +34 -0
- package/build/lib/machines/transport/mqtt-session.js +127 -0
- package/build/lib/machines/transport/nats-bus.d.ts +36 -0
- package/build/lib/machines/transport/nats-bus.js +47 -0
- package/build/lib/machines/transport/nats-feed.d.ts +26 -0
- package/build/lib/machines/transport/nats-feed.js +49 -0
- 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/serial.d.ts +21 -0
- package/build/lib/machines/transport/serial.js +54 -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 +24 -0
- package/build/lib/machines/transport/streams.js +49 -0
- 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/scripts/machine-line.d.ts +33 -0
- package/build/scripts/machine-line.js +301 -0
- package/build/services/machines/commands.d.ts +29 -0
- package/build/services/machines/commands.js +37 -0
- package/build/services/machines/config.d.ts +49 -0
- package/build/services/machines/config.js +94 -0
- package/build/services/machines/contracts.d.ts +210 -0
- package/build/services/machines/contracts.js +124 -0
- package/build/services/machines/forms.d.ts +55 -0
- package/build/services/machines/forms.js +37 -0
- package/build/services/machines/gateway/actions.d.ts +111 -0
- package/build/services/machines/gateway/actions.js +8 -0
- package/build/services/machines/gateway/address-book.d.ts +16 -0
- package/build/services/machines/gateway/address-book.js +24 -0
- package/build/services/machines/gateway/admission.d.ts +35 -0
- package/build/services/machines/gateway/admission.js +66 -0
- package/build/services/machines/gateway/api-escalations.d.ts +23 -0
- package/build/services/machines/gateway/api-escalations.js +101 -0
- package/build/services/machines/gateway/dispatcher.d.ts +76 -0
- package/build/services/machines/gateway/dispatcher.js +197 -0
- package/build/services/machines/gateway/escalations.d.ts +51 -0
- package/build/services/machines/gateway/escalations.js +40 -0
- package/build/services/machines/gateway/executor.d.ts +60 -0
- package/build/services/machines/gateway/executor.js +221 -0
- package/build/services/machines/gateway/gateway.d.ts +88 -0
- package/build/services/machines/gateway/gateway.js +270 -0
- package/build/services/machines/gateway/index.d.ts +10 -0
- package/build/services/machines/gateway/index.js +26 -0
- package/build/services/machines/gateway/jobs.d.ts +16 -0
- package/build/services/machines/gateway/jobs.js +24 -0
- package/build/services/machines/gateway/lifecycle.d.ts +21 -0
- package/build/services/machines/gateway/lifecycle.js +89 -0
- package/build/services/machines/gateway/log.d.ts +41 -0
- package/build/services/machines/gateway/log.js +42 -0
- package/build/services/machines/gateway/lt-http.d.ts +51 -0
- package/build/services/machines/gateway/lt-http.js +120 -0
- package/build/services/machines/gateway/pacing.d.ts +43 -0
- package/build/services/machines/gateway/pacing.js +158 -0
- package/build/services/machines/gateway/plan-escalation.d.ts +19 -0
- package/build/services/machines/gateway/plan-escalation.js +210 -0
- package/build/services/machines/gateway/plan-machine.d.ts +17 -0
- package/build/services/machines/gateway/plan-machine.js +283 -0
- package/build/services/machines/gateway/plan-shared.d.ts +18 -0
- package/build/services/machines/gateway/plan-shared.js +36 -0
- package/build/services/machines/gateway/provision.d.ts +22 -0
- package/build/services/machines/gateway/provision.js +79 -0
- package/build/services/machines/gateway/receipts.d.ts +33 -0
- package/build/services/machines/gateway/receipts.js +65 -0
- package/build/services/machines/gateway/reconcile.d.ts +35 -0
- package/build/services/machines/gateway/reconcile.js +99 -0
- package/build/services/machines/gateway/registration.d.ts +38 -0
- package/build/services/machines/gateway/registration.js +121 -0
- package/build/services/machines/gateway/reply-waiter.d.ts +24 -0
- package/build/services/machines/gateway/reply-waiter.js +65 -0
- package/build/services/machines/gateway/state.d.ts +145 -0
- package/build/services/machines/gateway/state.js +241 -0
- package/build/services/machines/gateway/trace.d.ts +16 -0
- package/build/services/machines/gateway/trace.js +43 -0
- package/build/services/machines/identities.d.ts +25 -0
- package/build/services/machines/identities.js +77 -0
- package/build/services/machines/index.d.ts +11 -0
- package/build/services/machines/index.js +27 -0
- package/build/services/machines/leadership.d.ts +35 -0
- package/build/services/machines/leadership.js +100 -0
- package/build/services/machines/roles.d.ts +13 -0
- package/build/services/machines/roles.js +62 -0
- package/build/services/machines/rows.d.ts +30 -0
- package/build/services/machines/rows.js +71 -0
- package/build/services/machines/select.d.ts +25 -0
- package/build/services/machines/select.js +76 -0
- package/build/services/machines/surface.d.ts +33 -0
- package/build/services/machines/surface.js +104 -0
- package/build/services/machines/testing/fake-long-tail.d.ts +79 -0
- package/build/services/machines/testing/fake-long-tail.js +158 -0
- package/build/services/machines/testing/index.d.ts +3 -0
- package/build/services/machines/testing/index.js +19 -0
- package/build/services/machines/testing/recording-log.d.ts +14 -0
- package/build/services/machines/testing/recording-log.js +22 -0
- package/build/services/machines/testing/wait.d.ts +3 -0
- package/build/services/machines/testing/wait.js +17 -0
- package/build/services/mcp/db-server/schemas.d.ts +2 -2
- package/build/start/adapters.js +11 -2
- package/build/start/index.js +20 -1
- package/build/system/mcp-servers/admin/schemas.d.ts +221 -221
- package/build/system/mcp-servers/db-query/schemas.d.ts +2 -2
- package/build/system/mcp-servers/human-queue-schemas.d.ts +6 -6
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/types/index.d.ts +3 -1
- package/build/types/machines.d.ts +167 -0
- package/build/types/machines.js +7 -0
- package/build/types/messaging.d.ts +72 -0
- package/build/types/messaging.js +11 -0
- package/build/types/startup.d.ts +49 -1
- package/docs/events.md +50 -4
- package/docs/machines.md +322 -0
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -246,6 +246,7 @@ All modes share PostgreSQL and scale independently. See [Cloud Deployment](https
|
|
|
246
246
|
| [Schema Enforcement](https://github.com/hotmeshio/long-tail/blob/main/docs/schema-enforcement.md) | form_schema as an enforced API contract on every resolve surface |
|
|
247
247
|
| [Code-Owned Configuration](https://github.com/hotmeshio/long-tail/blob/main/docs/code-owned-configuration.md) | `configSource` — declared config compared and applied at every boot |
|
|
248
248
|
| [Faceted Routing](https://github.com/hotmeshio/long-tail/blob/main/docs/faceted-routing.md) | Query and atomically claim the queue by facets; dispatcher pattern |
|
|
249
|
+
| [Machines](https://github.com/hotmeshio/long-tail/blob/main/docs/machines.md) | `start({ machines })`: machines resolve escalation rows over MQTT, as people do with forms |
|
|
249
250
|
| [SDK](https://github.com/hotmeshio/long-tail/blob/main/docs/sdk.md) | Embedded usage, `createClient`, event subscriptions |
|
|
250
251
|
| [Architecture](https://github.com/hotmeshio/long-tail/blob/main/docs/architecture.md) | Project structure, conventions, discovery |
|
|
251
252
|
| [Cloud](https://github.com/hotmeshio/long-tail/blob/main/docs/cloud.md) | AWS ECS, GCP Cloud Run, Docker |
|
package/build/index.d.ts
CHANGED
|
@@ -21,6 +21,10 @@ export { NatsEventAdapter } from './lib/events/nats';
|
|
|
21
21
|
export { InMemoryEventAdapter } from './lib/events/memory';
|
|
22
22
|
export { SocketIOEventAdapter, type SocketIOAuthenticator } from './lib/events/socketio';
|
|
23
23
|
export { publishMilestoneEvent, publishTaskEvent, publishEscalationEvent, publishActivityEvent, publishWorkflowEvent, publishAgentEvent, publishKnowledgeEvent } from './lib/events/publish';
|
|
24
|
+
export { messagingRegistry, MemoryMessagingProvider, DELIVERY } from './lib/messaging';
|
|
25
|
+
export { NatsMessagingProvider } from './lib/messaging/nats/provider';
|
|
26
|
+
export { renderNatsConf, type NatsConfOptions, type EdgeAccount, type NatsUser } from './lib/messaging/nats/render-conf';
|
|
27
|
+
export { jsPermissions } from './lib/messaging/nats/permissions';
|
|
24
28
|
export { telemetryRegistry } from './lib/telemetry';
|
|
25
29
|
export { HoneycombTelemetryAdapter } from './lib/telemetry/honeycomb';
|
|
26
30
|
export { loggerRegistry } from './lib/logger';
|
|
@@ -50,4 +54,5 @@ export { resolveCredential } from './services/iam/credentials';
|
|
|
50
54
|
export * as KnowledgeApi from './api/knowledge';
|
|
51
55
|
export type { LTClient, LTClientOptions } from './sdk';
|
|
52
56
|
export * as api from './api';
|
|
57
|
+
export * as Machines from './services/machines';
|
|
53
58
|
export { LTExpressAdapter } from './adapters/express';
|
package/build/index.js
CHANGED
|
@@ -15,9 +15,6 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
19
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
|
-
};
|
|
21
18
|
var __importStar = (this && this.__importStar) || (function () {
|
|
22
19
|
var ownKeys = function(o) {
|
|
23
20
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
@@ -35,13 +32,18 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
32
|
return result;
|
|
36
33
|
};
|
|
37
34
|
})();
|
|
35
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
36
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
|
+
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.
|
|
40
|
-
exports.LTExpressAdapter = exports.api = exports.KnowledgeApi = exports.resolveCredential = exports.mimeFromPath = exports.getStorageBackend = exports.listOAuthConnections = exports.getFreshAccessToken = exports.createClient = exports.CallbackEventAdapter = exports.builtinMcpServerFactories = exports.getSystemAgents = exports.getSystemWorkers = exports.registerMcpTool = exports.getToolContext = exports.getActivityIdentity = exports.McpEscalationStrategy = exports.DefaultEscalationStrategy = exports.escalationStrategyRegistry = exports.McpVisionServer = exports.McpTranslationServer = exports.McpServer = void 0;
|
|
39
|
+
exports.PinoLoggerAdapter = exports.loggerRegistry = exports.HoneycombTelemetryAdapter = exports.telemetryRegistry = exports.jsPermissions = exports.renderNatsConf = exports.NatsMessagingProvider = exports.DELIVERY = exports.MemoryMessagingProvider = exports.messagingRegistry = exports.publishKnowledgeEvent = exports.publishAgentEvent = exports.publishWorkflowEvent = exports.publishActivityEvent = exports.publishEscalationEvent = exports.publishTaskEvent = exports.publishMilestoneEvent = exports.SocketIOEventAdapter = exports.InMemoryEventAdapter = exports.NatsEventAdapter = exports.eventRegistry = exports.ltConfig = exports.seedConfigTopics = exports.seedSystemTopics = exports.TopicService = exports.displayGtin = exports.normalizeGtin = exports.isValidGtin = exports.assertValidScanIdentityRule = exports.assertValidScanSteps = exports.assertValidScanScheme = exports.UserService = exports.ConfigService = exports.EscalationService = exports.TaskService = exports.setAuthAdapter = exports.signToken = exports.requireAdmin = exports.requireAuth = exports.createAuthMiddleware = exports.JwtAuthAdapter = exports.conditionalAccumulator = exports.conditionLT = exports.conditional = exports.executeLT = exports.createLTActivityInterceptor = exports.createLTInterceptor = exports.registerLT = exports.midnightTheme = exports.start = void 0;
|
|
40
|
+
exports.LTExpressAdapter = exports.Machines = exports.api = exports.KnowledgeApi = exports.resolveCredential = exports.mimeFromPath = exports.getStorageBackend = exports.listOAuthConnections = exports.getFreshAccessToken = exports.createClient = exports.CallbackEventAdapter = exports.builtinMcpServerFactories = exports.getSystemAgents = exports.getSystemWorkers = exports.registerMcpTool = exports.getToolContext = exports.getActivityIdentity = exports.McpEscalationStrategy = exports.DefaultEscalationStrategy = exports.escalationStrategyRegistry = exports.McpVisionServer = exports.McpTranslationServer = exports.McpServer = exports.McpClient = exports.McpService = exports.BuiltInMcpAdapter = exports.mcpRegistry = exports.defaultMaintenanceConfig = exports.maintenanceRegistry = void 0;
|
|
41
41
|
const config_1 = require("./modules/config");
|
|
42
42
|
const logger_1 = require("./lib/logger");
|
|
43
43
|
const midnight_1 = require("./themes/midnight");
|
|
44
|
+
const path = __importStar(require("path"));
|
|
44
45
|
const start_1 = require("./start");
|
|
46
|
+
const line_1 = require("./lib/machines/line");
|
|
45
47
|
// ─── Package Exports ─────────────────────────────────────────────────────────
|
|
46
48
|
var start_2 = require("./start");
|
|
47
49
|
Object.defineProperty(exports, "start", { enumerable: true, get: function () { return start_2.start; } });
|
|
@@ -99,6 +101,16 @@ Object.defineProperty(exports, "publishActivityEvent", { enumerable: true, get:
|
|
|
99
101
|
Object.defineProperty(exports, "publishWorkflowEvent", { enumerable: true, get: function () { return publish_1.publishWorkflowEvent; } });
|
|
100
102
|
Object.defineProperty(exports, "publishAgentEvent", { enumerable: true, get: function () { return publish_1.publishAgentEvent; } });
|
|
101
103
|
Object.defineProperty(exports, "publishKnowledgeEvent", { enumerable: true, get: function () { return publish_1.publishKnowledgeEvent; } });
|
|
104
|
+
var messaging_1 = require("./lib/messaging");
|
|
105
|
+
Object.defineProperty(exports, "messagingRegistry", { enumerable: true, get: function () { return messaging_1.messagingRegistry; } });
|
|
106
|
+
Object.defineProperty(exports, "MemoryMessagingProvider", { enumerable: true, get: function () { return messaging_1.MemoryMessagingProvider; } });
|
|
107
|
+
Object.defineProperty(exports, "DELIVERY", { enumerable: true, get: function () { return messaging_1.DELIVERY; } });
|
|
108
|
+
var provider_1 = require("./lib/messaging/nats/provider");
|
|
109
|
+
Object.defineProperty(exports, "NatsMessagingProvider", { enumerable: true, get: function () { return provider_1.NatsMessagingProvider; } });
|
|
110
|
+
var render_conf_1 = require("./lib/messaging/nats/render-conf");
|
|
111
|
+
Object.defineProperty(exports, "renderNatsConf", { enumerable: true, get: function () { return render_conf_1.renderNatsConf; } });
|
|
112
|
+
var permissions_1 = require("./lib/messaging/nats/permissions");
|
|
113
|
+
Object.defineProperty(exports, "jsPermissions", { enumerable: true, get: function () { return permissions_1.jsPermissions; } });
|
|
102
114
|
var telemetry_1 = require("./lib/telemetry");
|
|
103
115
|
Object.defineProperty(exports, "telemetryRegistry", { enumerable: true, get: function () { return telemetry_1.telemetryRegistry; } });
|
|
104
116
|
var honeycomb_1 = require("./lib/telemetry/honeycomb");
|
|
@@ -151,6 +163,7 @@ var credentials_1 = require("./services/iam/credentials");
|
|
|
151
163
|
Object.defineProperty(exports, "resolveCredential", { enumerable: true, get: function () { return credentials_1.resolveCredential; } });
|
|
152
164
|
exports.KnowledgeApi = __importStar(require("./api/knowledge"));
|
|
153
165
|
exports.api = __importStar(require("./api"));
|
|
166
|
+
exports.Machines = __importStar(require("./services/machines"));
|
|
154
167
|
var express_1 = require("./adapters/express");
|
|
155
168
|
Object.defineProperty(exports, "LTExpressAdapter", { enumerable: true, get: function () { return express_1.LTExpressAdapter; } });
|
|
156
169
|
// ─── Server ──────────────────────────────────────────────────────────────────
|
|
@@ -252,6 +265,11 @@ async function main() {
|
|
|
252
265
|
escalation: {
|
|
253
266
|
strategy: 'mcp',
|
|
254
267
|
},
|
|
268
|
+
// Machines resolve rows over MQTT when a line and a transport are configured
|
|
269
|
+
// (docker-compose.machines.yml sets both).
|
|
270
|
+
machines: process.env.MACHINE_TRANSPORT && process.env.MACHINE_LINE_CONFIG
|
|
271
|
+
? machinesFromEnv(process.env.MACHINE_LINE_CONFIG, process.env.MACHINE_CODEC_MODULE)
|
|
272
|
+
: undefined,
|
|
255
273
|
branding: {
|
|
256
274
|
...(process.env.WHITE_LABEL ? { appName: process.env.WHITE_LABEL } : {}),
|
|
257
275
|
// Midnight ships as a registered theme — the working proof that a
|
|
@@ -259,11 +277,22 @@ async function main() {
|
|
|
259
277
|
themes: [midnight_1.midnightTheme],
|
|
260
278
|
},
|
|
261
279
|
telemetry: honeycombKey ? { honeycomb: { apiKey: honeycombKey } } : undefined,
|
|
262
|
-
|
|
263
|
-
|
|
280
|
+
// Messaging from LT_MESSAGING / LT_NATS_* when set; otherwise the older EVENT_TRANSPORT=nats with NATS_*.
|
|
281
|
+
messaging: !process.env.LT_MESSAGING && config_1.config.EVENT_TRANSPORT === 'nats'
|
|
282
|
+
? { nats: { url: config_1.config.NATS_URL, token: config_1.config.NATS_TOKEN || undefined, ws: { url: config_1.config.NATS_WS_URL || undefined } } }
|
|
264
283
|
: undefined,
|
|
265
284
|
});
|
|
266
285
|
}
|
|
286
|
+
/** The configured line, spoken to by the codec MACHINE_CODEC_MODULE names (its `createCodec(line)`), or the generic one. */
|
|
287
|
+
function machinesFromEnv(lineConfig, codecModule) {
|
|
288
|
+
const line = (0, line_1.loadLine)(lineConfig);
|
|
289
|
+
if (!codecModule)
|
|
290
|
+
return { lines: [line] };
|
|
291
|
+
const { createCodec } = require(path.resolve(codecModule));
|
|
292
|
+
if (typeof createCodec !== 'function')
|
|
293
|
+
throw new Error(`MACHINE_CODEC_MODULE=${codecModule} exports no createCodec(line)`);
|
|
294
|
+
return { lines: [line], codec: createCodec(line) };
|
|
295
|
+
}
|
|
267
296
|
// Run server when executed directly
|
|
268
297
|
if (require.main === module) {
|
|
269
298
|
require('dotenv').config();
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { LTEvent, LTEventAdapter } from '../../types';
|
|
2
|
+
import type { LTMessagingProvider } from '../../types/messaging';
|
|
3
|
+
/** The part of Long Tail's messaging the adapter uses. */
|
|
4
|
+
export type EventMessaging = Pick<LTMessagingProvider, 'connected' | 'pubsub'>;
|
|
2
5
|
/**
|
|
3
6
|
* NATS event adapter — publish AND subscribe.
|
|
4
7
|
*
|
|
@@ -11,6 +14,9 @@ import type { LTEvent, LTEventAdapter } from '../../types';
|
|
|
11
14
|
* An `_originId` field prevents the publishing container from
|
|
12
15
|
* re-dispatching its own events.
|
|
13
16
|
*
|
|
17
|
+
* Given `messaging` (Long Tail's messaging, `start({ messaging })`), it publishes
|
|
18
|
+
* and bridges over that connection; otherwise it opens a connection of its own.
|
|
19
|
+
*
|
|
14
20
|
* Usage:
|
|
15
21
|
* ```typescript
|
|
16
22
|
* import { eventRegistry, NatsEventAdapter } from '@hotmeshio/long-tail';
|
|
@@ -22,6 +28,8 @@ import type { LTEvent, LTEventAdapter } from '../../types';
|
|
|
22
28
|
export declare class NatsEventAdapter implements LTEventAdapter {
|
|
23
29
|
private nc;
|
|
24
30
|
private sub;
|
|
31
|
+
private readonly messaging;
|
|
32
|
+
private bridge;
|
|
25
33
|
private url;
|
|
26
34
|
private _wsUrl;
|
|
27
35
|
private _wsProxyTarget;
|
|
@@ -36,6 +44,7 @@ export declare class NatsEventAdapter implements LTEventAdapter {
|
|
|
36
44
|
wsProxy?: string;
|
|
37
45
|
subjectPrefix?: string;
|
|
38
46
|
token?: string;
|
|
47
|
+
messaging?: EventMessaging;
|
|
39
48
|
});
|
|
40
49
|
/**
|
|
41
50
|
* Public NATS WebSocket URL for browser connections.
|
|
@@ -59,6 +68,8 @@ export declare class NatsEventAdapter implements LTEventAdapter {
|
|
|
59
68
|
*/
|
|
60
69
|
setCallbackBridge(adapter: LTEventAdapter): void;
|
|
61
70
|
connect(): Promise<void>;
|
|
71
|
+
/** An event from another container, handed to the local callback adapter. */
|
|
72
|
+
private bridgeIn;
|
|
62
73
|
publish(event: LTEvent): Promise<void>;
|
|
63
74
|
disconnect(): Promise<void>;
|
|
64
75
|
}
|
package/build/lib/events/nats.js
CHANGED
|
@@ -18,6 +18,9 @@ const sc = (0, nats_1.StringCodec)();
|
|
|
18
18
|
* An `_originId` field prevents the publishing container from
|
|
19
19
|
* re-dispatching its own events.
|
|
20
20
|
*
|
|
21
|
+
* Given `messaging` (Long Tail's messaging, `start({ messaging })`), it publishes
|
|
22
|
+
* and bridges over that connection; otherwise it opens a connection of its own.
|
|
23
|
+
*
|
|
21
24
|
* Usage:
|
|
22
25
|
* ```typescript
|
|
23
26
|
* import { eventRegistry, NatsEventAdapter } from '@hotmeshio/long-tail';
|
|
@@ -30,9 +33,11 @@ class NatsEventAdapter {
|
|
|
30
33
|
constructor(options) {
|
|
31
34
|
this.nc = null;
|
|
32
35
|
this.sub = null;
|
|
36
|
+
this.bridge = null;
|
|
33
37
|
this._wsProxyBasePath = '';
|
|
34
38
|
this.originId = (0, crypto_1.randomUUID)();
|
|
35
39
|
this.callbackAdapter = null;
|
|
40
|
+
this.messaging = options?.messaging ?? null;
|
|
36
41
|
this.url = options?.url || config_1.config.NATS_URL;
|
|
37
42
|
this._wsUrl = options?.wsUrl || config_1.config.NATS_WS_URL || null;
|
|
38
43
|
this._wsProxyTarget = options?.wsProxy || null;
|
|
@@ -75,6 +80,11 @@ class NatsEventAdapter {
|
|
|
75
80
|
this.callbackAdapter = adapter;
|
|
76
81
|
}
|
|
77
82
|
async connect() {
|
|
83
|
+
if (this.messaging) {
|
|
84
|
+
if (this.callbackAdapter)
|
|
85
|
+
this.bridge = this.messaging.pubsub.subscribe(`${this.subjectPrefix}.>`, (m) => this.bridgeIn(m));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
78
88
|
this.nc = await (0, nats_1.connect)({
|
|
79
89
|
servers: this.url,
|
|
80
90
|
token: this.token,
|
|
@@ -102,15 +112,36 @@ class NatsEventAdapter {
|
|
|
102
112
|
})();
|
|
103
113
|
}
|
|
104
114
|
}
|
|
115
|
+
/** An event from another container, handed to the local callback adapter. */
|
|
116
|
+
bridgeIn(m) {
|
|
117
|
+
try {
|
|
118
|
+
const event = JSON.parse(m.payload);
|
|
119
|
+
if (event._originId === this.originId)
|
|
120
|
+
return;
|
|
121
|
+
this.callbackAdapter?.publish(event);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Malformed message, skip
|
|
125
|
+
}
|
|
126
|
+
}
|
|
105
127
|
async publish(event) {
|
|
106
|
-
if (!this.nc)
|
|
107
|
-
return;
|
|
108
128
|
const subject = `${this.subjectPrefix}.${event.type}`;
|
|
109
129
|
const enriched = { ...event, _originId: this.originId };
|
|
130
|
+
if (this.messaging) {
|
|
131
|
+
if (this.messaging.connected)
|
|
132
|
+
await this.messaging.pubsub.publish(subject, JSON.stringify(enriched));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!this.nc)
|
|
136
|
+
return;
|
|
110
137
|
this.nc.publish(subject, sc.encode(JSON.stringify(enriched)));
|
|
111
138
|
logger_1.loggerRegistry.info(`[lt-events:nats] published: ${subject} (origin=${this.originId.slice(0, 8)})`);
|
|
112
139
|
}
|
|
113
140
|
async disconnect() {
|
|
141
|
+
if (this.bridge) {
|
|
142
|
+
await this.bridge.close();
|
|
143
|
+
this.bridge = null;
|
|
144
|
+
}
|
|
114
145
|
if (this.sub) {
|
|
115
146
|
this.sub.unsubscribe();
|
|
116
147
|
this.sub = null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./protocol"), exports);
|
|
18
|
+
__exportStar(require("./transport"), exports);
|
|
19
|
+
__exportStar(require("./line"), exports);
|
|
20
|
+
__exportStar(require("./site/site-conf"), exports);
|
|
21
|
+
__exportStar(require("./site/edge-account"), exports);
|
|
22
|
+
__exportStar(require("./site/mosquitto-conf"), exports);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The line: site, line and the machines on it, in route order, read from a
|
|
3
|
+
* JSON file (MACHINE_LINE_CONFIG) or passed in by the host.
|
|
4
|
+
*/
|
|
5
|
+
import type { LineDef, MachineDef, MachineIdent, MaterialDef, MeasurementDef } from '../../types/machines';
|
|
6
|
+
export type { LineDef, MachineDef, MaterialDef, MeasurementDef };
|
|
7
|
+
/** How a line knows its machines' addresses (`LineDef.addressing`). */
|
|
8
|
+
export declare const ADDRESSING: {
|
|
9
|
+
readonly DECLARED: "declared";
|
|
10
|
+
readonly LEARNED: "learned";
|
|
11
|
+
};
|
|
12
|
+
/** Whether the line learns this station's address from the machine that claims it. */
|
|
13
|
+
export declare function learnsAddress(line: LineDef, machineId: string): boolean;
|
|
14
|
+
/** Which machine puts a unit on its carrier plate and which takes it off. */
|
|
15
|
+
export declare const CARRIER_ROLE: {
|
|
16
|
+
readonly BIND: "bind";
|
|
17
|
+
readonly RELEASE: "release";
|
|
18
|
+
readonly NONE: "none";
|
|
19
|
+
};
|
|
20
|
+
export type CarrierRole = typeof CARRIER_ROLE[keyof typeof CARRIER_ROLE];
|
|
21
|
+
/** Reject a line whose identifiers would not survive the topic mapping. */
|
|
22
|
+
export declare function validateLine(line: LineDef): LineDef;
|
|
23
|
+
/** The line at `path` (JSON), or `fallback` when no path is given. */
|
|
24
|
+
export declare function loadLine(path: string | undefined, fallback?: LineDef): LineDef;
|
|
25
|
+
export declare function identOf(line: LineDef, machineId: string): MachineIdent;
|
|
26
|
+
export declare function machineById(line: LineDef, machineId: string): MachineDef;
|
|
27
|
+
/** The broker identity of each machine whose address the line declares, {site}-{line}-{address}. */
|
|
28
|
+
export declare function clientIds(line: LineDef): string[];
|
|
29
|
+
/** The broker and Long Tail identity of the line's gateway. */
|
|
30
|
+
export declare function controllerId(line: LineDef): string;
|
|
31
|
+
/** The default role name for a process. */
|
|
32
|
+
export declare function stationRole(process: string): string;
|
|
33
|
+
export declare const DEFAULT_EXIT_ROLE = "line-exit";
|
|
34
|
+
export declare const DEFAULT_OPERATOR_ROLE = "line-operator";
|
|
35
|
+
/** The Long Tail role a machine resolves handoffs under. */
|
|
36
|
+
export declare function machineRole(def: Pick<MachineDef, 'role' | 'process'>): string;
|
|
37
|
+
/** Every station role on the line, once each, in route order. */
|
|
38
|
+
export declare function stationRoles(line: LineDef): string[];
|
|
39
|
+
/** The role of the person who receives units at line exit. */
|
|
40
|
+
export declare function exitRole(line: LineDef): string;
|
|
41
|
+
/** Roles of people who take units straight from a machine: the exit role and any receivers. */
|
|
42
|
+
export declare function receiverRoles(line: LineDef): string[];
|
|
43
|
+
/** The role of the person who decides failed handoffs and closes alarm and machine rows. */
|
|
44
|
+
export declare function operatorRole(line: LineDef): string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The line: site, line and the machines on it, in route order, read from a
|
|
4
|
+
* JSON file (MACHINE_LINE_CONFIG) or passed in by the host.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.DEFAULT_OPERATOR_ROLE = exports.DEFAULT_EXIT_ROLE = exports.CARRIER_ROLE = exports.ADDRESSING = void 0;
|
|
8
|
+
exports.learnsAddress = learnsAddress;
|
|
9
|
+
exports.validateLine = validateLine;
|
|
10
|
+
exports.loadLine = loadLine;
|
|
11
|
+
exports.identOf = identOf;
|
|
12
|
+
exports.machineById = machineById;
|
|
13
|
+
exports.clientIds = clientIds;
|
|
14
|
+
exports.controllerId = controllerId;
|
|
15
|
+
exports.stationRole = stationRole;
|
|
16
|
+
exports.machineRole = machineRole;
|
|
17
|
+
exports.stationRoles = stationRoles;
|
|
18
|
+
exports.exitRole = exitRole;
|
|
19
|
+
exports.receiverRoles = receiverRoles;
|
|
20
|
+
exports.operatorRole = operatorRole;
|
|
21
|
+
const fs_1 = require("fs");
|
|
22
|
+
const protocol_1 = require("./protocol");
|
|
23
|
+
/** How a line knows its machines' addresses (`LineDef.addressing`). */
|
|
24
|
+
exports.ADDRESSING = { DECLARED: 'declared', LEARNED: 'learned' };
|
|
25
|
+
/** Whether the line learns this station's address from the machine that claims it. */
|
|
26
|
+
function learnsAddress(line, machineId) {
|
|
27
|
+
return line.addressing === exports.ADDRESSING.LEARNED && !line.machines.find((m) => m.machineId === machineId)?.address;
|
|
28
|
+
}
|
|
29
|
+
/** Which machine puts a unit on its carrier plate and which takes it off. */
|
|
30
|
+
exports.CARRIER_ROLE = { BIND: 'bind', RELEASE: 'release', NONE: 'none' };
|
|
31
|
+
/** Reject a line whose identifiers would not survive the topic mapping. */
|
|
32
|
+
function validateLine(line) {
|
|
33
|
+
(0, protocol_1.assertTopicLevel)('siteId', line.siteId);
|
|
34
|
+
(0, protocol_1.assertTopicLevel)('lineId', line.lineId);
|
|
35
|
+
if (!line.machines?.length)
|
|
36
|
+
throw new Error('line has no machines');
|
|
37
|
+
if (line.addressing && !Object.values(exports.ADDRESSING).includes(line.addressing)) {
|
|
38
|
+
throw new Error(`addressing must be one of ${Object.values(exports.ADDRESSING).join(', ')}`);
|
|
39
|
+
}
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
const addresses = new Set();
|
|
42
|
+
for (const m of line.machines) {
|
|
43
|
+
(0, protocol_1.assertTopicLevel)('machineId', m.machineId);
|
|
44
|
+
if (seen.has(m.machineId))
|
|
45
|
+
throw new Error(`duplicate machineId "${m.machineId}"`);
|
|
46
|
+
seen.add(m.machineId);
|
|
47
|
+
if (!learnsAddress(line, m.machineId)) {
|
|
48
|
+
const address = m.address ?? m.machineId;
|
|
49
|
+
(0, protocol_1.assertTopicLevel)('address', address);
|
|
50
|
+
if (addresses.has(address))
|
|
51
|
+
throw new Error(`duplicate address "${address}"`);
|
|
52
|
+
addresses.add(address);
|
|
53
|
+
}
|
|
54
|
+
if (!(m.cycleSec > 0))
|
|
55
|
+
throw new Error(`${m.machineId}: cycleSec must be positive`);
|
|
56
|
+
if (m.queueDepth !== undefined && !(Number.isInteger(m.queueDepth) && m.queueDepth >= 1)) {
|
|
57
|
+
throw new Error(`${m.machineId}: queueDepth must be a whole number of at least 1`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return line;
|
|
61
|
+
}
|
|
62
|
+
/** The line at `path` (JSON), or `fallback` when no path is given. */
|
|
63
|
+
function loadLine(path, fallback) {
|
|
64
|
+
if (!path) {
|
|
65
|
+
if (!fallback)
|
|
66
|
+
throw new Error('no line definition: set MACHINE_LINE_CONFIG or pass one');
|
|
67
|
+
return validateLine(fallback);
|
|
68
|
+
}
|
|
69
|
+
return validateLine(JSON.parse((0, fs_1.readFileSync)(path, 'utf8')));
|
|
70
|
+
}
|
|
71
|
+
function identOf(line, machineId) {
|
|
72
|
+
const address = line.machines.find((m) => m.machineId === machineId)?.address;
|
|
73
|
+
return { siteId: line.siteId, lineId: line.lineId, machineId, ...(address ? { address } : {}) };
|
|
74
|
+
}
|
|
75
|
+
function machineById(line, machineId) {
|
|
76
|
+
const def = line.machines.find((m) => m.machineId === machineId);
|
|
77
|
+
if (!def)
|
|
78
|
+
throw new Error(`machine "${machineId}" is not on ${line.siteId}/${line.lineId}`);
|
|
79
|
+
return def;
|
|
80
|
+
}
|
|
81
|
+
/** The broker identity of each machine whose address the line declares, {site}-{line}-{address}. */
|
|
82
|
+
function clientIds(line) {
|
|
83
|
+
return line.machines.filter((m) => !learnsAddress(line, m.machineId)).map((m) => (0, protocol_1.clientIdOf)(identOf(line, m.machineId)));
|
|
84
|
+
}
|
|
85
|
+
/** The broker and Long Tail identity of the line's gateway. */
|
|
86
|
+
function controllerId(line) {
|
|
87
|
+
return `line-controller-${line.siteId}-${line.lineId}`;
|
|
88
|
+
}
|
|
89
|
+
/** The default role name for a process. */
|
|
90
|
+
function stationRole(process) {
|
|
91
|
+
return `line-${process.replace(/_/g, '-')}`;
|
|
92
|
+
}
|
|
93
|
+
exports.DEFAULT_EXIT_ROLE = 'line-exit';
|
|
94
|
+
exports.DEFAULT_OPERATOR_ROLE = 'line-operator';
|
|
95
|
+
/** The Long Tail role a machine resolves handoffs under. */
|
|
96
|
+
function machineRole(def) {
|
|
97
|
+
return def.role ?? stationRole(def.process);
|
|
98
|
+
}
|
|
99
|
+
/** Every station role on the line, once each, in route order. */
|
|
100
|
+
function stationRoles(line) {
|
|
101
|
+
return [...new Set(line.machines.map(machineRole))];
|
|
102
|
+
}
|
|
103
|
+
/** The role of the person who receives units at line exit. */
|
|
104
|
+
function exitRole(line) {
|
|
105
|
+
return line.roles?.exit ?? exports.DEFAULT_EXIT_ROLE;
|
|
106
|
+
}
|
|
107
|
+
/** Roles of people who take units straight from a machine: the exit role and any receivers. */
|
|
108
|
+
function receiverRoles(line) {
|
|
109
|
+
return [...new Set([exitRole(line), ...(line.roles?.receivers ?? [])])];
|
|
110
|
+
}
|
|
111
|
+
/** The role of the person who decides failed handoffs and closes alarm and machine rows. */
|
|
112
|
+
function operatorRole(line) {
|
|
113
|
+
return line.roles?.operator ?? exports.DEFAULT_OPERATOR_ROLE;
|
|
114
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wire, behind one seam. A codec turns a machine's MQTT messages into
|
|
3
|
+
* the line protocol's events (topics.ts, contract.ts) and the gateway's
|
|
4
|
+
* commands back into MQTT messages. The gateway, the rows and the workflow
|
|
5
|
+
* only ever see the line protocol. The generic codec speaks it natively; a
|
|
6
|
+
* host whose machines speak another protocol supplies a codec for it.
|
|
7
|
+
*/
|
|
8
|
+
import type { LineDef } from '../../../types/machines';
|
|
9
|
+
import { type Inspection } from './contract';
|
|
10
|
+
import { type MachineIdent } from './envelope';
|
|
11
|
+
export interface WireMessage {
|
|
12
|
+
topic: string;
|
|
13
|
+
payload: string;
|
|
14
|
+
}
|
|
15
|
+
export interface MachineCommand {
|
|
16
|
+
cmdId: string;
|
|
17
|
+
op: string;
|
|
18
|
+
args: Record<string, unknown>;
|
|
19
|
+
/** How many times this gateway has sent this cmdId, this time included. */
|
|
20
|
+
attempt?: number;
|
|
21
|
+
/** The command may have reached the machine before: sent earlier, or its row predates this gateway's takeover. */
|
|
22
|
+
retry?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** MQTT filters under each machine's topic, e.g. `job` or `alarm/+`. */
|
|
25
|
+
export interface CodecChannels {
|
|
26
|
+
/** Messages the gateway acts on; kept in the durable event stream. */
|
|
27
|
+
events: string[];
|
|
28
|
+
/** Readings that only prove a machine is alive; read live. */
|
|
29
|
+
telemetry: string[];
|
|
30
|
+
/** Where a machine reads its commands; only the line controller writes them. */
|
|
31
|
+
commands: string[];
|
|
32
|
+
}
|
|
33
|
+
/** Which topics a filter covers: a level left out matches any (`+`). */
|
|
34
|
+
export interface TopicScope {
|
|
35
|
+
siteId?: string;
|
|
36
|
+
lineId?: string;
|
|
37
|
+
/** The machine's wire address (`MachineDef.address`, else its id). */
|
|
38
|
+
address?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface LineCodec {
|
|
41
|
+
readonly root: string;
|
|
42
|
+
readonly channels: CodecChannels;
|
|
43
|
+
/**
|
|
44
|
+
* The topic of one machine channel. Defaults to `{root}/{site}/{line}/{address}/{channel}`.
|
|
45
|
+
* A layout must begin with `{root}/{site}/{line}/`, the prefix the line's subscription and
|
|
46
|
+
* retained reads use; the ACL and stream subjects are built from this.
|
|
47
|
+
*/
|
|
48
|
+
topicFor?(scope: TopicScope, channel: string): string;
|
|
49
|
+
/** Most units one dispatch may carry; the gateway sends one dispatch per unit when 1. Null: any. */
|
|
50
|
+
readonly unitsPerDispatch: number | null;
|
|
51
|
+
/**
|
|
52
|
+
* When a handoff's job goes to its receiver: as soon as the row exists (the default), or once every
|
|
53
|
+
* unit has arrived there, for machines that take work only for units they have scanned in. Each
|
|
54
|
+
* dispatched unit then carries the arrival event its receiver reported.
|
|
55
|
+
*/
|
|
56
|
+
readonly dispatchOn?: DispatchOn;
|
|
57
|
+
/**
|
|
58
|
+
* Strict: a message with contract violations is confirmed but never acted on, and a row shows it
|
|
59
|
+
* to a person. Otherwise (the default) known fields are acted on and the violations are logged.
|
|
60
|
+
*/
|
|
61
|
+
readonly strict?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The machines keep no retained state and report only what changes: the gateway asks each one for
|
|
64
|
+
* a snapshot when it takes the line, so it knows every machine's job before acting on a change.
|
|
65
|
+
*/
|
|
66
|
+
readonly snapshotOnStart?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Active alarms stay retained on the broker until they clear, as in the line protocol. A takeover's
|
|
69
|
+
* retained read is then every active alarm, and an alarm row whose alarm is missing from it cleared
|
|
70
|
+
* while no gateway listened. Otherwise a machine's snapshot lists its active alarms, and an alarm it
|
|
71
|
+
* no longer lists has cleared.
|
|
72
|
+
*/
|
|
73
|
+
readonly retainedAlarms?: boolean;
|
|
74
|
+
/** One wire message as zero or more line-protocol messages; empty when the topic is not a machine's. */
|
|
75
|
+
decode(topic: string, payload: string, retained?: boolean): Inspection[];
|
|
76
|
+
/** The command as wire messages; none when the machines have no such command, and the gateway then sends nothing. */
|
|
77
|
+
encode(ident: MachineIdent, command: MachineCommand): WireMessage[];
|
|
78
|
+
/**
|
|
79
|
+
* The receipt confirming messages the gateway is done with (each decoded with a `receiptId`), for
|
|
80
|
+
* machines that keep an outbox until confirmed. Absent: no receipts are sent.
|
|
81
|
+
*/
|
|
82
|
+
receipt?(ident: MachineIdent, messageIds: string[]): WireMessage[];
|
|
83
|
+
}
|
|
84
|
+
export declare const DISPATCH_ON: {
|
|
85
|
+
readonly CREATED: "created";
|
|
86
|
+
readonly ARRIVAL: "arrival";
|
|
87
|
+
};
|
|
88
|
+
export type DispatchOn = typeof DISPATCH_ON[keyof typeof DISPATCH_ON];
|
|
89
|
+
/** Which job event counts as the receiver's receipt. */
|
|
90
|
+
export declare const RECEIPT_FROM: {
|
|
91
|
+
readonly ARRIVED: "arrived";
|
|
92
|
+
readonly BOUND: "bound";
|
|
93
|
+
readonly STARTED: "started";
|
|
94
|
+
};
|
|
95
|
+
export type ReceiptFrom = typeof RECEIPT_FROM[keyof typeof RECEIPT_FROM];
|
|
96
|
+
export interface GenericCodecOptions {
|
|
97
|
+
root: string;
|
|
98
|
+
/** Maps topic levels back to machine ids for machines whose `address` differs from their id. */
|
|
99
|
+
line?: Pick<LineDef, 'machines'>;
|
|
100
|
+
/** For machines that report no arrival: treat the receiver's carrier bind or job start as the receipt. */
|
|
101
|
+
receiptFrom?: ReceiptFrom;
|
|
102
|
+
unitsPerDispatch?: number | null;
|
|
103
|
+
dispatchOn?: DispatchOn;
|
|
104
|
+
}
|
|
105
|
+
export declare const GENERIC_CHANNELS: CodecChannels;
|
|
106
|
+
/** The topic of one machine channel under the codec's layout. */
|
|
107
|
+
export declare function topicOf(codec: Pick<LineCodec, 'root' | 'topicFor'>, scope: TopicScope, channel: string): string;
|
|
108
|
+
export declare function genericCodec(opts: GenericCodecOptions): LineCodec;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The wire, behind one seam. A codec turns a machine's MQTT messages into
|
|
4
|
+
* the line protocol's events (topics.ts, contract.ts) and the gateway's
|
|
5
|
+
* commands back into MQTT messages. The gateway, the rows and the workflow
|
|
6
|
+
* only ever see the line protocol. The generic codec speaks it natively; a
|
|
7
|
+
* host whose machines speak another protocol supplies a codec for it.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.GENERIC_CHANNELS = exports.RECEIPT_FROM = exports.DISPATCH_ON = void 0;
|
|
11
|
+
exports.topicOf = topicOf;
|
|
12
|
+
exports.genericCodec = genericCodec;
|
|
13
|
+
const constants_1 = require("./constants");
|
|
14
|
+
const contract_1 = require("./contract");
|
|
15
|
+
const envelope_1 = require("./envelope");
|
|
16
|
+
const topics_1 = require("./topics");
|
|
17
|
+
exports.DISPATCH_ON = { CREATED: 'created', ARRIVAL: 'arrival' };
|
|
18
|
+
/** Which job event counts as the receiver's receipt. */
|
|
19
|
+
exports.RECEIPT_FROM = { ARRIVED: 'arrived', BOUND: 'bound', STARTED: 'started' };
|
|
20
|
+
exports.GENERIC_CHANNELS = {
|
|
21
|
+
events: [constants_1.CHANNEL.PRESENCE, constants_1.CHANNEL.SNAPSHOT, constants_1.CHANNEL.JOB, `${constants_1.CHANNEL.ALARM}/+`, constants_1.CHANNEL.REPLY, `${constants_1.CHANNEL.REPORT}/#`],
|
|
22
|
+
telemetry: [constants_1.CHANNEL.TELEMETRY],
|
|
23
|
+
commands: [constants_1.CHANNEL.CMD],
|
|
24
|
+
};
|
|
25
|
+
/** The topic of one machine channel under the codec's layout. */
|
|
26
|
+
function topicOf(codec, scope, channel) {
|
|
27
|
+
if (codec.topicFor)
|
|
28
|
+
return codec.topicFor(scope, channel);
|
|
29
|
+
const level = (v) => v ?? '+';
|
|
30
|
+
return `${codec.root}/${level(scope.siteId)}/${level(scope.lineId)}/${level(scope.address)}/${channel}`;
|
|
31
|
+
}
|
|
32
|
+
/** Topic level to machine id, for the machines whose address is not their id. */
|
|
33
|
+
function addressMap(line) {
|
|
34
|
+
return new Map((line?.machines ?? []).filter((m) => m.address && m.address !== m.machineId).map((m) => [m.address, m.machineId]));
|
|
35
|
+
}
|
|
36
|
+
/** The inspection with the topic's address read back as the machine id; an unknown address keeps the station its message claims. */
|
|
37
|
+
function atMachine(seen, ids) {
|
|
38
|
+
const machineId = seen.parsed && ids.get(seen.parsed.ident.machineId);
|
|
39
|
+
if (!machineId) {
|
|
40
|
+
const station = seen.body?.source?.station;
|
|
41
|
+
return typeof station === 'string' && station ? { ...seen, claim: station } : seen;
|
|
42
|
+
}
|
|
43
|
+
const ident = { ...seen.parsed.ident, machineId, address: seen.parsed.ident.machineId };
|
|
44
|
+
return { ...seen, parsed: { ...seen.parsed, ident } };
|
|
45
|
+
}
|
|
46
|
+
function withEvent(seen, event) {
|
|
47
|
+
const body = seen.body;
|
|
48
|
+
return { ...seen, body: { ...body, body: { ...body.body, event } } };
|
|
49
|
+
}
|
|
50
|
+
/** Another event as the receipt: the arrival first, then that event; reported arrivals are dropped. */
|
|
51
|
+
function inferReceipt(seen, from) {
|
|
52
|
+
if (seen.parsed?.kind !== topics_1.MESSAGE_KIND.JOB || !seen.body)
|
|
53
|
+
return [seen];
|
|
54
|
+
const event = seen.body.body?.event;
|
|
55
|
+
if (event === constants_1.JOB_EVENT.ARRIVED)
|
|
56
|
+
return [];
|
|
57
|
+
if (event === from)
|
|
58
|
+
return [withEvent(seen, constants_1.JOB_EVENT.ARRIVED), seen];
|
|
59
|
+
return [seen];
|
|
60
|
+
}
|
|
61
|
+
function genericCodec(opts) {
|
|
62
|
+
const receiptFrom = opts.receiptFrom ?? exports.RECEIPT_FROM.ARRIVED;
|
|
63
|
+
const ids = addressMap(opts.line);
|
|
64
|
+
return {
|
|
65
|
+
root: opts.root,
|
|
66
|
+
channels: exports.GENERIC_CHANNELS,
|
|
67
|
+
unitsPerDispatch: opts.unitsPerDispatch ?? null,
|
|
68
|
+
dispatchOn: opts.dispatchOn ?? exports.DISPATCH_ON.CREATED,
|
|
69
|
+
retainedAlarms: true,
|
|
70
|
+
decode(topic, payload, retained) {
|
|
71
|
+
const seen = atMachine((0, contract_1.inspectMessage)(opts.root, topic, payload, retained), ids);
|
|
72
|
+
if (!seen.parsed)
|
|
73
|
+
return [];
|
|
74
|
+
return receiptFrom === exports.RECEIPT_FROM.ARRIVED ? [seen] : inferReceipt(seen, receiptFrom);
|
|
75
|
+
},
|
|
76
|
+
encode(ident, command) {
|
|
77
|
+
return [{ topic: (0, topics_1.machineTopic)(opts.root, ident, constants_1.CHANNEL.CMD), payload: JSON.stringify((0, envelope_1.buildEnvelope)(ident, command)) }];
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|