node-opcua-address-space 2.182.2 → 2.183.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/api/helpers/argument_list.ts +8 -9
  2. package/api/helpers/call_helpers.ts +2 -0
  3. package/api/index.ts +14 -0
  4. package/api/interfaces/alarms_and_conditions/promote_to_alarm_options.ts +24 -0
  5. package/api/interfaces/alarms_and_conditions/ua_alarm_condition_ex.ts +33 -0
  6. package/api/interfaces/alarms_and_conditions/ua_limit_alarm_ex.ts +18 -0
  7. package/api/loader/nodeset_record.ts +2 -2
  8. package/api/loader/nodeset_record_applier.ts +1 -0
  9. package/api/loader/nodeset_xml_producer.ts +5 -1
  10. package/api/namespace_of.ts +28 -0
  11. package/dist/api/helpers/argument_list.js +8 -7
  12. package/dist/api/helpers/argument_list.js.map +1 -1
  13. package/dist/api/helpers/call_helpers.js +2 -0
  14. package/dist/api/helpers/call_helpers.js.map +1 -1
  15. package/dist/api/index.d.ts +11 -0
  16. package/dist/api/index.js +16 -3
  17. package/dist/api/index.js.map +1 -1
  18. package/dist/api/interfaces/alarms_and_conditions/promote_to_alarm_options.d.ts +23 -0
  19. package/dist/api/interfaces/alarms_and_conditions/promote_to_alarm_options.js +3 -0
  20. package/dist/api/interfaces/alarms_and_conditions/promote_to_alarm_options.js.map +1 -0
  21. package/dist/api/interfaces/alarms_and_conditions/ua_alarm_condition_ex.d.ts +31 -0
  22. package/dist/api/interfaces/alarms_and_conditions/ua_limit_alarm_ex.d.ts +17 -0
  23. package/dist/api/loader/nodeset_record.d.ts +1 -1
  24. package/dist/api/loader/nodeset_record.js +1 -1
  25. package/dist/api/loader/nodeset_record_applier.js +1 -0
  26. package/dist/api/loader/nodeset_record_applier.js.map +1 -1
  27. package/dist/api/loader/nodeset_xml_producer.js +7 -1
  28. package/dist/api/loader/nodeset_xml_producer.js.map +1 -1
  29. package/dist/api/namespace_of.d.ts +23 -0
  30. package/dist/api/namespace_of.js +29 -0
  31. package/dist/api/namespace_of.js.map +1 -0
  32. package/dist/impl/address_space_change_event_tools.js +4 -1
  33. package/dist/impl/address_space_change_event_tools.js.map +1 -1
  34. package/dist/impl/address_space_private.d.ts +6 -6
  35. package/dist/impl/alarms_and_conditions/ua_acknowledgeable_condition_impl.d.ts +7 -0
  36. package/dist/impl/alarms_and_conditions/ua_acknowledgeable_condition_impl.js +47 -37
  37. package/dist/impl/alarms_and_conditions/ua_acknowledgeable_condition_impl.js.map +1 -1
  38. package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.d.ts +72 -2
  39. package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.js +220 -101
  40. package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.js.map +1 -1
  41. package/dist/impl/alarms_and_conditions/ua_condition_impl.d.ts +19 -0
  42. package/dist/impl/alarms_and_conditions/ua_condition_impl.js +71 -16
  43. package/dist/impl/alarms_and_conditions/ua_condition_impl.js.map +1 -1
  44. package/dist/impl/alarms_and_conditions/ua_exclusive_deviation_alarm_impl.d.ts +1 -1
  45. package/dist/impl/alarms_and_conditions/ua_exclusive_deviation_alarm_impl.js +2 -2
  46. package/dist/impl/alarms_and_conditions/ua_exclusive_deviation_alarm_impl.js.map +1 -1
  47. package/dist/impl/alarms_and_conditions/ua_exclusive_limit_alarm_impl.d.ts +2 -2
  48. package/dist/impl/alarms_and_conditions/ua_exclusive_limit_alarm_impl.js +4 -4
  49. package/dist/impl/alarms_and_conditions/ua_exclusive_limit_alarm_impl.js.map +1 -1
  50. package/dist/impl/alarms_and_conditions/ua_limit_alarm_impl.d.ts +13 -2
  51. package/dist/impl/alarms_and_conditions/ua_limit_alarm_impl.js +21 -5
  52. package/dist/impl/alarms_and_conditions/ua_limit_alarm_impl.js.map +1 -1
  53. package/dist/impl/alarms_and_conditions/ua_non_exclusive_deviation_alarm_impl.d.ts +1 -1
  54. package/dist/impl/alarms_and_conditions/ua_non_exclusive_deviation_alarm_impl.js +2 -2
  55. package/dist/impl/alarms_and_conditions/ua_non_exclusive_deviation_alarm_impl.js.map +1 -1
  56. package/dist/impl/alarms_and_conditions/ua_non_exclusive_limit_alarm_impl.d.ts +1 -1
  57. package/dist/impl/alarms_and_conditions/ua_non_exclusive_limit_alarm_impl.js +2 -2
  58. package/dist/impl/alarms_and_conditions/ua_non_exclusive_limit_alarm_impl.js.map +1 -1
  59. package/dist/impl/alarms_and_conditions/ua_off_normal_alarm_impl.d.ts +1 -1
  60. package/dist/impl/alarms_and_conditions/ua_off_normal_alarm_impl.js +2 -2
  61. package/dist/impl/alarms_and_conditions/ua_off_normal_alarm_impl.js.map +1 -1
  62. package/dist/impl/base_node_impl.d.ts +2 -0
  63. package/dist/impl/base_node_impl.js +28 -0
  64. package/dist/impl/base_node_impl.js.map +1 -1
  65. package/dist/impl/base_node_private.d.ts +2 -0
  66. package/dist/impl/base_node_private.js.map +1 -1
  67. package/dist/impl/data_access/adjust_datavalue_status_code.d.ts +2 -2
  68. package/dist/impl/data_access/adjust_datavalue_status_code.js +35 -4
  69. package/dist/impl/data_access/adjust_datavalue_status_code.js.map +1 -1
  70. package/dist/impl/data_access/semantics_changed.d.ts +31 -0
  71. package/dist/impl/data_access/semantics_changed.js +77 -0
  72. package/dist/impl/data_access/semantics_changed.js.map +1 -0
  73. package/dist/impl/data_access/ua_multistate_discrete_impl.js +0 -2
  74. package/dist/impl/data_access/ua_multistate_discrete_impl.js.map +1 -1
  75. package/dist/impl/data_access/ua_two_state_discrete_impl.js +6 -15
  76. package/dist/impl/data_access/ua_two_state_discrete_impl.js.map +1 -1
  77. package/dist/impl/historical_access/read_processed_details_hook.d.ts +22 -0
  78. package/dist/impl/historical_access/read_processed_details_hook.js +12 -0
  79. package/dist/impl/historical_access/read_processed_details_hook.js.map +1 -0
  80. package/dist/impl/namespace_impl.js +2 -4
  81. package/dist/impl/namespace_impl.js.map +1 -1
  82. package/dist/impl/ua_object_impl.js +13 -1
  83. package/dist/impl/ua_object_impl.js.map +1 -1
  84. package/dist/impl/ua_variable_impl.js +12 -1
  85. package/dist/impl/ua_variable_impl.js.map +1 -1
  86. package/dist/tsconfig_base.tsbuildinfo +1 -1
  87. package/distHelpers/get_address_space_fixture.js +2 -1
  88. package/distHelpers/get_address_space_fixture.js.map +1 -1
  89. package/distHelpers/index.d.ts +1 -0
  90. package/distHelpers/index.js +1 -0
  91. package/distHelpers/index.js.map +1 -1
  92. package/distNodeJS/generate_address_space.js +5 -1
  93. package/distNodeJS/generate_address_space.js.map +1 -1
  94. package/impl/address_space_change_event_tools.ts +4 -1
  95. package/impl/address_space_private.ts +5 -17
  96. package/impl/alarms_and_conditions/ua_acknowledgeable_condition_impl.ts +53 -41
  97. package/impl/alarms_and_conditions/ua_alarm_condition_impl.ts +262 -111
  98. package/impl/alarms_and_conditions/ua_condition_impl.ts +94 -20
  99. package/impl/alarms_and_conditions/ua_exclusive_deviation_alarm_impl.ts +2 -2
  100. package/impl/alarms_and_conditions/ua_exclusive_limit_alarm_impl.ts +4 -4
  101. package/impl/alarms_and_conditions/ua_limit_alarm_impl.ts +22 -5
  102. package/impl/alarms_and_conditions/ua_non_exclusive_deviation_alarm_impl.ts +2 -2
  103. package/impl/alarms_and_conditions/ua_non_exclusive_limit_alarm_impl.ts +2 -2
  104. package/impl/alarms_and_conditions/ua_off_normal_alarm_impl.ts +2 -2
  105. package/impl/base_node_impl.ts +31 -0
  106. package/impl/base_node_private.ts +2 -0
  107. package/impl/data_access/adjust_datavalue_status_code.ts +36 -7
  108. package/impl/data_access/semantics_changed.ts +102 -0
  109. package/impl/data_access/ua_multistate_discrete_impl.ts +0 -2
  110. package/impl/data_access/ua_two_state_discrete_impl.ts +6 -16
  111. package/impl/historical_access/read_processed_details_hook.ts +35 -0
  112. package/impl/namespace_impl.ts +2 -6
  113. package/impl/ua_object_impl.ts +13 -1
  114. package/impl/ua_variable_impl.ts +12 -1
  115. package/package.json +39 -39
  116. package/source_nodejs/generate_address_space.ts +6 -1
