@attraccess/plugin-wago 0.1.0-nightly.1005.1 → 0.1.0-nightly.1009.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/dist/index.js CHANGED
@@ -48140,12 +48140,12 @@ var require_auditTime = __commonJS({
48140
48140
  var async_1 = require_async();
48141
48141
  var audit_1 = require_audit();
48142
48142
  var timer_1 = require_timer();
48143
- function auditTime(duration, scheduler) {
48143
+ function auditTime(duration2, scheduler) {
48144
48144
  if (scheduler === void 0) {
48145
48145
  scheduler = async_1.asyncScheduler;
48146
48146
  }
48147
48147
  return audit_1.audit(function() {
48148
- return timer_1.timer(duration, scheduler);
48148
+ return timer_1.timer(duration2, scheduler);
48149
48149
  });
48150
48150
  }
48151
48151
  exports2.auditTime = auditTime;
@@ -49118,9 +49118,9 @@ var require_delay = __commonJS({
49118
49118
  if (scheduler === void 0) {
49119
49119
  scheduler = async_1.asyncScheduler;
49120
49120
  }
49121
- var duration = timer_1.timer(due, scheduler);
49121
+ var duration2 = timer_1.timer(due, scheduler);
49122
49122
  return delayWhen_1.delayWhen(function() {
49123
- return duration;
49123
+ return duration2;
49124
49124
  });
49125
49125
  }
49126
49126
  exports2.delay = delay;
@@ -49546,13 +49546,13 @@ var require_groupBy = __commonJS({
49546
49546
  var Subject_1 = require_Subject();
49547
49547
  var lift_1 = require_lift();
49548
49548
  var OperatorSubscriber_1 = require_OperatorSubscriber();
49549
- function groupBy(keySelector, elementOrOptions, duration, connector) {
49549
+ function groupBy(keySelector, elementOrOptions, duration2, connector) {
49550
49550
  return lift_1.operate(function(source, subscriber) {
49551
49551
  var element;
49552
49552
  if (!elementOrOptions || typeof elementOrOptions === "function") {
49553
49553
  element = elementOrOptions;
49554
49554
  } else {
49555
- duration = elementOrOptions.duration, element = elementOrOptions.element, connector = elementOrOptions.connector;
49555
+ duration2 = elementOrOptions.duration, element = elementOrOptions.element, connector = elementOrOptions.connector;
49556
49556
  }
49557
49557
  var groups = /* @__PURE__ */ new Map();
49558
49558
  var notify = function(cb) {
@@ -49574,14 +49574,14 @@ var require_groupBy = __commonJS({
49574
49574
  groups.set(key_1, group_1 = connector ? connector() : new Subject_1.Subject());
49575
49575
  var grouped = createGroupedObservable(key_1, group_1);
49576
49576
  subscriber.next(grouped);
49577
- if (duration) {
49577
+ if (duration2) {
49578
49578
  var durationSubscriber_1 = OperatorSubscriber_1.createOperatorSubscriber(group_1, function() {
49579
49579
  group_1.complete();
49580
49580
  durationSubscriber_1 === null || durationSubscriber_1 === void 0 ? void 0 : durationSubscriber_1.unsubscribe();
49581
49581
  }, void 0, void 0, function() {
49582
49582
  return groups.delete(key_1);
49583
49583
  });
49584
- groupBySourceSubscriber.add(innerFrom_1.innerFrom(duration(grouped)).subscribe(durationSubscriber_1));
49584
+ groupBySourceSubscriber.add(innerFrom_1.innerFrom(duration2(grouped)).subscribe(durationSubscriber_1));
49585
49585
  }
49586
49586
  }
49587
49587
  group_1.next(element ? element(value) : value);
@@ -51102,11 +51102,11 @@ var require_throttleTime = __commonJS({
51102
51102
  var async_1 = require_async();
51103
51103
  var throttle_1 = require_throttle();
51104
51104
  var timer_1 = require_timer();
51105
- function throttleTime(duration, scheduler, config) {
51105
+ function throttleTime(duration2, scheduler, config) {
51106
51106
  if (scheduler === void 0) {
51107
51107
  scheduler = async_1.asyncScheduler;
51108
51108
  }
51109
- var duration$ = timer_1.timer(duration, scheduler);
51109
+ var duration$ = timer_1.timer(duration2, scheduler);
51110
51110
  return throttle_1.throttle(function() {
51111
51111
  return duration$;
51112
51112
  }, config);
@@ -52496,8 +52496,8 @@ function supportsOutputAction(channel, action) {
52496
52496
  }
52497
52497
  function pulseBehaviorError(capabilities, pulse) {
52498
52498
  if (!capabilities.includes("pulse") && pulse === void 0) return void 0;
52499
- const duration = pulse && typeof pulse === "object" && !Array.isArray(pulse) ? pulse.durationMs : void 0;
52500
- if (!capabilities.includes("output") || !capabilities.includes("pulse") || typeof duration !== "number" || !Number.isSafeInteger(duration) || duration <= 0)
52499
+ const duration2 = pulse && typeof pulse === "object" && !Array.isArray(pulse) ? pulse.durationMs : void 0;
52500
+ if (!capabilities.includes("output") || !capabilities.includes("pulse") || typeof duration2 !== "number" || !Number.isSafeInteger(duration2) || duration2 <= 0)
52501
52501
  return "Pulsed behavior requires output and pulse capabilities and a positive integer durationMs.";
52502
52502
  return void 0;
52503
52503
  }
@@ -52952,7 +52952,13 @@ function validateModbus(value) {
52952
52952
  });
52953
52953
  });
52954
52954
  config.devices.forEach((d, i) => {
52955
- keys(d, ["id", "name", "connectionId", "unitId", "profileId", "profileVersion"], `modbus.devices[${i}]`);
52955
+ keys(
52956
+ d,
52957
+ ["id", "name", "connectionId", "unitId", "profileId", "profileVersion", "pollIntervalMs"],
52958
+ `modbus.devices[${i}]`
52959
+ );
52960
+ if (d.pollIntervalMs !== void 0 && !integer(d.pollIntervalMs, 100, 36e5))
52961
+ fail(`modbus.devices[${i}].pollIntervalMs`, "device polling interval must be 100..3600000 ms");
52956
52962
  if (!name(d.name) || !integer(d.unitId, 1, 247) || !config.connections.some((c) => c.id === d.connectionId) || !findProfile(config, d))
52957
52963
  fail(`modbus.devices[${i}]`, "device requires name, unit 1..247, existing connection and exact profile version");
52958
52964
  });
@@ -53146,6 +53152,7 @@ function validateSnapshot(snapshot) {
53146
53152
  "physicalPointId",
53147
53153
  "profile",
53148
53154
  "capabilities",
53155
+ "invert",
53149
53156
  "disconnectPolicy",
53150
53157
  "range",
53151
53158
  "pulse",
@@ -53159,6 +53166,12 @@ function validateSnapshot(snapshot) {
53159
53166
  errors.push(referenceError(`${path}.physicalPointId`, "physical point", channel.physicalPointId));
53160
53167
  enumValue(channel.profile, `${path}.profile`, CHANNEL_PROFILES, errors);
53161
53168
  const capabilities = capabilityList(channel.capabilities, `${path}.capabilities`, errors);
53169
+ if (channel.invert !== void 0 && (typeof channel.invert !== "boolean" || !capabilities.has("input")))
53170
+ errors.push({
53171
+ path: `${path}.invert`,
53172
+ code: "invalid_invert",
53173
+ message: "invert requires a boolean and an input channel"
53174
+ });
53162
53175
  validateDisconnectPolicy(channel.disconnectPolicy, `${path}.disconnectPolicy`, errors);
53163
53176
  validateRange(channel.range, `${path}.range`, capabilities, errors);
53164
53177
  validatePulse(channel.pulse, `${path}.pulse`, capabilities, errors);
@@ -53210,7 +53223,7 @@ function exactKeys(value, path, allowed, errors, optional = []) {
53210
53223
  })
53211
53224
  );
53212
53225
  allowed.filter(
53213
- (key) => !["range", "pulse", "guard", "feedback", "measurement", ...optional].includes(key) && !(key in value)
53226
+ (key) => !["range", "pulse", "guard", "feedback", "measurement", "invert", ...optional].includes(key) && !(key in value)
53214
53227
  ).forEach(
53215
53228
  (key) => errors.push({
53216
53229
  path: path === "$" ? key : `${path}.${key}`,
@@ -53418,13 +53431,18 @@ function wagoAuditDetails(input) {
53418
53431
  for (const key of ["revision", "sourceRevision"]) {
53419
53432
  if (positiveInteger(input[key])) details[key] = input[key];
53420
53433
  }
53421
- if (typeof input.profileId === "string" && input.profileId.length <= 160 && input.profileId.trim()) details.profileId = input.profileId;
53422
- if (positiveInteger(input.profileVersion) && input.profileVersion <= 1e6) details.profileVersion = input.profileVersion;
53434
+ if (typeof input.profileId === "string" && input.profileId.length <= 160 && input.profileId.trim())
53435
+ details.profileId = input.profileId;
53436
+ if (positiveInteger(input.profileVersion) && input.profileVersion <= 1e6)
53437
+ details.profileVersion = input.profileVersion;
53423
53438
  if (WAGO_PRESETS.some((preset) => preset.id === input.presetId)) details.presetId = input.presetId;
53424
- if (typeof input.channelId === "string" && /^[a-zA-Z0-9_-]{1,64}$/.test(input.channelId)) details.channelId = input.channelId;
53425
- if (typeof input.commandId === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(input.commandId)) details.commandId = input.commandId;
53426
- if (["set", "pulse"].includes(input.operation)) details.operation = input.operation;
53427
- if (["dispatched", "acknowledged", "rejected", "timeout", "transport_failure"].includes(input.result)) details.result = input.result;
53439
+ if (typeof input.channelId === "string" && /^[a-zA-Z0-9_-]{1,64}$/.test(input.channelId))
53440
+ details.channelId = input.channelId;
53441
+ if (typeof input.commandId === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(input.commandId))
53442
+ details.commandId = input.commandId;
53443
+ if (["set", "pulse", "release"].includes(input.operation)) details.operation = input.operation;
53444
+ if (["dispatched", "acknowledged", "rejected", "timeout", "transport_failure"].includes(input.result))
53445
+ details.result = input.result;
53428
53446
  for (const side of ["before", "after"]) {
53429
53447
  for (const key of ["physicalPointCount", "logicalChannelCount"]) {
53430
53448
  const count = input[side]?.[key];
@@ -53452,12 +53470,15 @@ var WagoAudit = class {
53452
53470
  }
53453
53471
  /** For owners with asynchronous dispatch/ack lifecycles. Finish exactly once after the true outcome. */
53454
53472
  begin(principal, controllerId, action, details = {}) {
53455
- if (!positiveInteger(controllerId) || !WAGO_AUDIT_ACTIONS.includes(action)) throw new import_common2.BadRequestException("Invalid WAGO audit subject or action");
53456
- const actor = wagoAuditPrincipal({ user: {
53457
- id: principal.userId,
53458
- authenticationMethod: principal.authenticationMethod,
53459
- apiTokenId: principal.apiTokenId
53460
- } });
53473
+ if (!positiveInteger(controllerId) || !WAGO_AUDIT_ACTIONS.includes(action))
53474
+ throw new import_common2.BadRequestException("Invalid WAGO audit subject or action");
53475
+ const actor = wagoAuditPrincipal({
53476
+ user: {
53477
+ id: principal.userId,
53478
+ authenticationMethod: principal.authenticationMethod,
53479
+ apiTokenId: principal.apiTokenId
53480
+ }
53481
+ });
53461
53482
  const operationId = (0, import_node_crypto3.randomUUID)();
53462
53483
  const initial = wagoAuditDetails(details);
53463
53484
  let attempted;
@@ -53911,6 +53932,66 @@ WagoConfigurationRevision = __decorateClass([
53911
53932
  // backend/wago-command-handler.ts
53912
53933
  var import_node_crypto4 = require("node:crypto");
53913
53934
  var import_plugins_backend_sdk7 = require("@attraccess/plugins-backend-sdk");
53935
+
53936
+ // backend/wago-flow-preview.ts
53937
+ var WAGO_FLOW_PREVIEW_SCHEMA = { dynamic: true, type: "object", properties: {}, preview: [] };
53938
+ function wagoFlowPreview(config, kind, controller, snapshot, names) {
53939
+ const english = localizedPreview(config, kind, controller, snapshot, names, false);
53940
+ const german = localizedPreview(config, kind, controller, snapshot, names, true);
53941
+ return english.map((row, index) => ({ ...row, translations: { de: german[index] } }));
53942
+ }
53943
+ function localizedPreview(config, kind, controller, snapshot, names, german) {
53944
+ const text = (english, translated) => german ? translated : english;
53945
+ const channel = snapshot?.logicalChannels.find((item) => item.id === config.channelId);
53946
+ const point = snapshot?.physicalPoints?.find((item) => item.id === channel?.physicalPointId);
53947
+ let channelName = channel ? typeof names[channel.id] === "string" ? String(names[channel.id]) : channel.id : typeof config.channelId === "string" ? `${config.channelId} (${text("unavailable", "nicht verf\xFCgbar")})` : text("Not selected", "Nicht ausgew\xE4hlt");
53948
+ if (point?.modbus) {
53949
+ const device = snapshot?.modbus?.devices.find((item) => item.id === point.modbus?.deviceId);
53950
+ if (device) channelName = `${channelName} \xB7 ${device.name}`;
53951
+ } else if (point?.hardwareProfile === "751-9301") {
53952
+ const terminal = DIGITAL_TERMINALS.find((item) => item.channel === point.channel)?.label;
53953
+ if (terminal && !channelName.includes(terminal)) channelName += ` \xB7 ${terminal}`;
53954
+ }
53955
+ const rows = [
53956
+ {
53957
+ label: text("Device", "Ger\xE4t"),
53958
+ value: controller?.name || controller?.hardwareId || (typeof config.controllerId === "number" ? `Controller ${config.controllerId} (${text("unavailable", "nicht verf\xFCgbar")})` : text("Not selected", "Nicht ausgew\xE4hlt"))
53959
+ },
53960
+ { label: text("Channel", "Kanal"), value: channelName }
53961
+ ];
53962
+ if (kind === "command") {
53963
+ const action = config.action === "set" ? config.value === true ? text("Turn ON", "Einschalten") : config.value === false ? text("Turn OFF", "Ausschalten") : text("Select ON/OFF", "EIN/AUS ausw\xE4hlen") : config.action === "pulse" ? `${text("Pulse", "Impuls")}${channel?.pulse ? ` \xB7 ${duration(channel.pulse.durationMs)}` : ""}` : text("Not selected", "Nicht ausgew\xE4hlt");
53964
+ return [...rows, { label: text("Action", "Aktion"), value: action }];
53965
+ }
53966
+ const category = config.category === "measurement" ? text("Measurement", "Messwert") : config.category === "fault" ? text("Fault", "Fehler") : config.category === "state" ? channel?.capabilities.includes("input") ? text("Input state", "Eingangszustand") : text("Output state", "Ausgangszustand") : void 0;
53967
+ if (kind === "event")
53968
+ return [
53969
+ ...rows,
53970
+ {
53971
+ label: text("When", "Wenn"),
53972
+ value: category ? `${category} ${text("received", "empfangen")}` : text("Not selected", "Nicht ausgew\xE4hlt")
53973
+ }
53974
+ ];
53975
+ if (kind === "read")
53976
+ return [...rows, { label: text("Read", "Lesen"), value: category ?? text("Not selected", "Nicht ausgew\xE4hlt") }];
53977
+ const expected = typeof config.equals === "boolean" ? config.equals ? text("ON", "EIN") : text("OFF", "AUS") : typeof config.equals === "number" && Number.isFinite(config.equals) ? `${config.equals} (${text("wire value", "Rohwert")})` : text("Not set", "Nicht festgelegt");
53978
+ return [
53979
+ ...rows,
53980
+ {
53981
+ label: text("Wait for", "Warten auf"),
53982
+ value: `${category ?? text("Not selected", "Nicht ausgew\xE4hlt")} = ${expected}`
53983
+ },
53984
+ {
53985
+ label: text("Timeout", "Zeitlimit"),
53986
+ value: duration(typeof config.timeoutMs === "number" ? config.timeoutMs : 3e4)
53987
+ }
53988
+ ];
53989
+ }
53990
+ function duration(milliseconds) {
53991
+ return milliseconds < 1e3 ? `${milliseconds} ms` : `${milliseconds / 1e3} s`;
53992
+ }
53993
+
53994
+ // backend/wago-command-handler.ts
53914
53995
  var DEFAULT_COMMAND_TIMEOUT_SECONDS = 30;
53915
53996
  var MAX_COMMAND_TIMEOUT_SECONDS = 300;
53916
53997
  var WagoCommandError = class extends Error {
@@ -53925,10 +54006,10 @@ var WagoCommandHandler = class {
53925
54006
  __publicField(this, "dependencies", dependencies);
53926
54007
  __publicField(this, "pending", /* @__PURE__ */ new Map());
53927
54008
  }
53928
- async channelNames(controllerId, revision2) {
54009
+ async channelNames(controllerId, revision2, appliedOnly = false) {
53929
54010
  let names = {};
53930
54011
  if (controllerId) {
53931
- const draft = await this.dependencies.context.getRepository(WagoConfigurationDraft).findOneBy({ controllerId });
54012
+ const draft = appliedOnly ? null : await this.dependencies.context.getRepository(WagoConfigurationDraft).findOneBy({ controllerId });
53932
54013
  try {
53933
54014
  const storedNames = JSON.parse(revision2?.presetProvenance ?? draft?.presetProvenance ?? "null")?.editor?.names;
53934
54015
  if (storedNames && typeof storedNames === "object" && !Array.isArray(storedNames)) names = storedNames;
@@ -53937,14 +54018,30 @@ var WagoCommandHandler = class {
53937
54018
  }
53938
54019
  return names;
53939
54020
  }
53940
- async schema(config, resourceId) {
53941
- const controllers = await this.dependencies.controllers().find({ where: { trustState: "claimed" }, order: { name: "ASC" } });
54021
+ async schema(config, resourceId, previewOnly = false) {
53942
54022
  const controllerId = positiveInteger2(config.controllerId);
54023
+ const controllers = previewOnly && !controllerId ? [] : await this.dependencies.controllers().find({
54024
+ where: { trustState: "claimed", ...previewOnly ? { id: controllerId } : {} },
54025
+ order: { name: "ASC" }
54026
+ });
53943
54027
  const revision2 = controllerId ? await this.dependencies.appliedRevision(controllerId) : null;
53944
54028
  const snapshot = revision2 ? JSON.parse(revision2.snapshot) : null;
53945
54029
  const channelId2 = typeof config.channelId === "string" ? config.channelId : void 0;
53946
54030
  const outputChannels = snapshot?.logicalChannels.filter((item) => item.capabilities.includes("output")) ?? [];
53947
- const names = await this.channelNames(controllerId, revision2);
54031
+ const names = await this.channelNames(controllerId, revision2, previewOnly);
54032
+ if (previewOnly)
54033
+ return {
54034
+ dynamic: true,
54035
+ type: "object",
54036
+ properties: {},
54037
+ preview: wagoFlowPreview(
54038
+ config,
54039
+ "command",
54040
+ controllers.find((item) => item.id === controllerId),
54041
+ snapshot,
54042
+ names
54043
+ )
54044
+ };
53948
54045
  const channel = outputChannels.find((item) => item.id === channelId2);
53949
54046
  const references = channelId2 && controllerId ? await this.references(controllerId, channelId2, resourceId) : [];
53950
54047
  const properties = {
@@ -54024,6 +54121,13 @@ var WagoCommandHandler = class {
54024
54121
  return {
54025
54122
  dynamic: true,
54026
54123
  type: "object",
54124
+ preview: wagoFlowPreview(
54125
+ config,
54126
+ "command",
54127
+ controllers.find((item) => item.id === controllerId),
54128
+ snapshot,
54129
+ names
54130
+ ),
54027
54131
  properties,
54028
54132
  required: [
54029
54133
  .../* @__PURE__ */ new Set([
@@ -54036,8 +54140,8 @@ var WagoCommandHandler = class {
54036
54140
  ]
54037
54141
  };
54038
54142
  }
54039
- async validate(config, validationContext = /* @__PURE__ */ new Map()) {
54040
- const parsed = this.parse(config);
54143
+ async validate(config, validationContext = /* @__PURE__ */ new Map(), manual = false) {
54144
+ const parsed = this.parse(config, manual);
54041
54145
  if ("errors" in parsed) return parsed.errors;
54042
54146
  const { controllerId, channelId: channelId2, action, expectedConfigurationRevision } = parsed.value;
54043
54147
  const controller = await this.cached(
@@ -54065,7 +54169,7 @@ var WagoCommandHandler = class {
54065
54169
  if (!channel) return [{ field: "channelId", message: "The selected Logical Channel no longer exists." }];
54066
54170
  if (!channel.capabilities.includes("output"))
54067
54171
  return [{ field: "channelId", message: "The selected Logical Channel no longer supports output commands." }];
54068
- if (!supportsOutputAction(channel, action))
54172
+ if (action !== "release" && !supportsOutputAction(channel, action))
54069
54173
  return [
54070
54174
  {
54071
54175
  field: "action",
@@ -54074,11 +54178,11 @@ var WagoCommandHandler = class {
54074
54178
  ];
54075
54179
  return [];
54076
54180
  }
54077
- async execute(config, commandId = (0, import_node_crypto4.randomUUID)()) {
54078
- const errors = await this.validate(config);
54181
+ async execute(config, commandId = (0, import_node_crypto4.randomUUID)(), source) {
54182
+ const errors = await this.validate(config, /* @__PURE__ */ new Map(), source === "manual");
54079
54183
  if (errors.length)
54080
54184
  throw new WagoCommandError(errors.map((error) => error.message).join(" "), "controller-rejection");
54081
- const parsed = this.parse(config);
54185
+ const parsed = this.parse(config, source === "manual");
54082
54186
  if ("errors" in parsed)
54083
54187
  throw new WagoCommandError(parsed.errors.map((error) => error.message).join(" "), "controller-rejection");
54084
54188
  const {
@@ -54102,6 +54206,7 @@ var WagoCommandHandler = class {
54102
54206
  channelId: channelId2,
54103
54207
  action,
54104
54208
  ...action === "set" ? { value } : {},
54209
+ ...source ? { source } : {},
54105
54210
  expectedConfigurationRevision
54106
54211
  });
54107
54212
  const acknowledgement = completionBehavior === "acknowledged" ? this.waitForAcknowledgement(id, controllerId, acknowledgementTimeoutSeconds) : void 0;
@@ -54170,12 +54275,12 @@ var WagoCommandHandler = class {
54170
54275
  const nodes = await this.dependencies.context.dataSource.getRepository(import_plugins_backend_sdk7.ResourceFlowNode).createQueryBuilder("node").select(["node.id", "node.resourceId"]).where("node.type = :type", { type: "plugin.wago.command" }).andWhere("node.resourceId <> :resourceId", { resourceId }).andWhere("node.data ->> 'controllerId' = :controllerId", { controllerId }).andWhere("node.data ->> 'channelId' = :channelId", { channelId: channelId2 }).getMany();
54171
54276
  return nodes.map((node) => `resource ${node.resourceId} / node ${node.id}`);
54172
54277
  }
54173
- parse(config) {
54278
+ parse(config, manual = false) {
54174
54279
  const errors = [];
54175
54280
  const controllerId = positiveInteger2(config.controllerId);
54176
54281
  const expectedConfigurationRevision = positiveInteger2(config.expectedConfigurationRevision);
54177
54282
  const channelId2 = typeof config.channelId === "string" && config.channelId.trim() ? config.channelId : void 0;
54178
- const action = config.action === "set" || config.action === "pulse" ? config.action : void 0;
54283
+ const action = config.action === "set" || config.action === "pulse" || manual && config.action === "release" ? config.action : void 0;
54179
54284
  const completionBehavior = config.completionBehavior === "dispatch" ? "dispatch" : "acknowledged";
54180
54285
  const parsedTimeout = positiveInteger2(config.acknowledgementTimeoutSeconds);
54181
54286
  const acknowledgementTimeoutSeconds = parsedTimeout ?? DEFAULT_COMMAND_TIMEOUT_SECONDS;
@@ -54375,7 +54480,9 @@ var faultCodes = /* @__PURE__ */ new Set([
54375
54480
  "measurement_read_failed",
54376
54481
  "device_write_failed",
54377
54482
  "feedback_mismatch",
54378
- "feedback_read_failed"
54483
+ "feedback_read_failed",
54484
+ "modbus_read_failed",
54485
+ "digital_read_failed"
54379
54486
  ]);
54380
54487
  function identifier(value) {
54381
54488
  return typeof value === "string" && value.trim().length > 0 && value.length <= 256 && !Array.from(value).some((character) => character.charCodeAt(0) < 32);
@@ -54384,6 +54491,8 @@ function isObject(value) {
54384
54491
  return !!value && typeof value === "object" && !Array.isArray(value);
54385
54492
  }
54386
54493
  function validStatePayload(data, kind, canonical) {
54494
+ if (kind === "state" && data.manualOutputChannelIds !== void 0 && (!Array.isArray(data.manualOutputChannelIds) || data.manualOutputChannelIds.length > MAX_CHANNELS || !data.manualOutputChannelIds.every(identifier)))
54495
+ return false;
54387
54496
  if (canonical && kind === "state" && data.readiness !== void 0 && (!isObject(data.readiness) || data.readiness.hardwareAvailable !== void 0 && typeof data.readiness.hardwareAvailable !== "boolean"))
54388
54497
  return false;
54389
54498
  if (canonical && kind === "state" && !(data.contentHash === null || typeof data.contentHash === "string" && /^[0-9a-f]{64}$/i.test(data.contentHash)))
@@ -54556,6 +54665,7 @@ var WagoDiagnosticsStore = class {
54556
54665
  state.rejection = void 0;
54557
54666
  state.hardwareAvailable = void 0;
54558
54667
  state.stateSourceAt = void 0;
54668
+ state.manualOutputChannelIds = void 0;
54559
54669
  state.revision = void 0;
54560
54670
  state.contentHash = void 0;
54561
54671
  }
@@ -54586,6 +54696,7 @@ var WagoDiagnosticsStore = class {
54586
54696
  state.contentHash = contentHash;
54587
54697
  state.hardwareAvailable = hardwareAvailable;
54588
54698
  state.stateSourceAt = canonical ? data.timestamp : void 0;
54699
+ state.manualOutputChannelIds = canonical && Array.isArray(data.manualOutputChannelIds) ? data.manualOutputChannelIds : void 0;
54589
54700
  if (data.outputs && typeof data.outputs === "object" && !Array.isArray(data.outputs)) {
54590
54701
  for (const [channelId2, value] of Object.entries(data.outputs).slice(0, MAX_CHANNELS)) {
54591
54702
  if (identifier(channelId2) && typeof value === "boolean")
@@ -54597,6 +54708,9 @@ var WagoDiagnosticsStore = class {
54597
54708
  if (identifier(channelId2) && typeof value === "boolean")
54598
54709
  state.inputs[channelId2] = { kind: "input", value, ...metadata2 };
54599
54710
  }
54711
+ for (const id of [...Object.keys(state.outputs), ...Object.keys(state.inputs)]) {
54712
+ if (["modbus_read_failed", "digital_read_failed"].includes(state.faults[id]?.code)) delete state.faults[id];
54713
+ }
54600
54714
  }
54601
54715
  applyEvents(id, state, kind, data, canonical, metadata2) {
54602
54716
  const { receivedAt } = metadata2;
@@ -54708,8 +54822,8 @@ var WagoService = class {
54708
54822
  if (this.subscriptionRetryTimer) clearTimeout(this.subscriptionRetryTimer);
54709
54823
  this.commands.destroy();
54710
54824
  }
54711
- async commandSchema(config, resourceId) {
54712
- return this.commands.schema(config, resourceId);
54825
+ async commandSchema(config, resourceId, previewOnly = false) {
54826
+ return this.commands.schema(config, resourceId, previewOnly);
54713
54827
  }
54714
54828
  async validateCommandConfig(config, validationContext = /* @__PURE__ */ new Map()) {
54715
54829
  return this.commands.validate(config, validationContext);
@@ -54724,7 +54838,7 @@ var WagoService = class {
54724
54838
  if (typeof input.channelId !== "string" || !/^[A-Za-z0-9_-]{1,64}$/.test(input.channelId))
54725
54839
  throw new import_common3.BadRequestException("Invalid Logical Channel");
54726
54840
  const config = { ...input, controllerId, completionBehavior: "acknowledged" };
54727
- const errors = await this.commands.validate(config);
54841
+ const errors = await this.commands.validate(config, /* @__PURE__ */ new Map(), true);
54728
54842
  if (errors.length) throw new import_common3.BadRequestException("Manual command does not match the applied configuration");
54729
54843
  const commandId = (0, import_node_crypto5.randomUUID)();
54730
54844
  const details = { commandId, channelId: input.channelId, operation: input.action };
@@ -54732,7 +54846,7 @@ var WagoService = class {
54732
54846
  await lifecycle.attempt();
54733
54847
  let result;
54734
54848
  try {
54735
- await this.commands.execute(config, commandId);
54849
+ await this.commands.execute(config, commandId, "manual");
54736
54850
  result = "acknowledged";
54737
54851
  } catch (error) {
54738
54852
  result = error instanceof WagoCommandError ? error.kind === "controller-rejection" ? "rejected" : error.kind === "acknowledgement-timeout" ? "timeout" : "transport_failure" : "transport_failure";
@@ -56645,11 +56759,11 @@ async function commissionClock(execute, confirmInstall, report, now = Date.now,
56645
56759
  const start = monotonic();
56646
56760
  const observed = parseSample(await execute(CLOCK_INSPECTION_SCRIPT, CLOCK_LIMITS));
56647
56761
  const received = now();
56648
- const duration = monotonic() - start;
56649
- if (!validEpoch(Math.floor(received / 1e3)) || duration < 0 || duration > 4e3 || Math.abs(received - hostStarted - (monotonic() - started)) > 1e3)
56762
+ const duration2 = monotonic() - start;
56763
+ if (!validEpoch(Math.floor(received / 1e3)) || duration2 < 0 || duration2 > 4e3 || Math.abs(received - hostStarted - (monotonic() - started)) > 1e3)
56650
56764
  throw new Error("Application clock changed or controller clock observation expired.");
56651
- const host = received - duration / 2;
56652
- const uncertaintySeconds = Math.ceil(duration / 2e3) + 1;
56765
+ const host = received - duration2 / 2;
56766
+ const uncertaintySeconds = Math.ceil(duration2 / 2e3) + 1;
56653
56767
  const skew = observed.epoch - Math.floor(host / 1e3);
56654
56768
  if (Math.abs(skew) > MAX_SKEW_SECONDS)
56655
56769
  throw new Error("Controller clock skew exceeds the supported ten-year bound.");
@@ -62407,6 +62521,11 @@ var WagoDiagnosticsService = class {
62407
62521
  incompatible: !!controller.compatibilityError,
62408
62522
  ...runtimeStreamSummary(runtime),
62409
62523
  configuration: configurationSummary(draft, latest, applied, runtime, revisionMismatch),
62524
+ manualOutputChannelIds: connected && !revisionMismatch && freshness(runtime.stateSourceAt) === "fresh" ? (runtime.manualOutputChannelIds ?? []).filter(
62525
+ (id) => appliedSnapshot?.logicalChannels.some(
62526
+ (channel) => channel.id === id && channel.capabilities.includes("output")
62527
+ )
62528
+ ) : [],
62410
62529
  hardwareReadiness: "unknown",
62411
62530
  hardwareReadinessReason: "Reported hardware availability is shown when supplied; it does not prove physical I/O readiness. Applied configuration and cached output state are not physical proof.",
62412
62531
  channels: ((appliedSnapshot ?? snapshot)?.logicalChannels ?? []).slice(0, 256).map((channel) => {
@@ -62486,10 +62605,11 @@ function createWagoCommandNode(service) {
62486
62605
  type: WAGO_COMMAND_NODE_TYPE,
62487
62606
  label: "WAGO command",
62488
62607
  description: "Control a configured WAGO Logical Channel.",
62608
+ configSchema: WAGO_FLOW_PREVIEW_SCHEMA,
62489
62609
  inputs: ["input"],
62490
62610
  outputs: ["output", "failure"],
62491
62611
  isOutput: true,
62492
- resolveConfigSchema: (config, schemaContext) => service().commandSchema(config, schemaContext.resourceId),
62612
+ resolveConfigSchema: (config, schemaContext) => service().commandSchema(config, schemaContext.resourceId, schemaContext.purpose === "preview"),
62493
62613
  validateConfig: (config, validationContext) => service().validateCommandConfig(config, validationContext),
62494
62614
  getFailureBehavior: (config) => service().commandFailureBehavior(config),
62495
62615
  getFailureKind: (error) => service().commandFailureKind(error),
@@ -62511,19 +62631,21 @@ function createWagoStateNodes(service) {
62511
62631
  type: WAGO_EVENT_NODE_TYPE,
62512
62632
  label: "WAGO event received",
62513
62633
  description: "Start a flow when a channel reports state, a measurement, or a fault. Data is available in wago.",
62634
+ configSchema: WAGO_FLOW_PREVIEW_SCHEMA,
62514
62635
  isInput: true,
62515
62636
  inputs: [],
62516
62637
  outputs: ["output"],
62517
- resolveConfigSchema: (config) => service().resolveConfigSchema(config, "event"),
62638
+ resolveConfigSchema: (config, context) => service().resolveConfigSchema(config, "event", void 0, context.purpose === "preview"),
62518
62639
  validateConfig: (config, context) => service().validateConfig(config, "event", context)
62519
62640
  },
62520
62641
  {
62521
62642
  type: "plugin.wago.read-state",
62522
62643
  label: "WAGO read state",
62523
62644
  description: "Read the latest channel state or measurement into wago. Unavailable data uses the unavailable output.",
62645
+ configSchema: WAGO_FLOW_PREVIEW_SCHEMA,
62524
62646
  inputs: ["input"],
62525
62647
  outputs: ["output", "unavailable"],
62526
- resolveConfigSchema: (config) => service().resolveConfigSchema(config, "read"),
62648
+ resolveConfigSchema: (config, context) => service().resolveConfigSchema(config, "read", void 0, context.purpose === "preview"),
62527
62649
  validateConfig: (config, context) => service().validateConfig(config, "read", context),
62528
62650
  execute: async (node, input) => {
62529
62651
  const state = service().read(node.data);
@@ -62535,9 +62657,10 @@ function createWagoStateNodes(service) {
62535
62657
  type: "plugin.wago.wait-for-state",
62536
62658
  label: "WAGO wait for state",
62537
62659
  description: "Wait for an available channel value to match, then continue with the value in wago.",
62660
+ configSchema: WAGO_FLOW_PREVIEW_SCHEMA,
62538
62661
  inputs: ["input"],
62539
62662
  outputs: ["output", "timeout"],
62540
- resolveConfigSchema: (config) => service().resolveConfigSchema(config, "wait"),
62663
+ resolveConfigSchema: (config, context) => service().resolveConfigSchema(config, "wait", void 0, context.purpose === "preview"),
62541
62664
  validateConfig: (config, context) => service().validateConfig(config, "wait", context),
62542
62665
  execute: async (node, input) => {
62543
62666
  const state = await service().wait(node.data);
@@ -62669,11 +62792,15 @@ var WagoFlowService = class {
62669
62792
  this.subscriptions.push(...replacements);
62670
62793
  this.controllerByHardwareId = controllerByHardwareId;
62671
62794
  }
62672
- async resolveConfigSchema(config, kind, validationContext = /* @__PURE__ */ new Map()) {
62795
+ async resolveConfigSchema(config, kind, validationContext = /* @__PURE__ */ new Map(), previewOnly = false) {
62796
+ const controllerCacheKey = previewOnly ? `wago-flow-controllers:preview:${config.controllerId}` : "wago-flow-controllers";
62673
62797
  const controllers = await this.cached(
62674
62798
  validationContext,
62675
- "wago-flow-controllers",
62676
- () => this.controllers.find({ where: { trustState: "claimed" }, order: { name: "ASC" } })
62799
+ controllerCacheKey,
62800
+ () => previewOnly && typeof config.controllerId !== "number" ? Promise.resolve([]) : this.controllers.find({
62801
+ where: { trustState: "claimed", ...previewOnly ? { id: config.controllerId } : {} },
62802
+ order: { name: "ASC" }
62803
+ })
62677
62804
  );
62678
62805
  const selected = typeof config.controllerId === "number" ? controllers.find((controller) => controller.id === config.controllerId) : void 0;
62679
62806
  const revision2 = selected ? await this.cached(validationContext, `wago-applied-revision:${selected.id}`, async () => {
@@ -62684,12 +62811,19 @@ var WagoFlowService = class {
62684
62811
  });
62685
62812
  return latest ?? null;
62686
62813
  }) : null;
62687
- const channels = revision2 ? JSON.parse(revision2.snapshot).logicalChannels : [];
62814
+ const snapshot = revision2 ? JSON.parse(revision2.snapshot) : null;
62815
+ const channels = snapshot?.logicalChannels ?? [];
62688
62816
  let names = {};
62689
62817
  try {
62690
62818
  names = JSON.parse(revision2?.presetProvenance ?? "null")?.editor?.names ?? {};
62691
62819
  } catch {
62692
62820
  }
62821
+ if (previewOnly) return {
62822
+ dynamic: true,
62823
+ type: "object",
62824
+ properties: {},
62825
+ preview: wagoFlowPreview(config, kind, selected, snapshot, names)
62826
+ };
62693
62827
  const channel = channels.find((item) => item.id === config.channelId);
62694
62828
  const properties = {
62695
62829
  controllerId: {
@@ -62745,6 +62879,7 @@ var WagoFlowService = class {
62745
62879
  return {
62746
62880
  dynamic: true,
62747
62881
  type: "object",
62882
+ preview: wagoFlowPreview(config, kind, selected, snapshot, names),
62748
62883
  properties,
62749
62884
  required: ["controllerId", "channelId", "category", ...kind === "wait" ? ["equals", "timeoutMs"] : []]
62750
62885
  };
@@ -63128,7 +63263,7 @@ var controllerActions = [
63128
63263
  details: {
63129
63264
  channelId,
63130
63265
  commandId: { type: "string", pattern: uuidPattern2 },
63131
- operation: { type: "string", oneOf: ["set", "pulse"] },
63266
+ operation: { type: "string", oneOf: ["set", "pulse", "release"] },
63132
63267
  result: { type: "string", oneOf: ["dispatched", "acknowledged", "rejected", "timeout", "transport_failure"] }
63133
63268
  }
63134
63269
  }