@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
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.McpClient = exports.McpService = exports.BuiltInMcpAdapter = exports.mcpRegistry = exports.defaultMaintenanceConfig = exports.maintenanceRegistry = exports.PinoLoggerAdapter = exports.loggerRegistry = exports.HoneycombTelemetryAdapter = exports.telemetryRegistry = 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.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
- events: config_1.config.EVENT_TRANSPORT === 'nats'
263
- ? { nats: { url: config_1.config.NATS_URL, wsUrl: config_1.config.NATS_WS_URL || undefined, token: config_1.config.NATS_TOKEN || undefined } }
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
  }
@@ -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,6 @@
1
+ export * from './protocol';
2
+ export * from './transport';
3
+ export * from './line';
4
+ export * from './site/site-conf';
5
+ export * from './site/edge-account';
6
+ export * from './site/mosquitto-conf';
@@ -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
+ }