miravo 0.6.0 → 0.6.1
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/LICENSE +199 -0
- package/README.md +7 -70
- package/bin/miravo.js +84 -0
- package/package.json +20 -36
- package/console-dist/assets/activity-DsWeRq4h.js +0 -1
- package/console-dist/assets/api-4AqKvsm9.js +0 -1
- package/console-dist/assets/arrow-right-Ea_UhTNi.js +0 -1
- package/console-dist/assets/arrow-up-down-DFSjzzl2.js +0 -1
- package/console-dist/assets/blueprint-card-classes-DBFCDy0M.js +0 -1
- package/console-dist/assets/box-C1WaoOMF.js +0 -1
- package/console-dist/assets/card-BjeP32PN.js +0 -1
- package/console-dist/assets/catalog-BJzyzbLR.js +0 -1
- package/console-dist/assets/catalog-DKjEm-c4.js +0 -1
- package/console-dist/assets/catalog-template-icons-BrUeHaCY.js +0 -1
- package/console-dist/assets/checkbox-DZlJXTE4.js +0 -1
- package/console-dist/assets/chunk-OE4NN4TA-CEjpfhS6.js +0 -3
- package/console-dist/assets/combobox-BGnuCYxO.js +0 -1
- package/console-dist/assets/control-CPhvwx9o.js +0 -1
- package/console-dist/assets/control-Cg2up9NA.js +0 -1
- package/console-dist/assets/copy-button-WkbNkMcd.js +0 -1
- package/console-dist/assets/createLucideIcon-Bwxw859K.js +0 -1
- package/console-dist/assets/deploy-tab-JeWDwPoZ.js +0 -1
- package/console-dist/assets/dist-BhbDML0a.js +0 -1
- package/console-dist/assets/dist-Cy8XTNFT.js +0 -2
- package/console-dist/assets/download-DQ6NH16U.js +0 -1
- package/console-dist/assets/engine-store-CrLnz692.js +0 -1
- package/console-dist/assets/events-CXm29Ok4.js +0 -1
- package/console-dist/assets/events-D01IxhnP.js +0 -1
- package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
- package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
- package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
- package/console-dist/assets/fleet-CfxbKXt9.js +0 -1
- package/console-dist/assets/fleet-lotMGVqe.js +0 -1
- package/console-dist/assets/format-kXTonpDJ.js +0 -1
- package/console-dist/assets/gauge-Ci8zNZlk.js +0 -1
- package/console-dist/assets/get-error-message-3Q5Q-2G5.js +0 -1
- package/console-dist/assets/index-CVPKSDvJ.js +0 -12
- package/console-dist/assets/index-DJkgDeun.css +0 -2
- package/console-dist/assets/input-BWEQFptx.js +0 -1
- package/console-dist/assets/instance-detail-C5IVwuh3.js +0 -1
- package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
- package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
- package/console-dist/assets/label-uephX5ZG.js +0 -1
- package/console-dist/assets/layers-CUsLQ1fq.js +0 -1
- package/console-dist/assets/login-C3RJli0m.js +0 -126
- package/console-dist/assets/models-B_UioWQ5.js +0 -1
- package/console-dist/assets/models-_IPlPiZ7.js +0 -1
- package/console-dist/assets/not-found-B8gAm08S.js +0 -1
- package/console-dist/assets/platform-targets-R3T-4eJ7.js +0 -1
- package/console-dist/assets/platforms-BWh0BQ3Y.js +0 -1
- package/console-dist/assets/platforms-DStNbtVc.js +0 -1
- package/console-dist/assets/proxy-CsnYqnPp.js +0 -9
- package/console-dist/assets/query-keys-BvtE7BMs.js +0 -1
- package/console-dist/assets/radio-CoHegOv5.js +0 -1
- package/console-dist/assets/rolldown-runtime-COnpUsM8.js +0 -1
- package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
- package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
- package/console-dist/assets/select-CeO4vP0G.js +0 -1
- package/console-dist/assets/select-display-items-CIzY8953.js +0 -1
- package/console-dist/assets/server-D9Kfn8fQ.js +0 -1
- package/console-dist/assets/shallow-6lGjQhtJ.js +0 -1
- package/console-dist/assets/tags-CiXOIHtY.js +0 -4
- package/console-dist/assets/tags-FQCXIxrP.js +0 -1
- package/console-dist/assets/tooltip-HGLF366Q.js +0 -1
- package/console-dist/assets/transition-nav-link-Df4Rk6vV.js +0 -1
- package/console-dist/assets/transition-types-CPlGtM8w.js +0 -1
- package/console-dist/assets/triangle-alert-adNk_sea.js +0 -1
- package/console-dist/assets/use-platform-stream-DGbK1k2L.js +0 -2
- package/console-dist/assets/use-transition-navigate-CCpcWXB0.js +0 -2
- package/console-dist/assets/validation-Blvud9_O.js +0 -1
- package/console-dist/assets/vendor-charts-CAZdaPlq.js +0 -60
- package/console-dist/assets/vendor-table-wd8JuMmT.js +0 -4
- package/console-dist/assets/vendor-ui-ByahnaY0.js +0 -1
- package/console-dist/favicon.svg +0 -37
- package/console-dist/index.html +0 -82
- package/console-dist/miravo-logo.png +0 -0
- package/src/__tests__/integration/executable-methods-runner.ts +0 -262
- package/src/__tests__/integration/executable-methods.integration.ts +0 -2348
- package/src/__tests__/integration/opcua-concurrent-clients.integration.ts +0 -314
- package/src/commands/adapter.ts +0 -249
- package/src/commands/apply.test.ts +0 -62
- package/src/commands/apply.ts +0 -216
- package/src/commands/cleanup.test.ts +0 -34
- package/src/commands/cleanup.ts +0 -201
- package/src/commands/cli-test-setup.ts +0 -91
- package/src/commands/export.test.ts +0 -51
- package/src/commands/export.ts +0 -404
- package/src/commands/fork.ts +0 -219
- package/src/commands/inject.test.ts +0 -57
- package/src/commands/inject.ts +0 -212
- package/src/commands/load.ts +0 -167
- package/src/commands/logs.ts +0 -173
- package/src/commands/models.test.ts +0 -162
- package/src/commands/models.ts +0 -338
- package/src/commands/pause.ts +0 -67
- package/src/commands/platforms.test.ts +0 -186
- package/src/commands/platforms.ts +0 -371
- package/src/commands/ps.ts +0 -116
- package/src/commands/remove.ts +0 -38
- package/src/commands/reset.ts +0 -44
- package/src/commands/rm.ts +0 -97
- package/src/commands/spawn.ts +0 -45
- package/src/commands/speed.ts +0 -40
- package/src/commands/start/adapter-config.ts +0 -157
- package/src/commands/start/catalog-le-dt.test.ts +0 -50
- package/src/commands/start/catalog-le-dt.ts +0 -187
- package/src/commands/start/catalog-model-schema.ts +0 -89
- package/src/commands/start/catalog-template-detail.ts +0 -54
- package/src/commands/start/daemon.ts +0 -255
- package/src/commands/start/display.ts +0 -150
- package/src/commands/start/engine-setup.ts +0 -247
- package/src/commands/start/events.ts +0 -126
- package/src/commands/start/index.ts +0 -897
- package/src/commands/start/protocol-plans.ts +0 -239
- package/src/commands/start/template-loader.ts +0 -197
- package/src/commands/start/template-resolver.ts +0 -185
- package/src/commands/start/types.ts +0 -73
- package/src/commands/start.test.ts +0 -480
- package/src/commands/status.test.ts +0 -20
- package/src/commands/status.ts +0 -470
- package/src/commands/stop.test.ts +0 -84
- package/src/commands/stop.ts +0 -169
- package/src/commands/templates.test.ts +0 -58
- package/src/commands/templates.ts +0 -277
- package/src/index.ts +0 -166
- package/src/lib/admin-auth.ts +0 -157
- package/src/lib/admin-client.test.ts +0 -51
- package/src/lib/admin-client.ts +0 -326
- package/src/lib/admin-platform-routes.ts +0 -1017
- package/src/lib/admin-platform-stream.test.ts +0 -434
- package/src/lib/admin-platform-stream.ts +0 -788
- package/src/lib/admin-responses.ts +0 -211
- package/src/lib/admin-server.test.ts +0 -781
- package/src/lib/admin-server.ts +0 -797
- package/src/lib/auth-store.ts +0 -87
- package/src/lib/daemon-registry.test.ts +0 -429
- package/src/lib/daemon-registry.ts +0 -343
- package/src/lib/le/le-apply.test.ts +0 -591
- package/src/lib/le/le-apply.ts +0 -832
- package/src/lib/le/le-auth.ts +0 -89
- package/src/lib/le/le-cleanup.test.ts +0 -168
- package/src/lib/le/le-cleanup.ts +0 -399
- package/src/lib/le/le-cli-helpers.ts +0 -77
- package/src/lib/le/le-client.ts +0 -184
- package/src/lib/le/le-concurrency.test.ts +0 -157
- package/src/lib/le/le-concurrency.ts +0 -78
- package/src/lib/le/le-connection-test.ts +0 -42
- package/src/lib/le/le-devicehub-properties.test.ts +0 -53
- package/src/lib/le/le-devicehub-properties.ts +0 -33
- package/src/lib/le/le-devicehub.ts +0 -213
- package/src/lib/le/le-digital-twins.ts +0 -285
- package/src/lib/le/le-drivers.ts +0 -23
- package/src/lib/le/le-endpoint.ts +0 -40
- package/src/lib/le/le-environments.test.ts +0 -73
- package/src/lib/le/le-environments.ts +0 -156
- package/src/lib/le/le-markers.ts +0 -87
- package/src/lib/le/le-plan.test.ts +0 -26
- package/src/lib/le/le-plan.ts +0 -109
- package/src/lib/le/le-platform-operations.test.ts +0 -198
- package/src/lib/le/le-platform-operations.ts +0 -340
- package/src/lib/le/le-static-model-schema.ts +0 -78
- package/src/lib/le/le-static-planner.test.ts +0 -255
- package/src/lib/le/le-static-planner.ts +0 -442
- package/src/lib/le/le-template-exporter.test.ts +0 -527
- package/src/lib/le/le-template-exporter.ts +0 -459
- package/src/lib/le/le-value-types.ts +0 -23
- package/src/lib/mqtt-client.ts +0 -115
- package/src/lib/parse.test.ts +0 -123
- package/src/lib/parse.ts +0 -87
- package/src/lib/resolve-simulation.test.ts +0 -118
- package/src/lib/resolve-simulation.ts +0 -181
- package/src/lib/session-store.ts +0 -110
- package/src/lib/shutdown.test.ts +0 -562
- package/src/lib/shutdown.ts +0 -621
- package/src/lib/template-category.test.ts +0 -71
- package/src/lib/template-category.ts +0 -72
- package/src/lib/template-locator.ts +0 -55
- package/src/lib/template-utils.ts +0 -45
- package/src/lib/ws-hub.ts +0 -281
- package/src/logger.test.ts +0 -172
- package/src/logger.ts +0 -402
- package/src/output.test.ts +0 -64
- package/src/output.ts +0 -160
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
|
-
import type { ContentCatalog, Engine } from "@miravo/core";
|
|
3
|
-
import {
|
|
4
|
-
createEngine,
|
|
5
|
-
loadTemplate,
|
|
6
|
-
loadTwinModel,
|
|
7
|
-
resolveModelPath,
|
|
8
|
-
validateFullTemplate,
|
|
9
|
-
} from "@miravo/core";
|
|
10
|
-
import type { MiravoTemplate, TwinModel } from "@miravo/shared";
|
|
11
|
-
import {
|
|
12
|
-
ConfigValidationError,
|
|
13
|
-
DEFAULT_TICK_INTERVAL_MS,
|
|
14
|
-
DEFAULT_TIME_MULTIPLIER,
|
|
15
|
-
SimulationNameSchema,
|
|
16
|
-
} from "@miravo/shared";
|
|
17
|
-
import {
|
|
18
|
-
buildUNSBasePath,
|
|
19
|
-
collectReferencedModels,
|
|
20
|
-
forEachTwin,
|
|
21
|
-
} from "../../lib/template-utils.ts";
|
|
22
|
-
import { configLog } from "../../logger.ts";
|
|
23
|
-
import {
|
|
24
|
-
getCommandOptionSource,
|
|
25
|
-
reconcileProtocolBootPlan,
|
|
26
|
-
resolveProtocolBootPlans,
|
|
27
|
-
} from "./protocol-plans.ts";
|
|
28
|
-
import { loadTemplateModels, registerModelFromSearchPaths } from "./template-loader.ts";
|
|
29
|
-
import type { StartOptions } from "./types.ts";
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Step 2: Load and validate
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
export async function loadAndValidateTemplate(
|
|
36
|
-
templatePath: string,
|
|
37
|
-
options: StartOptions,
|
|
38
|
-
catalog: ContentCatalog,
|
|
39
|
-
): Promise<{
|
|
40
|
-
template: MiravoTemplate;
|
|
41
|
-
models: Map<string, TwinModel>;
|
|
42
|
-
seed: number;
|
|
43
|
-
seedExplicit: boolean;
|
|
44
|
-
}> {
|
|
45
|
-
const template = await loadTemplate(templatePath);
|
|
46
|
-
|
|
47
|
-
// Build model search paths from catalog
|
|
48
|
-
const catalogSearchPaths = catalog.getTemplateValidationSearchPaths(templatePath);
|
|
49
|
-
const searchPaths = [...catalogSearchPaths];
|
|
50
|
-
if (options.modelsDir) {
|
|
51
|
-
// Insert after template dir but before other paths
|
|
52
|
-
searchPaths.splice(1, 0, resolve(options.modelsDir));
|
|
53
|
-
}
|
|
54
|
-
const uniqueSearchPaths = [...new Set(searchPaths.map((path) => resolve(path)))];
|
|
55
|
-
|
|
56
|
-
// Cross-validate
|
|
57
|
-
const validation = await validateFullTemplate(templatePath, uniqueSearchPaths);
|
|
58
|
-
for (const issue of validation.issues) {
|
|
59
|
-
if (issue.severity === "error") {
|
|
60
|
-
configLog.error(`${issue.message}\n Fix: ${issue.fix}`);
|
|
61
|
-
} else {
|
|
62
|
-
configLog.warn(issue.message);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (!validation.valid) {
|
|
66
|
-
throw new ConfigValidationError({
|
|
67
|
-
message: "Template validation failed. See errors above.",
|
|
68
|
-
fix: "Fix the reported issues and try again.",
|
|
69
|
-
filePath: templatePath,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Build inline models map
|
|
74
|
-
const models = new Map<string, TwinModel>();
|
|
75
|
-
if (template.models) {
|
|
76
|
-
for (const inlineModel of template.models) {
|
|
77
|
-
models.set(inlineModel.model.name, inlineModel);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Resolve and load all referenced models not already inline
|
|
82
|
-
const referencedModelNames = collectReferencedModels(template);
|
|
83
|
-
|
|
84
|
-
for (const modelName of referencedModelNames) {
|
|
85
|
-
if (models.has(modelName)) continue;
|
|
86
|
-
const modelPath = await resolveModelPath(modelName, uniqueSearchPaths);
|
|
87
|
-
const model = await loadTwinModel(modelPath);
|
|
88
|
-
models.set(modelName, model);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Resolve seed: CLI flag > config setting > random
|
|
92
|
-
const configSeed = template.settings?.seed;
|
|
93
|
-
const cliSeed = options.seed;
|
|
94
|
-
const seedExplicit = cliSeed !== undefined || configSeed !== undefined;
|
|
95
|
-
const seed = cliSeed ?? configSeed ?? Date.now();
|
|
96
|
-
|
|
97
|
-
return { template, models, seed, seedExplicit };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function resolveSimulationName(input: {
|
|
101
|
-
readonly template: MiravoTemplate;
|
|
102
|
-
readonly cliName?: string;
|
|
103
|
-
readonly envName?: string;
|
|
104
|
-
}): string {
|
|
105
|
-
const { template, cliName, envName } = input;
|
|
106
|
-
|
|
107
|
-
const nameFromNamespace = (): string | undefined => {
|
|
108
|
-
const namespace = template.namespace;
|
|
109
|
-
if (!namespace) return undefined;
|
|
110
|
-
if ("enterprise" in namespace) {
|
|
111
|
-
return `${namespace.enterprise}-${namespace.site}`.toLowerCase();
|
|
112
|
-
}
|
|
113
|
-
return namespace.prefix.toLowerCase();
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const rawName = cliName ?? envName ?? nameFromNamespace();
|
|
117
|
-
if (!rawName) {
|
|
118
|
-
throw new ConfigValidationError({
|
|
119
|
-
message: "Template has no namespace.",
|
|
120
|
-
fix: "Provide a simulation name via --name or MIRAVO_NAME.",
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const parsed = SimulationNameSchema.safeParse(rawName);
|
|
125
|
-
if (!parsed.success) {
|
|
126
|
-
throw new ConfigValidationError({
|
|
127
|
-
message: `Invalid simulation name "${rawName}".`,
|
|
128
|
-
fix: "Use lowercase letters, numbers, and hyphens only (for example: north-plant).",
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return parsed.data;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
// Step 4: Engine creation
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
|
|
139
|
-
export function createAndPopulateEngine(
|
|
140
|
-
template: MiravoTemplate,
|
|
141
|
-
models: Map<string, TwinModel>,
|
|
142
|
-
catalog: ContentCatalog,
|
|
143
|
-
seed: number,
|
|
144
|
-
options: StartOptions,
|
|
145
|
-
templatePath: string,
|
|
146
|
-
simulationName: string,
|
|
147
|
-
): Engine {
|
|
148
|
-
const tickIntervalMs =
|
|
149
|
-
options.tick ?? template.settings?.tick_interval_ms ?? DEFAULT_TICK_INTERVAL_MS;
|
|
150
|
-
const timeMultiplier =
|
|
151
|
-
options.speed ?? template.settings?.time_multiplier ?? DEFAULT_TIME_MULTIPLIER;
|
|
152
|
-
const overrunThresholdMultiplier = template.settings?.overrun_threshold_multiplier;
|
|
153
|
-
const searchPaths = catalog.getTemplateValidationSearchPaths(templatePath);
|
|
154
|
-
|
|
155
|
-
let engine!: Engine;
|
|
156
|
-
engine = createEngine({
|
|
157
|
-
simulationName,
|
|
158
|
-
seed,
|
|
159
|
-
tickIntervalMs,
|
|
160
|
-
timeMultiplier,
|
|
161
|
-
overrunThresholdMultiplier,
|
|
162
|
-
templateMetadata: {
|
|
163
|
-
name: template.name,
|
|
164
|
-
path: templatePath,
|
|
165
|
-
},
|
|
166
|
-
beforeRun: async ({ restoredFromPersistence, persistedDisabledAdapters }) => {
|
|
167
|
-
const protocolBootPlans = resolveProtocolBootPlans({
|
|
168
|
-
options,
|
|
169
|
-
optionSources: {
|
|
170
|
-
mqtt: getCommandOptionSource(options.command, "mqtt"),
|
|
171
|
-
opcua: getCommandOptionSource(options.command, "opcua"),
|
|
172
|
-
mqttPort: getCommandOptionSource(options.command, "mqttPort"),
|
|
173
|
-
mqttHost: getCommandOptionSource(options.command, "mqttHost"),
|
|
174
|
-
mqttMode: getCommandOptionSource(options.command, "mqttMode"),
|
|
175
|
-
mqttUrl: getCommandOptionSource(options.command, "mqttUrl"),
|
|
176
|
-
mqttUsername: getCommandOptionSource(options.command, "mqttUsername"),
|
|
177
|
-
mqttPassword: getCommandOptionSource(options.command, "mqttPassword"),
|
|
178
|
-
opcuaPort: getCommandOptionSource(options.command, "opcuaPort"),
|
|
179
|
-
opcuaHost: getCommandOptionSource(options.command, "opcuaHost"),
|
|
180
|
-
modbus: getCommandOptionSource(options.command, "modbus"),
|
|
181
|
-
modbusPort: getCommandOptionSource(options.command, "modbusPort"),
|
|
182
|
-
modbusHost: getCommandOptionSource(options.command, "modbusHost"),
|
|
183
|
-
},
|
|
184
|
-
restoredFromPersistence,
|
|
185
|
-
persistedDisabledAdapters,
|
|
186
|
-
templateProtocols: template.protocols,
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
for (const plan of protocolBootPlans) {
|
|
190
|
-
await reconcileProtocolBootPlan(engine, plan);
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
persistence: {
|
|
194
|
-
enabled: true,
|
|
195
|
-
resetOnStart: options.resetState ?? false,
|
|
196
|
-
ensureModelRegistered: (modelName) =>
|
|
197
|
-
registerModelFromSearchPaths(engine.runtime, modelName, searchPaths),
|
|
198
|
-
loadTemplateModels: (persistedTemplatePath) => {
|
|
199
|
-
loadTemplateModels(catalog, persistedTemplatePath, engine.runtime);
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// Register template-referenced models
|
|
205
|
-
for (const [, model] of models) {
|
|
206
|
-
engine.runtime.registerModel(model);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Register all builtin models so they're available for ad-hoc spawning
|
|
210
|
-
const builtinDir = catalog.getBuiltinModelsDir();
|
|
211
|
-
for (const path of new Bun.Glob("*.twin.yaml").scanSync({ cwd: builtinDir })) {
|
|
212
|
-
const name = path.replace(".twin.yaml", "");
|
|
213
|
-
if (!engine.runtime.getModel(name)) {
|
|
214
|
-
registerModelFromSearchPaths(engine.runtime, name, searchPaths);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Spawn instances from template with boot-time run ID
|
|
219
|
-
const bootRunId = 1;
|
|
220
|
-
const allInstanceIds: string[] = [];
|
|
221
|
-
|
|
222
|
-
forEachTwin(template, (area, line, twin) => {
|
|
223
|
-
const unsBasePath = buildUNSBasePath(template, area.name, line.name);
|
|
224
|
-
const spawned = engine.runtime.spawn(twin.model, twin.count, {
|
|
225
|
-
variation: twin.variation,
|
|
226
|
-
unsBasePath,
|
|
227
|
-
...(twin.parameters !== undefined ? { parameterOverrides: twin.parameters } : {}),
|
|
228
|
-
...(twin.starting_hours !== undefined ? { startingHours: twin.starting_hours } : {}),
|
|
229
|
-
templateRunId: bootRunId,
|
|
230
|
-
});
|
|
231
|
-
for (const node of spawned) {
|
|
232
|
-
allInstanceIds.push(node.id);
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Register boot-time template as a template run for persistence/unload support
|
|
237
|
-
engine.registerTemplateRun({
|
|
238
|
-
templateRunId: bootRunId,
|
|
239
|
-
templateName: template.name,
|
|
240
|
-
templatePath,
|
|
241
|
-
prefix: "boot",
|
|
242
|
-
instanceIds: allInstanceIds,
|
|
243
|
-
models: [...models.keys()],
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
return engine;
|
|
247
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import type { Engine } from "@miravo/core";
|
|
2
|
-
import { colors } from "consola/utils";
|
|
3
|
-
import { printSummaryLine } from "../../lib/shutdown.ts";
|
|
4
|
-
import { engineLog, faultLog, lifecycleLog } from "../../logger.ts";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Shorten a full instance ID path to the last two segments for terminal output.
|
|
8
|
-
* E.g. "area-1/line-2/pump-003" → "line-2/pump-003".
|
|
9
|
-
* Full IDs can be 40+ chars with deep area/line/twin hierarchies.
|
|
10
|
-
*/
|
|
11
|
-
function shortId(instanceId: string): string {
|
|
12
|
-
const parts = instanceId.split("/");
|
|
13
|
-
return parts.length > 2 ? parts.slice(-2).join("/") : instanceId;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Step 7: Event subscriptions + periodic summary
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
const MAX_ERROR_CONTEXT_KEYS = 256;
|
|
21
|
-
const ERROR_CONTEXT_OVERFLOW = "__overflow__";
|
|
22
|
-
|
|
23
|
-
export function subscribeToEvents(
|
|
24
|
-
engine: Engine,
|
|
25
|
-
errorContextCounts: Map<string, number>,
|
|
26
|
-
): () => void {
|
|
27
|
-
const onLifecycle = (event: {
|
|
28
|
-
instanceId: string;
|
|
29
|
-
from: string;
|
|
30
|
-
to: string;
|
|
31
|
-
operatingHours: number;
|
|
32
|
-
}): void => {
|
|
33
|
-
lifecycleLog.info(
|
|
34
|
-
`${shortId(event.instanceId)} ${event.from} ${colors.dim("\u2192")} ${event.to} ${colors.gray(`(${event.operatingHours.toFixed(0)}h)`)}`,
|
|
35
|
-
);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const onFaultTriggered = (event: { instanceId: string; fault: string }): void => {
|
|
39
|
-
faultLog.warn(`${shortId(event.instanceId)} ${colors.bold(event.fault)} triggered`);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const onFaultCleared = (event: { instanceId: string; fault: string }): void => {
|
|
43
|
-
faultLog.info(`${shortId(event.instanceId)} ${colors.dim(event.fault)} cleared`);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const onWarn = (event: { message: string; context: string }): void => {
|
|
47
|
-
engineLog.debug(event.message, { context: event.context });
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const onReset = (event: {
|
|
51
|
-
instanceCount: number;
|
|
52
|
-
templateRunCount: number;
|
|
53
|
-
clearPersistence: boolean;
|
|
54
|
-
}): void => {
|
|
55
|
-
const suffix = event.clearPersistence ? " · saved state cleared" : "";
|
|
56
|
-
engineLog.info(
|
|
57
|
-
`simulation reset · ${colors.bold(String(event.instanceCount))} instances · ${colors.bold(String(event.templateRunCount))} template runs${suffix}`,
|
|
58
|
-
);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const onError = (event: { context: string; error: Error }): void => {
|
|
62
|
-
incrementErrorContextCount(errorContextCounts, event.context);
|
|
63
|
-
engineLog.error(event.error.message, { context: event.context }, event.error);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const onStateChanged = (event: {
|
|
67
|
-
from: "idle" | "running" | "paused" | "stopped";
|
|
68
|
-
to: "idle" | "running" | "paused" | "stopped";
|
|
69
|
-
}): void => {
|
|
70
|
-
if (event.to === "paused") {
|
|
71
|
-
engineLog.info(`simulation paused`);
|
|
72
|
-
} else if (event.to === "running" && event.from === "paused") {
|
|
73
|
-
engineLog.info(`simulation resumed`);
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
engine.bus.on("lifecycle:changed", onLifecycle);
|
|
78
|
-
engine.bus.on("fault:triggered", onFaultTriggered);
|
|
79
|
-
engine.bus.on("fault:cleared", onFaultCleared);
|
|
80
|
-
engine.bus.on("engine:warn", onWarn);
|
|
81
|
-
engine.bus.on("engine:error", onError);
|
|
82
|
-
engine.bus.on("engine:reset", onReset);
|
|
83
|
-
engine.bus.on("engine:state-changed", onStateChanged);
|
|
84
|
-
|
|
85
|
-
return () => {
|
|
86
|
-
engine.bus.off("lifecycle:changed", onLifecycle);
|
|
87
|
-
engine.bus.off("fault:triggered", onFaultTriggered);
|
|
88
|
-
engine.bus.off("fault:cleared", onFaultCleared);
|
|
89
|
-
engine.bus.off("engine:warn", onWarn);
|
|
90
|
-
engine.bus.off("engine:error", onError);
|
|
91
|
-
engine.bus.off("engine:reset", onReset);
|
|
92
|
-
engine.bus.off("engine:state-changed", onStateChanged);
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function normalizeErrorContext(raw: string): string {
|
|
97
|
-
return raw
|
|
98
|
-
.replace(/tick=\d+/g, "tick=*")
|
|
99
|
-
.replace(/instance="[^"]+"/g, 'instance="*"')
|
|
100
|
-
.replace(/model="[^"]+"/g, 'model="*"');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function incrementErrorContextCount(counts: Map<string, number>, rawContext: string): void {
|
|
104
|
-
const normalizedContext = normalizeErrorContext(rawContext);
|
|
105
|
-
if (counts.has(normalizedContext)) {
|
|
106
|
-
counts.set(normalizedContext, (counts.get(normalizedContext) ?? 0) + 1);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (counts.size < MAX_ERROR_CONTEXT_KEYS) {
|
|
111
|
-
counts.set(normalizedContext, 1);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
counts.set(ERROR_CONTEXT_OVERFLOW, (counts.get(ERROR_CONTEXT_OVERFLOW) ?? 0) + 1);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function startPeriodicSummary(
|
|
119
|
-
engine: Engine,
|
|
120
|
-
intervalSeconds: number,
|
|
121
|
-
): ReturnType<typeof setInterval> {
|
|
122
|
-
return setInterval(() => {
|
|
123
|
-
const metrics = engine.getMetrics();
|
|
124
|
-
printSummaryLine(metrics);
|
|
125
|
-
}, intervalSeconds * 1_000);
|
|
126
|
-
}
|