miravo 0.6.0 → 0.6.2

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 (187) hide show
  1. package/LICENSE +199 -0
  2. package/README.md +29 -59
  3. package/bin/miravo.js +84 -0
  4. package/package.json +21 -37
  5. package/console-dist/assets/activity-DsWeRq4h.js +0 -1
  6. package/console-dist/assets/api-4AqKvsm9.js +0 -1
  7. package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
  8. package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
  9. package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
  10. package/console-dist/assets/box-C1WaoOMF.js +0 -1
  11. package/console-dist/assets/card-BjeP32PN.js +0 -1
  12. package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
  13. package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
  14. package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
  15. package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
  16. package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
  17. package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
  18. package/console-dist/assets/control-CPhvwx9o.js +0 -1
  19. package/console-dist/assets/control-Cg2up9NA.js +0 -1
  20. package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
  21. package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
  22. package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
  23. package/console-dist/assets/dist-BhbDML0a.js +0 -1
  24. package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
  25. package/console-dist/assets/download-DQ6NH16U.js +0 -1
  26. package/console-dist/assets/engine-store-CrLnz692.js +0 -1
  27. package/console-dist/assets/events-CXm29Ok4.js +0 -1
  28. package/console-dist/assets/events-D01IxhnP.js +0 -1
  29. package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
  30. package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
  31. package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
  32. package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
  33. package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
  34. package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
  35. package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
  36. package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
  37. package/console-dist/assets/fleet-lotMGVqe.js +0 -1
  38. package/console-dist/assets/format-kXTonpDJ.js +0 -1
  39. package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
  40. package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
  41. package/console-dist/assets/index-CVPKSDvJ.js +0 -12
  42. package/console-dist/assets/index-DJkgDeun.css +0 -2
  43. package/console-dist/assets/input-BWEQFptx.js +0 -1
  44. package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
  45. package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
  46. package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
  47. package/console-dist/assets/label-uephX5ZG.js +0 -1
  48. package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
  49. package/console-dist/assets/login-C3RJli0m.js +0 -126
  50. package/console-dist/assets/models-B_UioWQ5.js +0 -1
  51. package/console-dist/assets/models-_IPlPiZ7.js +0 -1
  52. package/console-dist/assets/not-found-B8gAm08S.js +0 -1
  53. package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
  54. package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
  55. package/console-dist/assets/platforms-DStNbtVc.js +0 -1
  56. package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
  57. package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
  58. package/console-dist/assets/radio-CoHegOv5.js +0 -1
  59. package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
  60. package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
  61. package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
  62. package/console-dist/assets/select-CeO4vP0G.js +0 -1
  63. package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
  64. package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
  65. package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
  66. package/console-dist/assets/tags-CiXOIHtY.js +0 -4
  67. package/console-dist/assets/tags-FQCXIxrP.js +0 -1
  68. package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
  69. package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
  70. package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
  71. package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
  72. package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
  73. package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
  74. package/console-dist/assets/validation-Blvud9_O.js +0 -1
  75. package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
  76. package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
  77. package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
  78. package/console-dist/favicon.svg +0 -37
  79. package/console-dist/index.html +0 -82
  80. package/console-dist/miravo-logo.png +0 -0
  81. package/src/__tests__/integration/executable-methods-runner.ts +0 -262
  82. package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
  83. package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
  84. package/src/commands/adapter.ts +0 -249
  85. package/src/commands/apply.test.ts +0 -62
  86. package/src/commands/apply.ts +0 -216
  87. package/src/commands/cleanup.test.ts +0 -34
  88. package/src/commands/cleanup.ts +0 -201
  89. package/src/commands/cli-test-setup.ts +0 -91
  90. package/src/commands/export.test.ts +0 -51
  91. package/src/commands/export.ts +0 -404
  92. package/src/commands/fork.ts +0 -219
  93. package/src/commands/inject.test.ts +0 -57
  94. package/src/commands/inject.ts +0 -212
  95. package/src/commands/load.ts +0 -167
  96. package/src/commands/logs.ts +0 -173
  97. package/src/commands/models.test.ts +0 -162
  98. package/src/commands/models.ts +0 -338
  99. package/src/commands/pause.ts +0 -67
  100. package/src/commands/platforms.test.ts +0 -186
  101. package/src/commands/platforms.ts +0 -371
  102. package/src/commands/ps.ts +0 -116
  103. package/src/commands/remove.ts +0 -38
  104. package/src/commands/reset.ts +0 -44
  105. package/src/commands/rm.ts +0 -97
  106. package/src/commands/spawn.ts +0 -45
  107. package/src/commands/speed.ts +0 -40
  108. package/src/commands/start/adapter-config.ts +0 -157
  109. package/src/commands/start/catalog-le-dt.test.ts +0 -50
  110. package/src/commands/start/catalog-le-dt.ts +0 -187
  111. package/src/commands/start/catalog-model-schema.ts +0 -89
  112. package/src/commands/start/catalog-template-detail.ts +0 -54
  113. package/src/commands/start/daemon.ts +0 -255
  114. package/src/commands/start/display.ts +0 -150
  115. package/src/commands/start/engine-setup.ts +0 -247
  116. package/src/commands/start/events.ts +0 -126
  117. package/src/commands/start/index.ts +0 -897
  118. package/src/commands/start/protocol-plans.ts +0 -239
  119. package/src/commands/start/template-loader.ts +0 -197
  120. package/src/commands/start/template-resolver.ts +0 -185
  121. package/src/commands/start/types.ts +0 -73
  122. package/src/commands/start.test.ts +0 -480
  123. package/src/commands/status.test.ts +0 -20
  124. package/src/commands/status.ts +0 -470
  125. package/src/commands/stop.test.ts +0 -84
  126. package/src/commands/stop.ts +0 -169
  127. package/src/commands/templates.test.ts +0 -58
  128. package/src/commands/templates.ts +0 -277
  129. package/src/index.ts +0 -166
  130. package/src/lib/admin-auth.ts +0 -157
  131. package/src/lib/admin-client.test.ts +0 -51
  132. package/src/lib/admin-client.ts +0 -326
  133. package/src/lib/admin-platform-routes.ts +0 -1017
  134. package/src/lib/admin-platform-stream.test.ts +0 -434
  135. package/src/lib/admin-platform-stream.ts +0 -788
  136. package/src/lib/admin-responses.ts +0 -211
  137. package/src/lib/admin-server.test.ts +0 -781
  138. package/src/lib/admin-server.ts +0 -797
  139. package/src/lib/auth-store.ts +0 -87
  140. package/src/lib/daemon-registry.test.ts +0 -429
  141. package/src/lib/daemon-registry.ts +0 -343
  142. package/src/lib/le/le-apply.test.ts +0 -591
  143. package/src/lib/le/le-apply.ts +0 -832
  144. package/src/lib/le/le-auth.ts +0 -89
  145. package/src/lib/le/le-cleanup.test.ts +0 -168
  146. package/src/lib/le/le-cleanup.ts +0 -399
  147. package/src/lib/le/le-cli-helpers.ts +0 -77
  148. package/src/lib/le/le-client.ts +0 -184
  149. package/src/lib/le/le-concurrency.test.ts +0 -157
  150. package/src/lib/le/le-concurrency.ts +0 -78
  151. package/src/lib/le/le-connection-test.ts +0 -42
  152. package/src/lib/le/le-devicehub-properties.test.ts +0 -53
  153. package/src/lib/le/le-devicehub-properties.ts +0 -33
  154. package/src/lib/le/le-devicehub.ts +0 -213
  155. package/src/lib/le/le-digital-twins.ts +0 -285
  156. package/src/lib/le/le-drivers.ts +0 -23
  157. package/src/lib/le/le-endpoint.ts +0 -40
  158. package/src/lib/le/le-environments.test.ts +0 -73
  159. package/src/lib/le/le-environments.ts +0 -156
  160. package/src/lib/le/le-markers.ts +0 -87
  161. package/src/lib/le/le-plan.test.ts +0 -26
  162. package/src/lib/le/le-plan.ts +0 -109
  163. package/src/lib/le/le-platform-operations.test.ts +0 -198
  164. package/src/lib/le/le-platform-operations.ts +0 -340
  165. package/src/lib/le/le-static-model-schema.ts +0 -78
  166. package/src/lib/le/le-static-planner.test.ts +0 -255
  167. package/src/lib/le/le-static-planner.ts +0 -442
  168. package/src/lib/le/le-template-exporter.test.ts +0 -527
  169. package/src/lib/le/le-template-exporter.ts +0 -459
  170. package/src/lib/le/le-value-types.ts +0 -23
  171. package/src/lib/mqtt-client.ts +0 -115
  172. package/src/lib/parse.test.ts +0 -123
  173. package/src/lib/parse.ts +0 -87
  174. package/src/lib/resolve-simulation.test.ts +0 -118
  175. package/src/lib/resolve-simulation.ts +0 -181
  176. package/src/lib/session-store.ts +0 -110
  177. package/src/lib/shutdown.test.ts +0 -562
  178. package/src/lib/shutdown.ts +0 -621
  179. package/src/lib/template-category.test.ts +0 -71
  180. package/src/lib/template-category.ts +0 -72
  181. package/src/lib/template-locator.ts +0 -55
  182. package/src/lib/template-utils.ts +0 -45
  183. package/src/lib/ws-hub.ts +0 -281
  184. package/src/logger.test.ts +0 -172
  185. package/src/logger.ts +0 -402
  186. package/src/output.test.ts +0 -64
  187. package/src/output.ts +0 -160