@@ -203,7 +203,7 @@ export class UALimitAlarmImplBase extends UAAlarmConditionImplBase implements UA
203
203
  this.lowLowLimit.setValueFromSource({ dataType: DataType.Double, value });
204
204
  }
205
205
 
206
- protected _onInputDataValueChange(dataValue: DataValue): void {
206
+ public onInputDataValueChange(dataValue: DataValue): void {
207
207
  assert(dataValue instanceof DataValue);
208
208
 
209
209
  if (dataValue.statusCode.equals(StatusCodes.BadWaitingForInitialData)) {
@@ -212,20 +212,37 @@ export class UALimitAlarmImplBase extends UAAlarmConditionImplBase implements UA
212
212
  }
213
213
  if (dataValue.statusCode.isNotGood() && !dataValue.statusCode.equals(StatusCodes.UncertainInitialValue)) {
214
214
  // genuinely bad status (not the initial uncertain state) → no specific limit state, alarm inactive
215
- this._signalNewCondition(null, false, "Input node value is not good");
215
+ this.signalNewCondition(null, false, "Input node value is not good");
216
216
  return;
217
217
  }
218
218
  if (dataValue.value.dataType === DataType.Null) {
219
219
  // input not yet set → no specific limit state, alarm inactive
220
- this._signalNewCondition(null, false, "Input node value is null");
220
+ this.signalNewCondition(null, false, "Input node value is null");
221
221
  return;
222
222
  }
223
223
  const value = dataValue.value.value;
224
+ // deliberately the deprecated name, for the same reason as _calculateConditionInfo:
225
+ // an old subclass that overrode `_setStateBasedOnInputValue` is still reached, and a
226
+ // new assignment of the published name is reached through the delegate below.
224
227
  this._setStateBasedOnInputValue(value);
225
228
  }
226
229
 
227
- protected _setStateBasedOnInputValue(_value: number): void {
228
- throw new Error("_setStateBasedOnInputValue must be overriden");
230
+ /**
231
+ * How the alarm turns a numeric input value into limit states. Assign to it to give a
232
+ * limit alarm a rule of its own.
233
+ *
234
+ * The base implementation throws: a bare LimitAlarmType has no limit states to set, so
235
+ * every concrete limit alarm type defines this.
236
+ */
237
+ public setStateBasedOnInputValue(_value: number): void {
238
+ throw new Error("setStateBasedOnInputValue must be overriden");
239
+ }
240
+
241
+ /**
242
+ * @deprecated assign {@link setStateBasedOnInputValue} instead; this delegates to it.
243
+ */
244
+ public _setStateBasedOnInputValue(value: number): void {
245
+ this.setStateBasedOnInputValue(value);
229
246
  }
230
247
 
231
248
  protected _watchLimits(): void {
@@ -61,14 +61,14 @@ export class UANonExclusiveDeviationAlarmImplBase
61
61
 
62
62
  return alarm;
63
63
  }
64
- public _setStateBasedOnInputValue(value: number): void {
64
+ public setStateBasedOnInputValue(value: number): void {
65
65
  const setpointValue = this.getSetpointValue();
66
66
  if (setpointValue === null) {
67
67
  throw new Error("Cannot access setpoint Value");
68
68
  }
69
69
  assert(Number.isFinite(setpointValue), "expecting a valid setpoint value");
70
70
  // call base class implementation
71
- super._setStateBasedOnInputValue(value - setpointValue);
71
+ super.setStateBasedOnInputValue(value - setpointValue);
72
72
  }
73
73
 
74
74
  public getSetpointNodeNode(): UAVariableT<number, DataType.Double> | UAVariableT<number, DataType.Float> | undefined {
@@ -180,9 +180,9 @@ export class UANonExclusiveLimitAlarmImplBase extends UALimitAlarmImpl implement
180
180
 
181
181
  state_str = JSON.stringify(states);
182
182
 
183
- UALimitAlarmImpl.prototype._signalNewCondition.call(this, state_str, isActive, value);
183
+ UALimitAlarmImpl.prototype.signalNewCondition.call(this, state_str, isActive, value);
184
184
  }
185
- protected _setStateBasedOnInputValue(value: number): void {
185
+ public setStateBasedOnInputValue(value: number): void {
186
186
  assert(Number.isFinite(value), "expecting a valid value here");
187
187
 
188
188
  let isActive = false;
@@ -168,7 +168,7 @@ export class UAOffNormalAlarmImplBase extends UADiscreteAlarmImplBase implements
168
168
  }
169
169
  const stateName = isActive ? "Active" : "Inactive";
170
170
  // also raise the event
171
- this._signalNewCondition(stateName, isActive, message);
171
+ this.signalNewCondition(stateName, isActive, message);
172
172
  if (!isActive) {
173
173
  this.currentBranch().setRetain(false);
174
174
  }
@@ -183,7 +183,7 @@ export class UAOffNormalAlarmImplBase extends UADiscreteAlarmImplBase implements
183
183
  this.updateAlarmState(isActive, "automatique update");
184
184
  }
185
185
 
186
- protected _onInputDataValueChange(dataValue: DataValue): void {
186
+ public onInputDataValueChange(dataValue: DataValue): void {
187
187
  if (dataValue.statusCode.isNotGood()) {
188
188
  // what shall we do ?
189
189
  return;
@@ -165,6 +165,8 @@ export interface InternalBaseNodeOptions {
165
165
  browseName: QualifiedName;
166
166
  nodeId: NodeId;
167
167
  references?: UAReference[];
168
+ /** the parent the document declared (ParentNodeId), see CreateNodeOptions.parentNodeId */
169
+ parentNodeId?: NodeIdLike | BaseNode | null;
168
170
 
169
171
  displayName?: LocalizedTextLike | LocalizedTextLike[];
170
172
  description?: LocalizedTextLike | null;
@@ -443,6 +445,14 @@ export abstract class BaseNodeImpl<T extends BaseNodeEvents & ListenerSignature<
443
445
  if (options.releaseStatus) {
444
446
  this.releaseStatus = options.releaseStatus;
445
447
  }
448
+ if (options.parentNodeId) {
449
+ _private._declaredParentNodeId =
450
+ options.parentNodeId instanceof NodeId
451
+ ? options.parentNodeId
452
+ : (options.parentNodeId as BaseNode).nodeId instanceof NodeId
453
+ ? (options.parentNodeId as BaseNode).nodeId
454
+ : resolveNodeId(options.parentNodeId as NodeIdLike);
455
+ }
446
456
  if (options.declaredAccessRestrictions !== undefined) {
447
457
  this.declaredAccessRestrictions = options.declaredAccessRestrictions;
448
458
  }
@@ -1736,6 +1746,27 @@ function _setup_parent_item(this: BaseNode, referencesMap: Map<ReferenceKey, UAR
1736
1746
  const addressSpace = this.addressSpace;
1737
1747
 
1738
1748
  if (referencesMap.size > 0) {
1749
+ // the document declared a parent (ParentNodeId): it wins when it is one of the
1750
+ // hierarchical parents, whatever the order the references were declared in, and
1751
+ // even through Organizes, which is otherwise not a parent relation. OPC Foundation
1752
+ // NodeSets rely on it for a node reached from two parents (an add-in re-exposed
1753
+ // from a folder) and for a folder a type organizes, and name ids after it.
1754
+ const declared = _private._declaredParentNodeId;
1755
+ if (declared) {
1756
+ const hierarchical = this.findReferencesEx("HierarchicalReferences", BrowseDirection.Inverse);
1757
+ const match = hierarchical.find((r) => sameNodeId(r.nodeId, declared));
1758
+ if (match) {
1759
+ return ReferenceImpl.resolveReferenceNode(addressSpace, match);
1760
+ }
1761
+ // the parent may be asked for while the document is still being loaded, before
1762
+ // the other end of the reference has been declared: the document still says
1763
+ // who the parent is
1764
+ const declaredNode = addressSpace.findNode(declared);
1765
+ if (declaredNode) {
1766
+ return declaredNode;
1767
+ }
1768
+ }
1769
+
1739
1770
  references = this.findReferencesEx("Aggregates", BrowseDirection.Inverse);
1740
1771
 
1741
1772
  if (references.length >= 1) {
@@ -112,6 +112,8 @@ interface BaseNodeCache {
112
112
  _displayName?: LocalizedText[];
113
113
  _displayNameRaw?: LocalizedTextLike | LocalizedTextLike[];
114
114
  _parent?: BaseNode | null;
115
+ /** the parent the document declared (`ParentNodeId`); consulted once, when _parent is first computed */
116
+ _declaredParentNodeId?: NodeId;
115
117
  /**
116
118
  * the references other nodes hold to this one; `EMPTY_REFERENCE_INDEX` until the first one
117
119
  * arrives, since a nodeset declares most references from both ends and most nodes never get one
@@ -7,18 +7,47 @@ import { NodeClass } from "node-opcua-data-model";
7
7
  import type { DataValue } from "node-opcua-data-value";
8
8
  import { type StatusCode, StatusCodes } from "node-opcua-status-code";
9
9
  import type { Range } from "node-opcua-types";
10
- import type { Variant } from "node-opcua-variant";
11
- import type { UAVariableImpl } from "../ua_variable_impl.js";
10
+ import { type Variant, VariantArrayType } from "node-opcua-variant";
12
11
 
13
- function validate_value_range(range: Range, variant: Variant) {
14
- if (variant.value < range.low || variant.value > range.high) {
15
- return false;
12
+ function isOutOfRange(value: number, range: Range): boolean {
13
+ return value < range.low || value > range.high;
14
+ }
15
+
16
+ /**
17
+ * Checks that the value(s) carried by `variant` fall within `range`.
18
+ *
19
+ * `variant.value` is a plain number for a scalar write, but for an array (or matrix)
20
+ * variable it is an array-like (Array or a TypedArray) holding only the elements this
21
+ * particular Write touches - a client writing with an IndexRange sends just the sub-range
22
+ * it targets, so an out-of-range element elsewhere in the variable's full value (one this
23
+ * write leaves untouched) must never be seen here and must never fail the write. Comparing
24
+ * an array directly with `<`/`>` coerces it through Array.prototype.toString first: a
25
+ * one-element array happens to stringify to its bare element and compares correctly by
26
+ * accident, but any other length stringifies to a comma-joined list that parses to NaN,
27
+ * so every such comparison is silently false and a genuinely out-of-range multi-element
28
+ * write was never being rejected. Each element is compared numerically instead.
29
+ */
30
+ function validate_value_range(range: Range, variant: Variant): boolean {
31
+ const value = variant.value;
32
+ if (variant.arrayType === VariantArrayType.Array || variant.arrayType === VariantArrayType.Matrix) {
33
+ if (value === null || value === undefined) {
34
+ return true;
35
+ }
36
+ const length = (value as ArrayLike<number>).length;
37
+ for (let i = 0; i < length; i++) {
38
+ if (isOutOfRange((value as ArrayLike<number>)[i], range)) {
39
+ return false;
40
+ }
41
+ }
42
+ return true;
16
43
  }
17
- return true;
44
+ return !isOutOfRange(value as number, range);
18
45
  }
19
46
 
20
47
  export function adjustDataValueStatusCode(
21
- variable: UAVariableImpl,
48
+ // the public interface, not UAVariableImpl: this function is exported from api/, and the
49
+ // body only reads a child node, so naming an internal type here leaked it into the API
50
+ variable: UAVariable,
22
51
  dataValue: DataValue,
23
52
  acceptValueOutOfRange: boolean
24
53
  ): StatusCode {
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @module node-opcua-address-space
3
+ *
4
+ * OPC 10000-8 (Data Access) 5.2: some Properties of a DataItem carry the *meaning* of its
5
+ * value rather than the value itself. When one of them changes, a client that has cached the
6
+ * old meaning would misinterpret every subsequent value, so the Server has to say so: the
7
+ * next data change notification of the DataItem carries the SemanticsChanged bit of
8
+ * OPC 10000-4 7.39.
9
+ *
10
+ * The address space side of that is `UAVariable#handle_semantic_changed()`, which bumps
11
+ * `semantic_version`; the MonitoredItem watches that counter. What was missing is the wiring
12
+ * between a Property and its DataItem: the `addAnalogDataItem` / `addTwoStateDiscrete` /
13
+ * `addMultiStateDiscrete` helpers each attached a "value_changed" listener by hand, so only a
14
+ * node built through one of those helpers was covered. A DataItem instantiated from its
15
+ * VariableType, loaded from a NodeSet, or assembled property by property (which is the only
16
+ * way to build the ArrayItemType subtypes) had none, and its EURange could be written without
17
+ * any notification ever carrying the bit.
18
+ *
19
+ * So the relation is resolved from the Property instead, at the moment its value changes:
20
+ * a Property named after one of the semantics-bearing Properties below, whose parent is a
21
+ * DataItem, notifies that parent. That covers every way a DataItem can come into existence,
22
+ * and needs no bookkeeping at construction time - which is what defeated a creation-time hook,
23
+ * since a hand-built DataItem gets its Properties *after* the Variable itself.
24
+ */
25
+ import type { UAVariable, UAVariableType } from "node-opcua-address-space-base";
26
+ import { VariableTypeIds } from "node-opcua-constants";
27
+ import { NodeClass } from "node-opcua-data-model";
28
+ import { resolveNodeId } from "node-opcua-nodeid";
29
+
30
+ /**
31
+ * the Properties whose value carries the semantics of the DataItem they belong to.
32
+ *
33
+ * DataItemType : Definition, ValuePrecision
34
+ * AnalogItemType/UnitType : EURange, InstrumentRange, EngineeringUnits
35
+ * ArrayItemType (+subtypes): Title, AxisScaleType, XAxisDefinition, YAxisDefinition, ZAxisDefinition
36
+ * TwoStateDiscreteType : TrueState, FalseState
37
+ * MultiStateDiscreteType : EnumStrings
38
+ * MultiStateValueDiscrete : EnumValues
39
+ */
40
+ const semanticsBearingProperties = new Set<string>([
41
+ "AxisScaleType",
42
+ "Definition",
43
+ "EngineeringUnits",
44
+ "EURange",
45
+ "EnumStrings",
46
+ "EnumValues",
47
+ "FalseState",
48
+ "InstrumentRange",
49
+ "Title",
50
+ "TrueState",
51
+ "ValuePrecision",
52
+ "XAxisDefinition",
53
+ "YAxisDefinition",
54
+ "ZAxisDefinition"
55
+ ]);
56
+
57
+ const dataItemTypeNodeId = resolveNodeId(VariableTypeIds.DataItemType);
58
+
59
+ /**
60
+ * the DataItem whose semantics `property` carries, or null when `property` is not one of them.
61
+ *
62
+ * A positive answer is memoized on the Property; a negative one is not, because the name test
63
+ * that gates the lookup is a single Set hit and because a Property can be attached to its
64
+ * DataItem after its first value change.
65
+ */
66
+ function semanticsOwnerOf(property: UAVariable): UAVariable | null {
67
+ const name = property.browseName.name;
68
+ if (!name || !semanticsBearingProperties.has(name)) {
69
+ return null;
70
+ }
71
+ const cache = property as UAVariable & { $$semanticsOwner?: UAVariable };
72
+ if (cache.$$semanticsOwner) {
73
+ return cache.$$semanticsOwner;
74
+ }
75
+ const parent = property.parent;
76
+ if (!parent || parent.nodeClass !== NodeClass.Variable) {
77
+ return null;
78
+ }
79
+ const dataItem = parent as UAVariable;
80
+ const typeDefinition = dataItem.typeDefinitionObj;
81
+ if (!typeDefinition || typeDefinition.nodeClass !== NodeClass.VariableType) {
82
+ return null;
83
+ }
84
+ const dataItemType = dataItem.addressSpace.findNode(dataItemTypeNodeId) as UAVariableType | null;
85
+ if (!dataItemType || !typeDefinition.isSubtypeOf(dataItemType)) {
86
+ return null;
87
+ }
88
+ cache.$$semanticsOwner = dataItem;
89
+ return dataItem;
90
+ }
91
+
92
+ /**
93
+ * called whenever a Variable's value really changed: if that Variable is a semantics-bearing
94
+ * Property of a DataItem, the DataItem's semantic_version is bumped so that the next data
95
+ * change notification of the DataItem carries the SemanticsChanged bit.
96
+ */
97
+ export function notifySemanticsChangedIfNeeded(property: UAVariable): void {
98
+ const dataItem = semanticsOwnerOf(property);
99
+ if (dataItem) {
100
+ (dataItem as UAVariable & { handle_semantic_changed: (dataValue?: unknown) => void }).handle_semantic_changed();
101
+ }
102
+ }
@@ -128,8 +128,6 @@ export function promoteToMultiStateDiscrete<T, DT extends DataType>(node: UAVari
128
128
  _node._post_initialize();
129
129
 
130
130
  assert(_node.enumStrings.browseName.toString() === "EnumStrings");
131
- const handler = _node.handle_semantic_changed.bind(_node);
132
- _node.enumStrings.on("value_changed", handler);
133
131
  _node.install_extra_properties();
134
132
  return node as UAMultiStateDiscreteImpl<T, DT>;
135
133
  }
@@ -32,13 +32,10 @@ export class UATwoStateDiscreteImplBase extends UAVariableImplT<boolean, DataTyp
32
32
  _post_initialize(): void {
33
33
  // The StatusCode SemanticsChanged bit shall be set if any of the FalseState or TrueState
34
34
  // (changes can cause misinterpretation by users or (scripting) programs) Properties are changed
35
- // (see section 5.2 for additional information).
36
- const handler = this.handle_semantic_changed.bind(this);
37
-
35
+ // (see section 5.2 for additional information). That is done generically in
36
+ // impl/data_access/semantics_changed.ts; all this has to do is warn about a missing Property.
38
37
  const falseState = this.getPropertyByName("FalseState");
39
- if (falseState) {
40
- falseState.on("value_changed", handler);
41
- } else {
38
+ if (!falseState) {
42
39
  /* c8 ignore next */
43
40
  console.warn(
44
41
  "warning: UATwoStateDiscrete -> a FalseState property is mandatory ",
@@ -47,9 +44,7 @@ export class UATwoStateDiscreteImplBase extends UAVariableImplT<boolean, DataTyp
47
44
  );
48
45
  }
49
46
  const trueState = this.getPropertyByName("TrueState");
50
- if (trueState) {
51
- trueState.on("value_changed", handler);
52
- } else {
47
+ if (!trueState) {
53
48
  /* c8 ignore next */
54
49
  console.warn(
55
50
  "waring: UATwoStateDiscrete -> a TrueState property is mandatory",
@@ -154,11 +149,10 @@ export function _addTwoStateDiscrete(namespace: INamespace, options: AddTwoState
154
149
  const dataValueVerif = variable.readValue();
155
150
  assert(dataValueVerif.value.dataType === DataType.Boolean);
156
151
  */
157
- const handler = (variable as UAVariableImpl).handle_semantic_changed.bind(variable);
158
152
 
159
153
  add_dataItem_stuff(variable, options);
160
154
 
161
- const trueStateNode = namespace.addVariable({
155
+ namespace.addVariable({
162
156
  browseName: { name: "TrueState", namespaceIndex: 0 },
163
157
  dataType: "LocalizedText",
164
158
  minimumSamplingInterval: 0,
@@ -171,9 +165,7 @@ export function _addTwoStateDiscrete(namespace: INamespace, options: AddTwoState
171
165
  })
172
166
  });
173
167
 
174
- trueStateNode.on("value_changed", handler);
175
-
176
- const falseStateNode = namespace.addVariable({
168
+ namespace.addVariable({
177
169
  browseName: { name: "FalseState", namespaceIndex: 0 },
178
170
  dataType: "LocalizedText",
179
171
  minimumSamplingInterval: 0,
@@ -187,8 +179,6 @@ export function _addTwoStateDiscrete(namespace: INamespace, options: AddTwoState
187
179
  })
188
180
  });
189
181
 
190
- falseStateNode.on("value_changed", handler);
191
-
192
182
  variable.install_extra_properties();
193
183
 
194
184
  return promoteToTwoStateDiscrete(variable);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The supported way to plug a ReadProcessedDetails implementation into an address space.
3
+ *
4
+ * The aggregate functions live in node-opcua-aggregates, and the address space calls into them
5
+ * when a client issues a ReadProcessed history request, so the two packages need a hook between
6
+ * them. It used to be installed by casting the address space to AddressSpacePrivate and
7
+ * assigning to `_readProcessedDetails` from outside, which made an underscore-prefixed field
8
+ * part of another package's build in all but name.
9
+ */
10
+ import type { ContinuationData, IAddressSpace, ISessionContext, UAVariable } from "node-opcua-address-space-base";
11
+ import type { QualifiedNameLike } from "node-opcua-data-model";
12
+ import type { NumericRange } from "node-opcua-numeric-range";
13
+ import type { CallbackT } from "node-opcua-status-code";
14
+ import type { HistoryReadResult, ReadProcessedDetails } from "node-opcua-types";
15
+ import type { AddressSpacePrivate } from "../address_space_private.js";
16
+
17
+ /** answers a ReadProcessed history request for one variable */
18
+ export type ReadProcessedDetailsFunc = (
19
+ variable: UAVariable,
20
+ context: ISessionContext,
21
+ historyReadDetails: ReadProcessedDetails,
22
+ indexRange: NumericRange | null,
23
+ dataEncoding: QualifiedNameLike | null,
24
+ continuationData: ContinuationData,
25
+ callback: CallbackT<HistoryReadResult>
26
+ ) => void;
27
+
28
+ /**
29
+ * Install the handler an address space calls for ReadProcessed history requests.
30
+ *
31
+ * Without one, a ReadProcessed request answers BadHistoryOperationUnsupported.
32
+ */
33
+ export function installReadProcessedDetails(addressSpace: IAddressSpace, handler: ReadProcessedDetailsFunc): void {
34
+ (addressSpace as AddressSpacePrivate)._readProcessedDetails = handler;
35
+ }
@@ -1036,8 +1036,8 @@ export class NamespaceImpl implements NamespacePrivate {
1036
1036
 
1037
1037
  assert(euRange.readValue().value.value instanceof Range);
1038
1038
 
1039
- const handler = variable.handle_semantic_changed.bind(variable);
1040
- euRange.on("value_changed", handler);
1039
+ // the SemanticsChanged wiring of EURange/InstrumentRange/EngineeringUnits is generic and
1040
+ // lives in impl/data_access/semantics_changed.ts: it covers DataItems built any other way too.
1041
1041
 
1042
1042
  if (Object.hasOwn(options, "instrumentRange")) {
1043
1043
  const instrumentRangeExisting = variable.getPropertyByName("InstrumentRange") as UAVariableImpl | null;
@@ -1062,8 +1062,6 @@ export class NamespaceImpl implements NamespacePrivate {
1062
1062
  StatusCodes.Good
1063
1063
  );
1064
1064
  }
1065
-
1066
- instrumentRange.on("value_changed", handler);
1067
1065
  }
1068
1066
  (variable as unknown as { acceptValueOutOfRange?: boolean }).acceptValueOutOfRange = options.acceptValueOutOfRange;
1069
1067
 
@@ -1096,8 +1094,6 @@ export class NamespaceImpl implements NamespacePrivate {
1096
1094
  StatusCodes.Good
1097
1095
  );
1098
1096
  }
1099
-
1100
- eu.on("value_changed", handler);
1101
1097
  }
1102
1098
 
1103
1099
  variable.install_extra_properties();
@@ -77,6 +77,10 @@ export class UAObjectImpl<T extends UAObjectEvents & ListenerSignature<T> = UAOb
77
77
  this._eventNotifier = options.eventNotifier || EventNotifierFlags.None;
78
78
  assert(typeof this.eventNotifier === "number" && isValidByte(this.eventNotifier));
79
79
  this.symbolicName = options.symbolicName || null;
80
+ // every event MonitoredItem on this object is one "event" listener, and the Server
81
+ // object collects them for every Session: more than ten is the normal case, not a
82
+ // leak for Node to warn about (UAVariableImpl does the same for its "value_changed")
83
+ this.setMaxListeners(5000);
80
84
  }
81
85
 
82
86
  public readAttribute(
@@ -275,4 +279,12 @@ export class UAObjectImpl<T extends UAObjectEvents & ListenerSignature<T> = UAOb
275
279
  }
276
280
  }
277
281
 
278
- const plainChalk = new Proxy(chalk, { get: () => (s: string) => s }) as typeof chalk;
282
+ // A colourless chalk for `inspect(node, { colors: false })`. Not a Proxy over `chalk`
283
+ // returning the identity for every property: chalk 4 defines each style (`cyan`, ...)
284
+ // on first use as a non-writable, non-configurable own data property, after which a
285
+ // Proxy `get` trap returning anything else violates the Proxy invariant and throws a
286
+ // TypeError. Node's own `warnMaxListenersExceeded` inspects the emitter without
287
+ // colours, so that TypeError surfaced inside `node.on("event", ...)` when an eleventh
288
+ // event MonitoredItem was created on the Server object, and killed the server process
289
+ // (CTT A and C Refresh Err_004).
290
+ const plainChalk = new chalk.Instance({ level: 0 });
@@ -84,6 +84,7 @@ import { apply_condition_refresh, type ConditionRefreshCache } from "./apply_con
84
84
  import { BaseNodeImpl, type InternalBaseNodeOptions } from "./base_node_impl.js";
85
85
  import { _clone, ToStringBuilder, UAVariable_toString, valueRankToString } from "./base_node_private.js";
86
86
  import { adjustDataValueStatusCode } from "./data_access/adjust_datavalue_status_code.js";
87
+ import { notifySemanticsChangedIfNeeded } from "./data_access/semantics_changed.js";
87
88
  import { _getBasicDataType } from "./get_basic_datatype.js";
88
89
  import { type EnumerationInfo, type IEnumItem, UADataTypeImpl } from "./ua_data_type_impl.js";
89
90
  import {
@@ -104,7 +105,8 @@ const warningLog = make_warningLog("ua_variable_impl");
104
105
  const doDebug = checkDebugFlag("ua_variable_impl");
105
106
  const errorLog = make_errorLog("ua_variable_impl");
106
107
 
107
- const plainChalk = new Proxy(chalk, { get: () => (s: string) => s }) as typeof chalk;
108
+ // a colourless chalk; see ua_object_impl.ts for why it is not a Proxy over `chalk`
109
+ const plainChalk = new chalk.Instance({ level: 0 });
108
110
 
109
111
  export function adjust_accessLevel(accessLevel: string | number | AccessLevelFlag | null | undefined): AccessLevelFlag {
110
112
  const flag = makeAccessLevelFlag(accessLevel ?? "CurrentRead | CurrentWrite");
@@ -1963,6 +1965,15 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1963
1965
  }
1964
1966
 
1965
1967
  if (!sameDataValue(old_dataValue, dataValue)) {
1968
+ // a Property that carries the semantics of a DataItem (EURange, TrueState, ...) has to make
1969
+ // the DataItem's next notification carry the SemanticsChanged bit. Done from the Property
1970
+ // rather than with a listener installed when the DataItem is built, because a hand-built
1971
+ // DataItem gets its Properties after itself - see impl/data_access/semantics_changed.ts.
1972
+ // ... but a Property receiving its first value is a Variable being built, not a
1973
+ // semantics change: until then its StatusCode is UncertainInitialValue.
1974
+ if (old_dataValue.statusCode.isGood()) {
1975
+ notifySemanticsChangedIfNeeded(this as UAVariable);
1976
+ }
1966
1977
  if (this.getBasicDataType() === DataType.ExtensionObject) {
1967
1978
  const preciseClock = coerceClock(this.$dataValue.sourceTimestamp, this.$dataValue.sourcePicoseconds);
1968
1979
  const cache: Set<UAVariable> = new Set();
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "node-opcua-address-space",
3
- "version": "2.182.2",
3
+ "version": "2.183.0",
4
4
  "description": "pure nodejs OPCUA SDK - module address-space",
5
5
  "main": "./dist/api/index.js",
6
6
  "types": "./dist/api/index.d.ts",
7
7
  "engines": {
8
- "node": ">=18"
8
+ "node": ">=22.13.0"
9
9
  },
10
10
  "scripts": {
11
11
  "build": "tsc -b",
@@ -22,50 +22,50 @@
22
22
  "@types/semver": "7.8.0",
23
23
  "chalk": "4.1.2",
24
24
  "dequeue": "^1.0.5",
25
- "node-opcua-address-space-base": "2.182.0",
26
- "node-opcua-assert": "2.179.0",
27
- "node-opcua-basic-types": "2.182.0",
28
- "node-opcua-binary-stream": "2.181.0",
29
- "node-opcua-client-dynamic-extension-object": "2.182.1",
30
- "node-opcua-constants": "2.181.0",
25
+ "node-opcua-address-space-base": "2.183.0",
26
+ "node-opcua-assert": "2.183.0",
27
+ "node-opcua-basic-types": "2.183.0",
28
+ "node-opcua-binary-stream": "2.183.0",
29
+ "node-opcua-client-dynamic-extension-object": "2.183.0",
30
+ "node-opcua-constants": "2.183.0",
31
31
  "node-opcua-crypto": "5.10.1",
32
- "node-opcua-data-access": "2.182.0",
33
- "node-opcua-data-model": "2.182.0",
34
- "node-opcua-data-value": "2.182.0",
35
- "node-opcua-date-time": "2.181.0",
36
- "node-opcua-debug": "2.181.0",
37
- "node-opcua-enum": "2.181.0",
38
- "node-opcua-extension-object": "2.182.0",
39
- "node-opcua-factory": "2.182.0",
40
- "node-opcua-nodeid": "2.182.0",
41
- "node-opcua-nodeset-ua": "2.182.0",
42
- "node-opcua-numeric-range": "2.182.0",
43
- "node-opcua-object-registry": "2.181.0",
44
- "node-opcua-pseudo-session": "2.182.1",
45
- "node-opcua-service-browse": "2.182.0",
46
- "node-opcua-service-call": "2.182.0",
47
- "node-opcua-service-history": "2.182.0",
48
- "node-opcua-service-translate-browse-path": "2.182.0",
49
- "node-opcua-service-write": "2.182.0",
50
- "node-opcua-status-code": "2.181.0",
51
- "node-opcua-types": "2.182.0",
52
- "node-opcua-utils": "2.181.0",
53
- "node-opcua-variant": "2.182.0",
54
- "node-opcua-xml2json": "2.182.0",
32
+ "node-opcua-data-access": "2.183.0",
33
+ "node-opcua-data-model": "2.183.0",
34
+ "node-opcua-data-value": "2.183.0",
35
+ "node-opcua-date-time": "2.183.0",
36
+ "node-opcua-debug": "2.183.0",
37
+ "node-opcua-enum": "2.183.0",
38
+ "node-opcua-extension-object": "2.183.0",
39
+ "node-opcua-factory": "2.183.0",
40
+ "node-opcua-nodeid": "2.183.0",
41
+ "node-opcua-nodeset-ua": "2.183.0",
42
+ "node-opcua-numeric-range": "2.183.0",
43
+ "node-opcua-object-registry": "2.183.0",
44
+ "node-opcua-pseudo-session": "2.183.0",
45
+ "node-opcua-service-browse": "2.183.0",
46
+ "node-opcua-service-call": "2.183.0",
47
+ "node-opcua-service-history": "2.183.0",
48
+ "node-opcua-service-translate-browse-path": "2.183.0",
49
+ "node-opcua-service-write": "2.183.0",
50
+ "node-opcua-status-code": "2.183.0",
51
+ "node-opcua-types": "2.183.0",
52
+ "node-opcua-utils": "2.183.0",
53
+ "node-opcua-variant": "2.183.0",
54
+ "node-opcua-xml2json": "2.183.0",
55
55
  "semver": "^7.8.5",
56
56
  "thenify-ex": "4.4.0",
57
57
  "xml-writer": "^1.7.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "humanize": "0.0.9",
61
- "node-opcua-benchmarker": "2.181.0",
62
- "node-opcua-leak-detector": "2.181.0",
63
- "node-opcua-nodesets": "2.182.0",
64
- "node-opcua-packet-analyzer": "2.182.0",
65
- "node-opcua-schemas": "2.182.0",
66
- "node-opcua-service-filter": "2.182.2",
61
+ "node-opcua-benchmarker": "2.183.0",
62
+ "node-opcua-leak-detector": "2.183.0",
63
+ "node-opcua-nodesets": "2.183.0",
64
+ "node-opcua-packet-analyzer": "2.183.0",
65
+ "node-opcua-schemas": "2.183.0",
66
+ "node-opcua-service-filter": "2.183.0",
67
67
  "node-opcua-test-fixtures": "2.181.0",
68
- "node-opcua-test-helpers": "2.181.0"
68
+ "node-opcua-test-helpers": "2.183.0"
69
69
  },
70
70
  "author": "Etienne Rossignon",
71
71
  "license": "MIT",
@@ -82,7 +82,7 @@
82
82
  "internet of things"
83
83
  ],
84
84
  "homepage": "http://node-opcua.github.io/",
85
- "gitHead": "cb72d06e8bcb611479f44f9f45f67bbee93bc32e",
85
+ "gitHead": "d9a9af54d162f0fbb8d287e168aaeeda2d5c38b7",
86
86
  "files": [
87
87
  "dist",
88
88
  "distHelpers",
@@ -145,10 +145,15 @@ async function siblingOrXml(xmlFile: string): Promise<{ source: NamedNodesetSour
145
145
  return { source: { name: `${xmlFile} (image)`, source: image }, path: "image" };
146
146
  }
147
147
 
148
+ // The one place this module learns where it sits on disk. `import.meta.dirname`
149
+ // cannot be used while this package emits CommonJS (TS1470), so the ESM migration
150
+ // has this single line to change rather than several scattered uses.
151
+ const here = __dirname;
152
+
148
153
  /** the version this package was built as, for the header of the images it writes */
149
154
  export function addressSpacePackageVersion(): string {
150
155
  try {
151
- const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")) as { version?: string };
156
+ const packageJson = JSON.parse(fs.readFileSync(path.join(here, "..", "package.json"), "utf8")) as { version?: string };
152
157
  return packageJson.version ?? "unknown";
153
158
  } catch {
154
159
  return "unknown";