@@ -1,38 +0,0 @@
1
- import type { EngineCommand } from "@miravo/shared";
2
- import { CONTROL_CMD_TIMEOUT_MS } from "@miravo/shared";
3
- import type { Command } from "commander";
4
- import { executeAdminCommand, resolveAdminUrl } from "../lib/admin-client.ts";
5
- import { parseTimeoutSeconds } from "../lib/parse.ts";
6
- import { handleCommandError, handleCommandResult } from "../logger.ts";
7
-
8
- interface RemoveOptions {
9
- readonly json?: boolean;
10
- readonly adminUrl?: string;
11
- readonly timeout?: number;
12
- }
13
-
14
- export function registerRemoveCommand(program: Command): void {
15
- program
16
- .command("remove <instanceId>")
17
- .description("Remove an instance from the running simulation")
18
- .option("--json", "Machine-readable JSON output")
19
- .option("--admin-url <url>", "Admin API URL (env: MIRAVO_ADMIN_URL)")
20
- .option("--timeout <seconds>", "Timeout in seconds (default: 5)", parseTimeoutSeconds)
21
- .addHelpText(
22
- "after",
23
- `
24
- Examples:
25
- $ miravo remove pump-001 Remove instance by ID`,
26
- )
27
- .action(async (instanceId: string, options: RemoveOptions) => {
28
- try {
29
- const adminUrl = resolveAdminUrl(options.adminUrl);
30
- const timeoutMs = options.timeout ?? CONTROL_CMD_TIMEOUT_MS;
31
- const command: EngineCommand = { type: "removeInstance", instanceId };
32
- const result = await executeAdminCommand(adminUrl, command, timeoutMs);
33
- handleCommandResult(result, options.json);
34
- } catch (error) {
35
- handleCommandError(error);
36
- }
37
- });
38
- }
@@ -1,44 +0,0 @@
1
- import type { EngineCommand } from "@miravo/shared";
2
- import { CONTROL_CMD_TIMEOUT_MS } from "@miravo/shared";
3
- import type { Command } from "commander";
4
- import { executeAdminCommand, resolveAdminUrl } from "../lib/admin-client.ts";
5
- import { parseTimeoutSeconds } from "../lib/parse.ts";
6
- import { handleCommandError, handleCommandResult } from "../logger.ts";
7
-
8
- interface ResetOptions {
9
- readonly clearPersistence?: boolean;
10
- readonly json?: boolean;
11
- readonly adminUrl?: string;
12
- readonly timeout?: number;
13
- }
14
-
15
- export function registerResetCommand(program: Command): void {
16
- program
17
- .command("reset")
18
- .description("Reset simulation state: remove all instances and template runs")
19
- .option("--clear-persistence", "Also clear persisted state (start fresh on next boot)")
20
- .option("--json", "Machine-readable JSON output")
21
- .option("--admin-url <url>", "Admin API URL (env: MIRAVO_ADMIN_URL)")
22
- .option("--timeout <seconds>", "Timeout in seconds (default: 5)", parseTimeoutSeconds)
23
- .addHelpText(
24
- "after",
25
- `
26
- Examples:
27
- $ miravo reset Reset simulation state
28
- $ miravo reset --clear-persistence Reset and clear saved state`,
29
- )
30
- .action(async (options: ResetOptions) => {
31
- try {
32
- const adminUrl = resolveAdminUrl(options.adminUrl);
33
- const timeoutMs = options.timeout ?? CONTROL_CMD_TIMEOUT_MS;
34
- const command: EngineCommand = {
35
- type: "resetSimulation",
36
- clearPersistence: options.clearPersistence ?? false,
37
- };
38
- const result = await executeAdminCommand(adminUrl, command, timeoutMs);
39
- handleCommandResult(result, options.json);
40
- } catch (error) {
41
- handleCommandError(error);
42
- }
43
- });
44
- }
@@ -1,97 +0,0 @@
1
- import type { Command } from "commander";
2
- import { probeEntryIdentity, readEntry, removeEntry } from "../lib/daemon-registry.ts";
3
- import { handleCommandError, writeOutput } from "../logger.ts";
4
-
5
- interface RmOptions {
6
- readonly force?: boolean;
7
- readonly json?: boolean;
8
- }
9
-
10
- export function registerRmCommand(program: Command): void {
11
- program
12
- .command("rm <name>")
13
- .description("Remove a stale daemon registry entry")
14
- .option("-f, --force", "Kill the process if still alive before removing")
15
- .option("--json", "Machine-readable JSON output")
16
- .addHelpText(
17
- "after",
18
- `
19
- Examples:
20
- $ miravo rm my-sim Remove stale registry entry
21
- $ miravo rm my-sim --force Kill process and remove entry`,
22
- )
23
- .action(async (name: string, options: RmOptions) => {
24
- try {
25
- await rmAction(name, options);
26
- } catch (error) {
27
- handleCommandError(error);
28
- }
29
- });
30
- }
31
-
32
- function jsonOut(obj: Record<string, unknown>): void {
33
- console.log(JSON.stringify(obj, null, 2));
34
- }
35
-
36
- async function rmAction(name: string, options: RmOptions): Promise<void> {
37
- const entry = await readEntry(name);
38
- if (!entry) {
39
- const msg = `Simulation "${name}" not found in daemon registry.`;
40
- if (options.json) {
41
- jsonOut({ success: false, name, message: msg });
42
- } else {
43
- writeOutput(msg);
44
- }
45
- process.exitCode = 1;
46
- return;
47
- }
48
-
49
- const identity = await probeEntryIdentity(entry);
50
-
51
- if (identity.status === "unreachable") {
52
- const msg = `Simulation "${name}" could not be safely verified because its admin server is unreachable while PID ${entry.pid} is still alive.`;
53
- if (options.json) {
54
- jsonOut({
55
- success: false,
56
- name,
57
- message: msg,
58
- error: "Retry later, or inspect the process manually before removing the registry entry.",
59
- });
60
- } else {
61
- writeOutput(
62
- `${msg} Retry later, or inspect the process manually before removing the registry entry.`,
63
- );
64
- }
65
- process.exitCode = 1;
66
- return;
67
- }
68
-
69
- if (identity.status === "verified") {
70
- if (!options.force) {
71
- const msg = `Simulation "${name}" is still running (PID ${entry.pid}). Use --force to kill it, or 'miravo stop ${name}' for graceful shutdown.`;
72
- if (options.json) {
73
- jsonOut({ success: false, name, message: msg });
74
- } else {
75
- writeOutput(msg);
76
- }
77
- process.exitCode = 1;
78
- return;
79
- }
80
-
81
- // Force kill
82
- try {
83
- process.kill(entry.pid, "SIGKILL");
84
- } catch {
85
- // Ignore — may have exited between check and kill
86
- }
87
- await Bun.sleep(200);
88
- }
89
-
90
- await removeEntry(name);
91
- const msg = `Removed daemon registry entry for "${name}".`;
92
- if (options.json) {
93
- jsonOut({ success: true, name, message: msg });
94
- } else {
95
- writeOutput(msg);
96
- }
97
- }
@@ -1,45 +0,0 @@
1
- import type { EngineCommand } from "@miravo/shared";
2
- import { CONTROL_CMD_TIMEOUT_MS } from "@miravo/shared";
3
- import type { Command } from "commander";
4
- import { executeAdminCommand, resolveAdminUrl } from "../lib/admin-client.ts";
5
- import { parsePositiveInt, parseTimeoutSeconds } from "../lib/parse.ts";
6
- import { handleCommandError, handleCommandResult } from "../logger.ts";
7
-
8
- interface SpawnOptions {
9
- readonly count?: number;
10
- readonly json?: boolean;
11
- readonly adminUrl?: string;
12
- readonly timeout?: number;
13
- }
14
-
15
- export function registerSpawnCommand(program: Command): void {
16
- program
17
- .command("spawn <model>")
18
- .description("Spawn new instances of a model into the running simulation")
19
- .option("-c, --count <n>", "Number of instances to spawn (default: 1)", parsePositiveInt)
20
- .option("--json", "Machine-readable JSON output")
21
- .option("--admin-url <url>", "Admin API URL (env: MIRAVO_ADMIN_URL)")
22
- .option("--timeout <seconds>", "Timeout in seconds (default: 5)", parseTimeoutSeconds)
23
- .addHelpText(
24
- "after",
25
- `
26
- Examples:
27
- $ miravo spawn centrifugal-pump Spawn one instance
28
- $ miravo spawn centrifugal-pump -c 5 Spawn five instances`,
29
- )
30
- .action(async (model: string, options: SpawnOptions) => {
31
- try {
32
- const adminUrl = resolveAdminUrl(options.adminUrl);
33
- const timeoutMs = options.timeout ?? CONTROL_CMD_TIMEOUT_MS;
34
- const command: EngineCommand = {
35
- type: "spawn",
36
- model,
37
- count: options.count ?? 1,
38
- };
39
- const result = await executeAdminCommand(adminUrl, command, timeoutMs);
40
- handleCommandResult(result, options.json);
41
- } catch (error) {
42
- handleCommandError(error);
43
- }
44
- });
45
- }
@@ -1,40 +0,0 @@
1
- import type { EngineCommand } from "@miravo/shared";
2
- import { CONTROL_CMD_TIMEOUT_MS } from "@miravo/shared";
3
- import type { Command } from "commander";
4
- import { executeAdminCommand, resolveAdminUrl } from "../lib/admin-client.ts";
5
- import { parsePositiveFloat, parseTimeoutSeconds } from "../lib/parse.ts";
6
- import { handleCommandError, handleCommandResult } from "../logger.ts";
7
-
8
- interface SpeedOptions {
9
- readonly json?: boolean;
10
- readonly adminUrl?: string;
11
- readonly timeout?: number;
12
- }
13
-
14
- export function registerSpeedCommand(program: Command): void {
15
- program
16
- .command("speed <multiplier>")
17
- .description("Set simulation time multiplier (1.0 = real-time)")
18
- .option("--json", "Machine-readable JSON output")
19
- .option("--admin-url <url>", "Admin API URL (env: MIRAVO_ADMIN_URL)")
20
- .option("--timeout <seconds>", "Timeout in seconds (default: 5)", parseTimeoutSeconds)
21
- .addHelpText(
22
- "after",
23
- `
24
- Examples:
25
- $ miravo speed 10 Run at 10x real time
26
- $ miravo speed 0.5 Run at half speed`,
27
- )
28
- .action(async (multiplierStr: string, options: SpeedOptions) => {
29
- try {
30
- const multiplier = parsePositiveFloat(multiplierStr);
31
- const adminUrl = resolveAdminUrl(options.adminUrl);
32
- const timeoutMs = options.timeout ?? CONTROL_CMD_TIMEOUT_MS;
33
- const command: EngineCommand = { type: "setSpeed", multiplier };
34
- const result = await executeAdminCommand(adminUrl, command, timeoutMs);
35
- handleCommandResult(result, options.json);
36
- } catch (error) {
37
- handleCommandError(error);
38
- }
39
- });
40
- }
@@ -1,157 +0,0 @@
1
- import type { MiravoTemplate, ProtocolConfig } from "@miravo/shared";
2
- import {
3
- ConfigValidationError,
4
- DEFAULT_MODBUS_PORT,
5
- DEFAULT_MQTT_PORT,
6
- DEFAULT_OPCUA_PORT,
7
- } from "@miravo/shared";
8
- import { displayHost } from "../../lib/daemon-registry.ts";
9
- import type { MqttMode, MqttRunOverrides } from "./types.ts";
10
-
11
- // ---------------------------------------------------------------------------
12
- // Step 5: Adapter default config builders
13
- // ---------------------------------------------------------------------------
14
-
15
- export function buildMQTTDefaultConfig(
16
- template: MiravoTemplate,
17
- portOverride: number | undefined,
18
- mqttOverrides: MqttRunOverrides,
19
- simulationName: string,
20
- ): Record<string, unknown> {
21
- const mqttConfig = template.protocols?.mqtt;
22
- const port = portOverride ?? mqttConfig?.port ?? DEFAULT_MQTT_PORT;
23
- let embedded = mqttConfig?.embedded ?? true;
24
- let external = mqttConfig?.external;
25
- let host = "0.0.0.0";
26
- let username = mqttConfig?.username;
27
- let password = mqttConfig?.password;
28
- const modeOverride = mqttOverrides.mode;
29
- if (modeOverride !== undefined) {
30
- embedded = modeOverride === "embedded";
31
- if (embedded) {
32
- external = undefined;
33
- username = undefined;
34
- password = undefined;
35
- }
36
- }
37
- if (mqttOverrides.url !== undefined) external = mqttOverrides.url;
38
- if (mqttOverrides.host !== undefined) host = mqttOverrides.host;
39
- if (mqttOverrides.username !== undefined) username = mqttOverrides.username;
40
- if (mqttOverrides.password !== undefined) password = mqttOverrides.password;
41
-
42
- if (embedded && (external || username || password)) {
43
- throw new ConfigValidationError({
44
- message: "Embedded MQTT mode cannot be combined with external MQTT URL or credentials.",
45
- fix: "Use --mqtt-mode external for external broker settings, or remove external MQTT overrides.",
46
- });
47
- }
48
- if (!embedded && !external) {
49
- throw new ConfigValidationError({
50
- message: "MQTT mode is external but no broker URL was provided.",
51
- fix: "Set protocols.mqtt.external in template or pass --mqtt-url / MIRAVO_MQTT_URL.",
52
- });
53
- }
54
- if (password && !username) {
55
- throw new ConfigValidationError({
56
- message: "MQTT password was provided without username.",
57
- fix: "Provide --mqtt-username / MIRAVO_MQTT_USERNAME when setting a password.",
58
- });
59
- }
60
-
61
- const drainBudget = mqttOverrides.drainBudget ?? mqttConfig?.drain_budget;
62
- const batchSize = mqttOverrides.batchSize ?? mqttConfig?.batch_size;
63
- const brokerConcurrency = mqttOverrides.brokerConcurrency ?? mqttConfig?.broker_concurrency;
64
-
65
- return {
66
- name: "mqtt",
67
- enabled: true,
68
- simulationName,
69
- embedded,
70
- port,
71
- host,
72
- retain: mqttConfig?.retain ?? false,
73
- ...(external !== undefined ? { external } : {}),
74
- ...(username !== undefined ? { username } : {}),
75
- ...(password !== undefined ? { password } : {}),
76
- ...(mqttConfig?.reconnect !== undefined ? { reconnect: mqttConfig.reconnect } : {}),
77
- ...(mqttConfig?.rbe !== undefined ? { rbe: mqttConfig.rbe } : {}),
78
- ...(mqttConfig?.metadata !== undefined ? { metadata: mqttConfig.metadata } : {}),
79
- ...(drainBudget !== undefined ? { drain_budget: drainBudget } : {}),
80
- ...(batchSize !== undefined ? { batch_size: batchSize } : {}),
81
- ...(brokerConcurrency !== undefined ? { broker_concurrency: brokerConcurrency } : {}),
82
- };
83
- }
84
-
85
- export function buildOPCUADefaultConfig(
86
- template: MiravoTemplate,
87
- portOverride: number | undefined,
88
- hostOverride: string | undefined,
89
- simulationName: string,
90
- writeBudgetOverride?: number,
91
- ): Record<string, unknown> {
92
- const opcuaConfig = template.protocols?.opcua;
93
- const port = portOverride ?? opcuaConfig?.port ?? DEFAULT_OPCUA_PORT;
94
- const host = hostOverride ?? opcuaConfig?.host ?? "0.0.0.0";
95
- const writeBudget = writeBudgetOverride ?? opcuaConfig?.write_budget;
96
-
97
- return {
98
- name: "opcua",
99
- enabled: true,
100
- port,
101
- host,
102
- security: opcuaConfig?.security ?? "none",
103
- simulationName,
104
- ...(opcuaConfig?.buildInfo ? { buildInfo: opcuaConfig.buildInfo } : {}),
105
- ...(writeBudget !== undefined ? { write_budget: writeBudget } : {}),
106
- };
107
- }
108
-
109
- export function buildModbusDefaultConfig(
110
- template: MiravoTemplate,
111
- portOverride: number | undefined,
112
- hostOverride: string | undefined,
113
- ): Record<string, unknown> {
114
- const modbusConfig = template.protocols?.modbus;
115
- const port = portOverride ?? modbusConfig?.port ?? DEFAULT_MODBUS_PORT;
116
- const host = hostOverride ?? modbusConfig?.host ?? "0.0.0.0";
117
- return { name: "modbus", enabled: true, port, host };
118
- }
119
-
120
- /** Derive the MQTT endpoint string from a resolved adapter config. */
121
- export function buildMQTTEndpoint(config: ProtocolConfig): string {
122
- const cfg = config as Record<string, unknown>;
123
- const port = cfg["port"] ?? DEFAULT_MQTT_PORT;
124
- const embedded = cfg["embedded"] ?? true;
125
- const external = cfg["external"] as string | undefined;
126
- return embedded ? `mqtt://localhost:${port}` : (external as string);
127
- }
128
-
129
- /** Derive the OPC UA endpoint string from a resolved adapter config. */
130
- export function buildOPCUAEndpoint(config: ProtocolConfig): string {
131
- const cfg = config as Record<string, unknown>;
132
- const port = cfg["port"] ?? DEFAULT_OPCUA_PORT;
133
- const host = cfg["host"] ?? "0.0.0.0";
134
- const resolvedDisplayHost = host === "0.0.0.0" ? "localhost" : host;
135
- return `opc.tcp://${resolvedDisplayHost}:${port}`;
136
- }
137
-
138
- export function buildModbusEndpoint(config: Record<string, unknown>): string {
139
- const port = (config as { port?: number }).port ?? DEFAULT_MODBUS_PORT;
140
- const host = (config as { host?: string }).host ?? "0.0.0.0";
141
- return `modbus://${displayHost(String(host))}:${port}`;
142
- }
143
-
144
- export function parseMqttModeOption(raw: string | undefined): MqttMode | undefined {
145
- if (raw === undefined) {
146
- return undefined;
147
- }
148
-
149
- if (raw === "embedded" || raw === "external") {
150
- return raw;
151
- }
152
-
153
- throw new ConfigValidationError({
154
- message: `Invalid MQTT mode "${raw}".`,
155
- fix: "Use --mqtt-mode embedded|external or MIRAVO_MQTT_MODE=embedded|external.",
156
- });
157
- }
@@ -1,50 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import type { ModelSchema } from "@miravo/shared";
3
- import { buildLeDtJsonFromSchema } from "./catalog-le-dt.ts";
4
-
5
- function parseExport(json: string): {
6
- staticAttributes: { key: string; value: string }[];
7
- } {
8
- const payload = JSON.parse(json) as [{ staticAttributes: { key: string; value: string }[] }];
9
- return payload[0];
10
- }
11
-
12
- const modelSchema: ModelSchema = {
13
- name: "pump",
14
- description: "Pump model",
15
- parameters: {},
16
- members: {
17
- serial: {
18
- kind: "property",
19
- dataType: "String",
20
- access: "read",
21
- unit: null,
22
- euRange: null,
23
- default: "P-001",
24
- },
25
- rpm: {
26
- kind: "variable",
27
- dataType: "Float",
28
- access: "read",
29
- unit: null,
30
- euRange: null,
31
- default: null,
32
- },
33
- },
34
- methods: {},
35
- faults: [],
36
- lifecycle: null,
37
- };
38
-
39
- describe("catalog LE DT export", () => {
40
- test("includes Miravo marker placeholders in single-model export", () => {
41
- const exported = parseExport(buildLeDtJsonFromSchema(modelSchema));
42
- const markerValues = new Map(exported.staticAttributes.map((attr) => [attr.key, attr.value]));
43
-
44
- expect(markerValues.get("miravo_managed")).toBe("false");
45
- expect(markerValues.get("miravo_template")).toBe("__miravo_placeholder__");
46
- expect(markerValues.get("miravo_model")).toBe("__miravo_placeholder__");
47
- expect(markerValues.get("miravo_instance")).toBe("__miravo_placeholder__");
48
- expect(markerValues.get("serial")).toBe("P-001");
49
- });
50
- });
@@ -1,187 +0,0 @@
1
- /**
2
- * Generates Litmus Edge Digital Twin import JSON from Miravo models.
3
- *
4
- * The output matches the format produced by LE's `ExportModel` GraphQL mutation
5
- * and can be imported directly via `ImportModel` or the LE UI.
6
- *
7
- * Model export mapping (`buildLeDtJsonFromSchema`):
8
- * - kind: property → LE static attributes (placeholder values)
9
- * - kind: variable (r/rw)→ LE dynamic attributes via topicExpression devicehub.alias.{device}.<member>
10
- * - LE parameters → wiring placeholders only ({device})
11
- * - Methods/faults/lifecycle are not exported
12
- */
13
-
14
- import type { ModelSchema } from "@miravo/shared";
15
-
16
- /** Maps Miravo dataType → LE DynamicAttributeType enum values. */
17
- const LE_DATA_TYPE: Readonly<Record<string, string>> = {
18
- Double: "number",
19
- Float: "number",
20
- Int32: "number",
21
- UInt16: "number",
22
- UInt32: "number",
23
- Boolean: "boolean",
24
- String: "string",
25
- };
26
-
27
- const ZERO = "00000000-0000-0000-0000-000000000000";
28
- const MIRAVO_MARKER_PLACEHOLDER = "__miravo_placeholder__";
29
-
30
- interface StaticAttr {
31
- readonly id: string;
32
- readonly parentId: string;
33
- readonly modelId: string;
34
- readonly instanceId: string;
35
- readonly key: string;
36
- readonly value: string;
37
- readonly type: number;
38
- }
39
-
40
- function createStaticAttr(modelId: string, key: string, value: string): StaticAttr {
41
- return {
42
- id: crypto.randomUUID(),
43
- parentId: ZERO,
44
- modelId,
45
- instanceId: ZERO,
46
- key,
47
- value,
48
- type: 0,
49
- };
50
- }
51
-
52
- function buildMiravoMarkerPlaceholders(modelId: string): StaticAttr[] {
53
- return [
54
- createStaticAttr(modelId, "miravo_managed", "false"),
55
- createStaticAttr(modelId, "miravo_template", MIRAVO_MARKER_PLACEHOLDER),
56
- createStaticAttr(modelId, "miravo_model", MIRAVO_MARKER_PLACEHOLDER),
57
- createStaticAttr(modelId, "miravo_instance", MIRAVO_MARKER_PLACEHOLDER),
58
- createStaticAttr(modelId, "uns_path", MIRAVO_MARKER_PLACEHOLDER),
59
- ];
60
- }
61
-
62
- export function buildLeDtJsonFromSchema(schema: ModelSchema): string {
63
- const modelId = crypto.randomUUID();
64
-
65
- // Static attributes: Miravo marker placeholders + property members.
66
- // LE materializes instance-owned static attrs from model attrs present at
67
- // instance creation time, so these placeholders must exist on the model.
68
- const staticAttributes = [
69
- ...buildMiravoMarkerPlaceholders(modelId),
70
- ...Object.entries(schema.members)
71
- .filter(([, m]) => m.kind === "property")
72
- .map(([key, m]) =>
73
- createStaticAttr(modelId, key, m.default != null ? String(m.default) : "<>"),
74
- ),
75
- ];
76
-
77
- // Dynamic attributes: readable variable members
78
- const dynamicAttributes = Object.entries(schema.members)
79
- .filter(([, m]) => m.kind === "variable")
80
- .map(([name, m]) => ({
81
- id: crypto.randomUUID(),
82
- modelId,
83
- parentId: ZERO,
84
- instanceId: ZERO,
85
- topic: "",
86
- name,
87
- unit: m.unit ?? "",
88
- dataType: LE_DATA_TYPE[m.dataType] ?? "number",
89
- schemaId: null,
90
- skipParentKey: false,
91
- topicExpression: `devicehub.alias.{device}.${name}`,
92
- }));
93
-
94
- // Hierarchy: "info" folder → static attrs, "measurements" folder → dynamic attrs
95
- const hierarchyNodes = buildHierarchy(modelId, staticAttributes, dynamicAttributes);
96
-
97
- return JSON.stringify(
98
- [
99
- {
100
- id: modelId,
101
- assets: "ASSET",
102
- name: schema.name.toUpperCase(),
103
- description: schema.description ?? "",
104
- image: null,
105
- dynamicAttributes,
106
- staticAttributes,
107
- hierarchyNodes,
108
- transformationBackup: null,
109
- parameters: [{ name: "device", value: "miravo" }],
110
- isUNS: false,
111
- connectorName: "",
112
- version: 0,
113
- },
114
- ],
115
- null,
116
- 2,
117
- );
118
- }
119
-
120
- function buildHierarchy(
121
- modelId: string,
122
- staticAttrs: ReadonlyArray<{ id: string; key: string }>,
123
- dynamicAttrs: ReadonlyArray<{ id: string; name: string }>,
124
- ): Array<Record<string, unknown>> {
125
- const nodes: Array<Record<string, unknown>> = [];
126
- let rootPos = 0;
127
-
128
- if (staticAttrs.length > 0) {
129
- const folderId = crypto.randomUUID();
130
- nodes.push(hFolder(folderId, modelId, rootPos++, "info"));
131
- let pos = 0;
132
- for (const a of staticAttrs) {
133
- nodes.push(hLeaf(modelId, pos++, folderId, a.key, a.id, "static"));
134
- }
135
- }
136
-
137
- if (dynamicAttrs.length > 0) {
138
- const folderId = crypto.randomUUID();
139
- nodes.push(hFolder(folderId, modelId, rootPos, "measurements"));
140
- let pos = 0;
141
- for (const a of dynamicAttrs) {
142
- nodes.push(hLeaf(modelId, pos++, folderId, a.name, a.id, "dynamic"));
143
- }
144
- }
145
-
146
- return nodes;
147
- }
148
-
149
- function hFolder(
150
- id: string,
151
- modelId: string,
152
- position: number,
153
- name: string,
154
- ): Record<string, unknown> {
155
- return {
156
- id,
157
- modelId,
158
- position,
159
- parentId: null,
160
- name,
161
- isFolder: true,
162
- attributeId: null,
163
- attributeType: null,
164
- nodeType: "folder",
165
- };
166
- }
167
-
168
- function hLeaf(
169
- modelId: string,
170
- position: number,
171
- parentId: string,
172
- name: string,
173
- attributeId: string,
174
- attributeType: string,
175
- ): Record<string, unknown> {
176
- return {
177
- id: crypto.randomUUID(),
178
- modelId,
179
- position,
180
- parentId,
181
- name,
182
- isFolder: false,
183
- attributeId,
184
- attributeType,
185
- nodeType: "attribute",
186
- };
187
- }