node-opcua-address-space 2.181.0 → 2.182.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 (136) hide show
  1. package/api/helpers/ensure_secure_access.ts +53 -2
  2. package/api/index.ts +30 -1
  3. package/api/loader/generateAddressSpaceRaw.ts +44 -139
  4. package/api/loader/load_nodeset2.ts +5 -0
  5. package/api/loader/narrow_instance_datatypes.ts +264 -0
  6. package/api/loader/nodeset_builtin_formats.ts +143 -0
  7. package/api/loader/nodeset_format.ts +153 -0
  8. package/api/loader/nodeset_image.ts +37 -4
  9. package/api/loader/nodeset_image_codec.ts +153 -34
  10. package/api/loader/nodeset_record.ts +26 -2
  11. package/api/loader/nodeset_record_applier.ts +54 -3
  12. package/api/loader/nodeset_records_to_xml.ts +620 -0
  13. package/api/loader/nodeset_source.ts +185 -26
  14. package/api/loader/nodeset_to_image.ts +16 -5
  15. package/api/loader/nodeset_xml_header.ts +119 -0
  16. package/api/loader/nodeset_xml_primitives.ts +209 -0
  17. package/api/loader/nodeset_xml_producer.ts +61 -2
  18. package/api/loader/parsers/variant_parser.ts +9 -0
  19. package/bin/nodeset_equivalence.js +750 -0
  20. package/bin/nodeset_tool_common.js +72 -0
  21. package/bin/opcua-nodeset-image.js +93 -103
  22. package/bin/opcua-nodeset-live.js +139 -0
  23. package/dist/api/helpers/ensure_secure_access.d.ts +20 -0
  24. package/dist/api/helpers/ensure_secure_access.js +39 -0
  25. package/dist/api/helpers/ensure_secure_access.js.map +1 -1
  26. package/dist/api/index.d.ts +7 -2
  27. package/dist/api/index.js +21 -1
  28. package/dist/api/index.js.map +1 -1
  29. package/dist/api/loader/generateAddressSpaceRaw.d.ts +1 -7
  30. package/dist/api/loader/generateAddressSpaceRaw.js +32 -125
  31. package/dist/api/loader/generateAddressSpaceRaw.js.map +1 -1
  32. package/dist/api/loader/load_nodeset2.js +4 -0
  33. package/dist/api/loader/load_nodeset2.js.map +1 -1
  34. package/dist/api/loader/narrow_instance_datatypes.d.ts +30 -0
  35. package/dist/api/loader/narrow_instance_datatypes.js +217 -0
  36. package/dist/api/loader/narrow_instance_datatypes.js.map +1 -0
  37. package/dist/api/loader/nodeset_builtin_formats.d.ts +27 -0
  38. package/dist/api/loader/nodeset_builtin_formats.js +133 -0
  39. package/dist/api/loader/nodeset_builtin_formats.js.map +1 -0
  40. package/dist/api/loader/nodeset_format.d.ts +123 -0
  41. package/dist/api/loader/nodeset_format.js +63 -0
  42. package/dist/api/loader/nodeset_format.js.map +1 -0
  43. package/dist/api/loader/nodeset_image.d.ts +17 -1
  44. package/dist/api/loader/nodeset_image.js +32 -4
  45. package/dist/api/loader/nodeset_image.js.map +1 -1
  46. package/dist/api/loader/nodeset_image_codec.d.ts +4 -0
  47. package/dist/api/loader/nodeset_image_codec.js +142 -53
  48. package/dist/api/loader/nodeset_image_codec.js.map +1 -1
  49. package/dist/api/loader/nodeset_record.d.ts +26 -2
  50. package/dist/api/loader/nodeset_record.js +11 -2
  51. package/dist/api/loader/nodeset_record.js.map +1 -1
  52. package/dist/api/loader/nodeset_record_applier.js +52 -3
  53. package/dist/api/loader/nodeset_record_applier.js.map +1 -1
  54. package/dist/api/loader/nodeset_records_to_xml.d.ts +16 -0
  55. package/dist/api/loader/nodeset_records_to_xml.js +536 -0
  56. package/dist/api/loader/nodeset_records_to_xml.js.map +1 -0
  57. package/dist/api/loader/nodeset_source.d.ts +68 -7
  58. package/dist/api/loader/nodeset_source.js +157 -20
  59. package/dist/api/loader/nodeset_source.js.map +1 -1
  60. package/dist/api/loader/nodeset_to_image.d.ts +4 -4
  61. package/dist/api/loader/nodeset_to_image.js +14 -4
  62. package/dist/api/loader/nodeset_to_image.js.map +1 -1
  63. package/dist/api/loader/nodeset_xml_header.d.ts +22 -0
  64. package/dist/api/loader/nodeset_xml_header.js +113 -0
  65. package/dist/api/loader/nodeset_xml_header.js.map +1 -0
  66. package/dist/api/loader/nodeset_xml_primitives.d.ts +37 -0
  67. package/dist/api/loader/nodeset_xml_primitives.js +196 -0
  68. package/dist/api/loader/nodeset_xml_primitives.js.map +1 -0
  69. package/dist/api/loader/nodeset_xml_producer.js +56 -1
  70. package/dist/api/loader/nodeset_xml_producer.js.map +1 -1
  71. package/dist/api/loader/parsers/variant_parser.js +7 -0
  72. package/dist/api/loader/parsers/variant_parser.js.map +1 -1
  73. package/dist/impl/_instantiate_helpers.js +1 -0
  74. package/dist/impl/_instantiate_helpers.js.map +1 -1
  75. package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.js +4 -0
  76. package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.js.map +1 -1
  77. package/dist/impl/alarms_and_conditions/ua_certificate_expiration_alarm_impl.js +14 -0
  78. package/dist/impl/alarms_and_conditions/ua_certificate_expiration_alarm_impl.js.map +1 -1
  79. package/dist/impl/base_node_impl.d.ts +9 -0
  80. package/dist/impl/base_node_impl.js +11 -0
  81. package/dist/impl/base_node_impl.js.map +1 -1
  82. package/dist/impl/base_node_private.js +67 -97
  83. package/dist/impl/base_node_private.js.map +1 -1
  84. package/dist/impl/nodeset_tools/construct_namespace_dependency.js +5 -1
  85. package/dist/impl/nodeset_tools/construct_namespace_dependency.js.map +1 -1
  86. package/dist/impl/nodeset_tools/nodeset_to_records.js +61 -15
  87. package/dist/impl/nodeset_tools/nodeset_to_records.js.map +1 -1
  88. package/dist/impl/nodeset_tools/nodeset_to_xml.d.ts +1 -8
  89. package/dist/impl/nodeset_tools/nodeset_to_xml.js +174 -190
  90. package/dist/impl/nodeset_tools/nodeset_to_xml.js.map +1 -1
  91. package/dist/impl/ua_data_type_impl.d.ts +21 -0
  92. package/dist/impl/ua_data_type_impl.js +27 -2
  93. package/dist/impl/ua_data_type_impl.js.map +1 -1
  94. package/dist/impl/ua_object_type_impl.d.ts +9 -0
  95. package/dist/impl/ua_object_type_impl.js +12 -1
  96. package/dist/impl/ua_object_type_impl.js.map +1 -1
  97. package/dist/impl/ua_variable_impl.js +27 -10
  98. package/dist/impl/ua_variable_impl.js.map +1 -1
  99. package/dist/impl/ua_variable_impl_ext_obj.js +19 -2
  100. package/dist/impl/ua_variable_impl_ext_obj.js.map +1 -1
  101. package/dist/impl/ua_variable_type_impl.d.ts +8 -1
  102. package/dist/impl/ua_variable_type_impl.js +39 -2
  103. package/dist/impl/ua_variable_type_impl.js.map +1 -1
  104. package/dist/tsconfig_base.tsbuildinfo +1 -1
  105. package/distNodeJS/generate_address_space.js +4 -0
  106. package/distNodeJS/generate_address_space.js.map +1 -1
  107. package/impl/_instantiate_helpers.ts +3 -0
  108. package/impl/alarms_and_conditions/ua_acknowledgeable_condition_impl.ts +4 -4
  109. package/impl/alarms_and_conditions/ua_alarm_condition_impl.ts +19 -9
  110. package/impl/alarms_and_conditions/ua_base_event_impl.ts +9 -9
  111. package/impl/alarms_and_conditions/ua_certificate_expiration_alarm_impl.ts +17 -0
  112. package/impl/alarms_and_conditions/ua_condition_impl.ts +18 -18
  113. package/impl/alarms_and_conditions/ua_exclusive_deviation_alarm_impl.ts +2 -2
  114. package/impl/alarms_and_conditions/ua_exclusive_limit_alarm_impl.ts +1 -1
  115. package/impl/alarms_and_conditions/ua_limit_alarm_impl.ts +16 -16
  116. package/impl/alarms_and_conditions/ua_non_exclusive_deviation_alarm_impl.ts +2 -2
  117. package/impl/alarms_and_conditions/ua_non_exclusive_limit_alarm_impl.ts +4 -4
  118. package/impl/alarms_and_conditions/ua_off_normal_alarm_impl.ts +1 -1
  119. package/impl/base_node_impl.ts +15 -0
  120. package/impl/base_node_private.ts +97 -146
  121. package/impl/data_access/ua_multistate_discrete_impl.ts +1 -1
  122. package/impl/data_access/ua_multistate_value_discrete_impl.ts +2 -2
  123. package/impl/data_access/ua_two_state_discrete_impl.ts +2 -2
  124. package/impl/nodeset_tools/construct_namespace_dependency.ts +5 -1
  125. package/impl/nodeset_tools/nodeset_to_records.ts +66 -16
  126. package/impl/nodeset_tools/nodeset_to_xml.ts +127 -160
  127. package/impl/state_machine/finite_state_machine.ts +3 -3
  128. package/impl/state_machine/ua_shelving_state_machine_ex.ts +20 -20
  129. package/impl/ua_data_type_impl.ts +42 -4
  130. package/impl/ua_method_impl.ts +1 -1
  131. package/impl/ua_object_type_impl.ts +16 -3
  132. package/impl/ua_variable_impl.ts +27 -10
  133. package/impl/ua_variable_impl_ext_obj.ts +20 -2
  134. package/impl/ua_variable_type_impl.ts +49 -3
  135. package/package.json +29 -27
  136. package/source_nodejs/generate_address_space.ts +7 -1
@@ -23,12 +23,29 @@ import { type LocalizedText, makeAccessLevelFlag, NodeClass, QualifiedName } fro
23
23
  import { make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
24
24
  import type { ExtensionObject } from "node-opcua-extension-object";
25
25
  import { NodeId, NodeIdType } from "node-opcua-nodeid";
26
+ import { StatusCodes } from "node-opcua-status-code";
26
27
  import { EnumDefinition, StructureDefinition, StructureType } from "node-opcua-types";
27
28
  import { isNullOrUndefined, lowerFirstLetter } from "node-opcua-utils";
28
29
  import { DataType, Variant, VariantArrayType } from "node-opcua-variant";
29
30
  import XMLWriter from "xml-writer";
30
31
  import { makeDefinitionMap } from "../../api/loader/decode_xml_extension_object.js";
31
32
  import type { DefinitionMap2 } from "../../api/loader/make_xml_extension_object_parser.js";
33
+ import {
34
+ _dumpLocalizedText,
35
+ _dumpNodeId,
36
+ _dumpQualifiedName,
37
+ _dumpXmlElement,
38
+ b,
39
+ coerceInt64ToInt32,
40
+ findXsdNamespaceUri,
41
+ getPrefix,
42
+ initXmlWriterEx,
43
+ int64ToDecimalString,
44
+ makeTypeXsd,
45
+ n,
46
+ restoreDefaultNamespace,
47
+ startElementEx
48
+ } from "../../api/loader/nodeset_xml_primitives.js";
32
49
  import { SessionContext } from "../../api/session_context.js";
33
50
  import type { XmlWriter } from "../../api/xml_writer.js";
34
51
  import { BaseNodeImpl, getReferenceType } from "../base_node_impl.js";
@@ -46,6 +63,8 @@ import {
46
63
  } from "./construct_namespace_dependency.js";
47
64
  import { type NodesetWalkEvent, namespaceToWalkEvents, nodeToWalkEvents, sortByNodeId } from "./nodeset_to_records.js";
48
65
 
66
+ export { coerceInt64ToInt32, initXmlWriterEx, makeTypeXsd } from "../../api/loader/nodeset_xml_primitives.js";
67
+
49
68
  const debugLog = make_debugLog("nodeset_to_xml");
50
69
  const warningLog = make_warningLog("nodeset_to_xml");
51
70
  const errorLog = make_errorLog("nodeset_to_xml");
@@ -70,28 +89,6 @@ function _dumpDescription(xw: XmlWriter, node: { description?: LocalizedText }):
70
89
  }
71
90
  }
72
91
 
73
- function translateNodeId(xw: XmlWriter, nodeId: NodeId): NodeId {
74
- assert(nodeId instanceof NodeId);
75
- const nn = xw.translationTable.get(nodeId.namespace);
76
- const translatedNode = new NodeId(nodeId.identifierType, nodeId.value, nn);
77
- return translatedNode;
78
- }
79
-
80
- function n(xw: XmlWriter, nodeId: NodeId): string {
81
- return translateNodeId(xw, nodeId).toString().replace("ns=0;", "");
82
- }
83
-
84
- function translateBrowseName(xw: XmlWriter, browseName: QualifiedName): QualifiedName {
85
- assert(browseName instanceof QualifiedName);
86
- const nn = xw.translationTable.get(browseName.namespaceIndex);
87
- const translatedBrowseName = new QualifiedName({ namespaceIndex: nn, name: browseName.name });
88
- return translatedBrowseName;
89
- }
90
-
91
- function b(xw: XmlWriter, browseName: QualifiedName): string {
92
- return translateBrowseName(xw, browseName).toString().replace("ns=0;", "");
93
- }
94
-
95
92
  function hasHigherPriorityThan(namespaceIndex1: number, namespaceIndex2: number, priorityTable: number[]) {
96
93
  const order1 = priorityTable[namespaceIndex1];
97
94
  const order2 = priorityTable[namespaceIndex2];
@@ -129,6 +126,7 @@ function _dumpReferences(xw: XmlWriter, node: BaseNode) {
129
126
  // below already tolerates it being absent.
130
127
  const connectsToReferenceType = addressSpace.findReferenceType("ConnectsTo");
131
128
  const hasEventSourceReferenceType = _mustFindReferenceType(addressSpace, "HasEventSource");
129
+ const hierarchicalReferencesType = _mustFindReferenceType(addressSpace, "HierarchicalReferences");
132
130
 
133
131
  function referenceToKeep(reference: UAReference): boolean {
134
132
  // resolved here: a reference nobody has looked at since the load carries only the NodeId of its type
@@ -165,10 +163,26 @@ function _dumpReferences(xw: XmlWriter, node: BaseNode) {
165
163
  } else if (referenceType.isSubtypeOf(organizesReferencesType) && !reference.isForward) {
166
164
  // Organizes inverse — the current node is organized by an external folder
167
165
  return true;
166
+ } else if (referenceType.isSubtypeOf(organizesReferencesType) && reference.isForward) {
167
+ // Organizes forward into another namespace: the target lives in a document this export
168
+ // does not write, so nothing else will ever declare this reference and dropping it
169
+ // loses it outright. Within our own namespace the target declares the inverse and this
170
+ // end stays silent, exactly as the Aggregates branch above decides it
171
+ return targetedNamespaceIndex !== node.nodeId.namespace;
168
172
  } else if (connectsToReferenceType && referenceType.isSubtypeOf(connectsToReferenceType) && reference.isForward) {
169
173
  return true;
170
174
  } else if (referenceType.isSubtypeOf(hasEventSourceReferenceType) && reference.isForward) {
171
175
  return true;
176
+ } else if (referenceType.isSubtypeOf(hierarchicalReferencesType)) {
177
+ // a hierarchical reference type the branches above do not name: a companion
178
+ // specification's own, such as Robotics' Requires, Controls and HasSafetyStates. Those
179
+ // fell through to `return false` and were dropped in both directions, so a nodeset that
180
+ // defines its own hierarchical reference type lost every reference that used it.
181
+ // Decided the way Aggregates is decided just above: this end writes the reference
182
+ // unless the other end has the higher priority and will write it instead
183
+ return reference.isForward
184
+ ? !_hasHigherPriorityThan(xw, targetedNamespaceIndex, node.nodeId.namespace)
185
+ : targetedNamespaceIndex !== node.nodeId.namespace;
172
186
  }
173
187
  return false;
174
188
  }
@@ -192,36 +206,6 @@ function _dumpReferences(xw: XmlWriter, node: BaseNode) {
192
206
  }
193
207
  xw.endElement();
194
208
  }
195
- function _dumpLocalizedText(xw: XmlWriter, v: LocalizedText) {
196
- const uax = getPrefix(xw, "http://opcfoundation.org/UA/2008/02/Types.xsd");
197
- if (v.locale?.length) {
198
- xw.startElement(`${uax}Locale`);
199
- xw.text(v.locale);
200
- xw.endElement();
201
- }
202
- xw.startElement(`${uax}Text`);
203
- if (v.text) {
204
- xw.text(v.text);
205
- }
206
- xw.endElement();
207
- }
208
- function _dumpQualifiedName(xw: XmlWriter, v: QualifiedName) {
209
- const uax = getPrefix(xw, "http://opcfoundation.org/UA/2008/02/Types.xsd");
210
- const t = translateBrowseName(xw, v);
211
- if (t.name) {
212
- xw.startElement(`${uax}Name`);
213
- xw.text(t.name);
214
- xw.endElement();
215
- }
216
- if (t.namespaceIndex) {
217
- xw.startElement(`${uax}NamespaceIndex`);
218
- xw.text(t.namespaceIndex.toString());
219
- xw.endElement();
220
- }
221
- }
222
- function _dumpXmlElement(xw: XmlWriter, v: string) {
223
- xw.text(v);
224
- }
225
209
  /*
226
210
  <uax:ExtensionObject>
227
211
  <uax:TypeId>
@@ -239,78 +223,6 @@ function _dumpXmlElement(xw: XmlWriter, v: string) {
239
223
  </uax:Body>
240
224
  </uax:ExtensionObject>
241
225
  */
242
- type XmlNamespaceUri = string;
243
- type NamespaceUri = string;
244
- type XmlNs = string;
245
- interface XmlWriterEx extends XmlWriter {
246
- map: Record<XmlNamespaceUri, XmlNs>;
247
- stackMap: Record<XmlNamespaceUri, XmlNs>[];
248
- namespaceArray: NamespaceUri[];
249
- }
250
- export function initXmlWriterEx(xw: XmlWriter, map: Record<XmlNamespaceUri, XmlNs>, namespaceArray: NamespaceUri[]): void {
251
- const xwe = xw as XmlWriterEx;
252
- xwe.map = map;
253
- xwe.stackMap = [];
254
- xwe.namespaceArray = namespaceArray;
255
- }
256
- function findXsdNamespaceUri(xw: XmlWriter, nodeId: NodeId): string {
257
- const xwe = xw as XmlWriterEx;
258
- if (!xwe.namespaceArray) {
259
- return "";
260
- }
261
- const namespace = xwe.namespaceArray[nodeId.namespace];
262
- if (namespace === "http://opcfoundation.org/UA/") {
263
- return "http://opcfoundation.org/UA/2008/02/Types.xsd";
264
- }
265
- // c8 ignore next
266
- if (!namespace) {
267
- return "";
268
- }
269
- return `${namespace.replace(/\/$/, "")}/Types.xsd`;
270
- }
271
-
272
- function getPrefix(xw: XmlWriter, namespace: XmlNamespaceUri): XmlNs {
273
- const xwe = xw as XmlWriterEx;
274
- if (!xwe.map) return "";
275
- const p = xwe.map[namespace] || "";
276
- return p ? `${p}:` : "";
277
- }
278
-
279
- function restoreDefaultNamespace(xw: XmlWriter) {
280
- const xwe = xw as XmlWriterEx;
281
- if (!xwe.map) return;
282
- const previousMap = xwe.stackMap.pop();
283
- if (previousMap) {
284
- xwe.map = previousMap;
285
- }
286
- }
287
-
288
- function setDefaultNamespace(xw: XmlWriter, namespace: XmlNamespaceUri): void {
289
- const xwe = xw as XmlWriterEx;
290
- if (!xwe.map) return;
291
- if (xwe.map[namespace] !== "") {
292
- xw.writeAttribute("xmlns", namespace);
293
- }
294
-
295
- xwe.stackMap.push({
296
- ...xwe.map
297
- });
298
- xwe.map[namespace] = "";
299
- }
300
-
301
- function startElementEx(xw: XmlWriter, _ns: XmlNs, name: string, defaultNamespace: XmlNamespaceUri) {
302
- const _xwe = xw as XmlWriterEx;
303
- xw.startElement(name);
304
- setDefaultNamespace(xw, defaultNamespace);
305
- }
306
-
307
- function _dumpNodeId(xw: XmlWriter, v: NodeId) {
308
- const xmlns = getPrefix(xw, "http://opcfoundation.org/UA/2008/02/Types.xsd");
309
- xw.startElement(`${xmlns}Identifier`);
310
- xw.text(n(xw, v));
311
- xw.endElement();
312
- }
313
-
314
226
  function _dumpVariantValue(xw: XmlWriter, dataTypeNodeId: NodeId, dataType: DataType, addressSpace: IAddressSpace, value: unknown) {
315
227
  if (value === undefined || value === null) {
316
228
  return;
@@ -449,7 +361,7 @@ function _dumpVariantInnerValue(
449
361
  break;
450
362
  case DataType.Int64:
451
363
  case DataType.UInt64:
452
- xw.text((value as [number, number])[1].toString());
364
+ xw.text(int64ToDecimalString(value as Int64, dataType === DataType.Int64));
453
365
  break;
454
366
  case DataType.Boolean:
455
367
  case DataType.SByte:
@@ -662,7 +574,6 @@ function _dumpValue(xw: XmlWriter, node: UAVariable | UAVariableType, variant: V
662
574
  return;
663
575
  }
664
576
 
665
- const dataTypeName = dataTypeNode.browseName.name?.toString();
666
577
  const baseDataTypeName = DataType[variant.dataType];
667
578
 
668
579
  if (baseDataTypeName === "Null") {
@@ -673,9 +584,13 @@ function _dumpValue(xw: XmlWriter, node: UAVariable | UAVariableType, variant: V
673
584
  // determine if dataTypeName is a ExtensionObject
674
585
  const isExtensionObject = variant.dataType === DataType.ExtensionObject;
675
586
 
676
- if (_isDefaultValue(variant)) {
587
+ // nothing to serialize: a scalar with no value, or an array that is null rather than empty.
588
+ // This is the one thing _isDefaultValue used to catch here that still needs catching -- it is
589
+ // a test for the absence of a value, not for a value that happens to equal a type's default
590
+ if (variant.value === null || variant.value === undefined) {
677
591
  return;
678
592
  }
593
+
679
594
  xw.startElement("Value");
680
595
 
681
596
  const uax = getPrefix(xw, "http://opcfoundation.org/UA/2008/02/Types.xsd");
@@ -702,7 +617,11 @@ function _dumpValue(xw: XmlWriter, node: UAVariable | UAVariableType, variant: V
702
617
  switch (variant.arrayType) {
703
618
  case VariantArrayType.Matrix:
704
619
  case VariantArrayType.Array:
705
- startElementEx(xw, uax, `ListOf${dataTypeName}`, "http://opcfoundation.org/UA/2008/02/Types.xsd");
620
+ // the built-in type, not the DataType's browse name: the uax schema defines
621
+ // ListOfInt32 and ListOfString, and nothing named after a nodeset's own DataType.
622
+ // A `ListOfIdType` is not an element any reader knows, so the reader that meets it
623
+ // walks straight past into the first child and takes the array for a scalar
624
+ startElementEx(xw, uax, `ListOf${baseDataTypeName}`, "http://opcfoundation.org/UA/2008/02/Types.xsd");
706
625
  variant.value.forEach(encodeXml);
707
626
  restoreDefaultNamespace(xw);
708
627
  xw.endElement();
@@ -720,12 +639,15 @@ function _dumpValue(xw: XmlWriter, node: UAVariable | UAVariableType, variant: V
720
639
  }
721
640
 
722
641
  function _dumpArrayDimensionsAttribute(xw: XmlWriter, node: UAVariableType | UAVariable) {
723
- if (node.arrayDimensions) {
724
- if (node.valueRank === -1 || (node.arrayDimensions.length === 1 && node.arrayDimensions[0] === 0)) {
725
- return;
726
- }
727
- xw.writeAttribute("ArrayDimensions", node.arrayDimensions.join(","));
728
- }
642
+ // ArrayDimensions="0" is written only when the document declared it: node-opcua synthesizes [0]
643
+ // on the variables it generates, so writing it unconditionally would invent the attribute on
644
+ // every generated node. See arrayDimensionsWereDeclared in nodeset_record_applier
645
+ if (!node.arrayDimensions || node.valueRank <= 0) return;
646
+ // all-zero dimensions are what node-opcua synthesizes when nobody declared any, at whatever
647
+ // rank: [0] for a one-dimensional value, [0,0] for a matrix
648
+ const degenerate = node.arrayDimensions.every((d) => d === 0);
649
+ if (degenerate && !(node as unknown as { arrayDimensionsWereDeclared?: boolean }).arrayDimensionsWereDeclared) return;
650
+ xw.writeAttribute("ArrayDimensions", node.arrayDimensions.join(","));
729
651
  }
730
652
 
731
653
  function getParent(node: BaseNode): BaseNode | null {
@@ -749,6 +671,10 @@ function dumpCommonAttributes(xw: XmlWriter, node: BaseNode) {
749
671
  if (Object.hasOwn(node, "symbolicName")) {
750
672
  xw.writeAttribute("SymbolicName", (node as unknown as { symbolicName: string }).symbolicName);
751
673
  }
674
+ // Released is the default and is left unwritten; the other two the document declared
675
+ if (node.releaseStatus) {
676
+ xw.writeAttribute("ReleaseStatus", node.releaseStatus);
677
+ }
752
678
  if (Object.hasOwn(node, "isAbstract")) {
753
679
  const isAbstract = (node as unknown as { isAbstract: boolean }).isAbstract;
754
680
  if (isAbstract) {
@@ -773,8 +699,11 @@ function dumpCommonAttributes(xw: XmlWriter, node: BaseNode) {
773
699
  }
774
700
  // access policy: undefined means "inherit from the namespace", an empty rolePermissions array
775
701
  // means "this node deliberately grants nothing".
776
- if (node.accessRestrictions !== undefined) {
777
- xw.writeAttribute("AccessRestrictions", node.accessRestrictions.toString());
702
+ // what the document declared, which survives whatever the loader was asked to enforce; the
703
+ // enforced flag is the fallback for a node built in code rather than loaded
704
+ const accessRestrictions = node.declaredAccessRestrictions ?? node.accessRestrictions?.toString();
705
+ if (accessRestrictions !== undefined) {
706
+ xw.writeAttribute("AccessRestrictions", accessRestrictions);
778
707
  }
779
708
  if (node.rolePermissions !== undefined && node.rolePermissions.length === 0) {
780
709
  xw.writeAttribute("HasNoPermissions", "true");
@@ -785,6 +714,19 @@ function dumpCommonAttributes(xw: XmlWriter, node: BaseNode) {
785
714
  xw.writeAttribute("MinimumSamplingInterval", minimumSamplingInterval);
786
715
  }
787
716
  }
717
+ // EventNotifier lives on UAObject, UAObjectType and UAView in the XSD. 0 is the default and is
718
+ // left unwritten; anything else the document declared. It was written nowhere at all until now,
719
+ // so every node that declared one lost it on the way out
720
+ if (node.nodeClass === NodeClass.Object || node.nodeClass === NodeClass.ObjectType || node.nodeClass === NodeClass.View) {
721
+ // typeof, not truthiness: a node with a child called "EventNotifier" resolves the name to
722
+ // that child through the shared child accessors, so `node.eventNotifier` is a BaseNode
723
+ // there and not the attribute. PublishedEventsType is one, and stringifying it wrote a
724
+ // whole node dump into the attribute
725
+ const eventNotifier = (node as unknown as { eventNotifier?: unknown }).eventNotifier;
726
+ if (typeof eventNotifier === "number" && eventNotifier !== 0) {
727
+ xw.writeAttribute("EventNotifier", eventNotifier.toString());
728
+ }
729
+ }
788
730
  // Historizing exists in the XSD on UAVariable only: UAVariableType is restricted to
789
731
  // DataType/ValueRank/ArrayDimensions. UAVariableTypeImpl nevertheless owns a `historizing`
790
732
  // property, so the node class must be tested here rather than the presence of the property.
@@ -819,19 +761,6 @@ function dumpCommonElements(xw: XmlWriter, node: BaseNode) {
819
761
  _dumpRolePermissions(xw, node);
820
762
  }
821
763
 
822
- export function coerceInt64ToInt32(int64: Int64): number {
823
- if (typeof int64 === "number") {
824
- return int64 as number;
825
- }
826
- if (int64[0] === 0xffffffff && int64[1] === 0xffffffff) {
827
- return 0xffffffff;
828
- }
829
- if (int64[0] !== 0) {
830
- warningLog("coerceInt64ToInt32 , loosing high word in conversion");
831
- }
832
- return int64[1];
833
- }
834
-
835
764
  function _dumpEnumDefinition(xw: XmlWriter, enumDefinition: EnumDefinition) {
836
765
  enumDefinition.fields = enumDefinition.fields || [];
837
766
 
@@ -860,6 +789,12 @@ function _dumpStructureDefinition(
860
789
  // do not repeat elements that are already defined in base structure in the xml ouput!
861
790
  const fields = structureDefinition.fields || [];
862
791
  const nbFieldsInBase: number = baseStructureDefinition ? baseStructureDefinition.fields?.length || 0 : 0;
792
+ // Part 3 8.51: in a *WithSubtypedValues structure, isOptional is where "subtyping allowed" is
793
+ // held, and the document spells that AllowSubTypes. Written as IsOptional it said the opposite
794
+ // of what it meant, and the AllowSubTypes the document had was simply gone
795
+ const subtypedValues =
796
+ structureDefinition.structureType === StructureType.StructureWithSubtypedValues ||
797
+ structureDefinition.structureType === StructureType.UnionWithSubtypedValues;
863
798
 
864
799
  for (let index = nbFieldsInBase; index < fields.length; index++) {
865
800
  const defItem = fields[index];
@@ -872,8 +807,8 @@ function _dumpStructureDefinition(
872
807
  if (defItem.valueRank !== undefined && defItem.valueRank !== -1) {
873
808
  xw.writeAttribute("ValueRank", defItem.valueRank);
874
809
  }
875
- if (defItem.isOptional /* && defItem.isOptional !== false */) {
876
- xw.writeAttribute("IsOptional", defItem.isOptional.toString());
810
+ if (defItem.isOptional) {
811
+ xw.writeAttribute(subtypedValues ? "AllowSubTypes" : "IsOptional", "true");
877
812
  }
878
813
  if (defItem.maxStringLength !== undefined && defItem.maxStringLength !== 0) {
879
814
  xw.writeAttribute("MaxStringLength", defItem.maxStringLength);
@@ -892,9 +827,37 @@ function _dumpStructureDefinition(
892
827
  function _dumpUADataTypeDefinition(xw: XmlWriter, uaDataType: UADataType) {
893
828
  const uaDataTypeBase = uaDataType.subtypeOfObj;
894
829
 
830
+ // an OptionSet first: node-opcua builds neither an enumeration nor a structure for one, so
831
+ // neither branch below would fire and the whole <Definition> would be dropped. The fields are
832
+ // the ones the loader kept, see UADataTypeImpl.isOptionSetDataType
833
+ const asImpl = uaDataType as unknown as {
834
+ isOptionSetDataType?: boolean;
835
+ declaredDefinitionFields?: Array<{ name?: string | null; value?: number; description?: unknown }>;
836
+ declaredDefinitionName?: string;
837
+ };
838
+ // what the document called this definition, or -- for a DataType built here, which never had a
839
+ // declaration -- the browse name, which is the only name available
840
+ const definitionName = asImpl.declaredDefinitionName ?? b(xw, uaDataType.browseName);
841
+ if (asImpl.isOptionSetDataType && asImpl.declaredDefinitionFields) {
842
+ xw.startElement("Definition");
843
+ xw.writeAttribute("Name", definitionName);
844
+ xw.writeAttribute("IsOptionSet", "true");
845
+ for (const field of asImpl.declaredDefinitionFields) {
846
+ xw.startElement("Field");
847
+ xw.writeAttribute("Name", field.name || "");
848
+ if (field.value !== undefined && field.value !== null) {
849
+ xw.writeAttribute("Value", field.value);
850
+ }
851
+ _dumpDescription(xw, field as { description?: LocalizedText });
852
+ xw.endElement();
853
+ }
854
+ xw.endElement();
855
+ return;
856
+ }
857
+
895
858
  if (uaDataType.isEnumeration()) {
896
859
  xw.startElement("Definition");
897
- xw.writeAttribute("Name", b(xw, uaDataType.browseName));
860
+ xw.writeAttribute("Name", definitionName);
898
861
  _dumpEnumDefinition(xw, uaDataType.getEnumDefinition());
899
862
  xw.endElement();
900
863
  return;
@@ -909,9 +872,11 @@ function _dumpUADataTypeDefinition(xw: XmlWriter, uaDataType: UADataType) {
909
872
  const t = true;
910
873
  if (t || dataValue.statusCode.isGood()) {
911
874
  const definition = uaDataType.getStructureDefinition();
912
- const baseDefinition = uaDataTypeBase ? uaDataTypeBase.getStructureDefinition() : null;
875
+ // a structure's supertype need not be a structure: `Structure` itself subtypes BaseDataType,
876
+ // which has no definition at all. Asking it for one threw, and took the whole export with it
877
+ const baseDefinition = uaDataTypeBase?.isStructure() ? uaDataTypeBase.getStructureDefinition() : null;
913
878
  xw.startElement("Definition");
914
- xw.writeAttribute("Name", b(xw, uaDataType.browseName));
879
+ xw.writeAttribute("Name", definitionName);
915
880
  if (definition.structureType === StructureType.Union) {
916
881
  xw.writeAttribute("IsUnion", "true");
917
882
  }
@@ -993,8 +958,14 @@ function dumpUAVariable(xw: XmlWriter, node: UAVariable) {
993
958
  // sub elements
994
959
  dumpCommonElements(xw, node);
995
960
 
996
- const value = (node as UAVariableImpl).$dataValue.value;
997
- if (value) {
961
+ // a value the loader made up for a variable the document left unvalued is not written
962
+ // back out; see valueWasSynthesized in nodeset_record_applier. What used to stand here was
963
+ // _isDefaultValue, which dropped every zero and every empty string alike -- suppressing the
964
+ // made-up ones at the cost of the ones the document really did declare
965
+ const synthesized = (node as unknown as { valueWasSynthesized?: boolean }).valueWasSynthesized;
966
+ const dataValue = (node as UAVariableImpl).$dataValue;
967
+ const value = dataValue.value;
968
+ if (value && !synthesized && !dataValue.statusCode.equals(StatusCodes.BadWaitingForInitialData)) {
998
969
  _dumpValue(xw, node, value);
999
970
  }
1000
971
  }
@@ -1236,10 +1207,6 @@ BaseNodeImpl.prototype.dumpXML = function (this: BaseNodeImpl, xw: XmlWriter) {
1236
1207
  writeWalkEvents(xw, this.addressSpace, translationTable, events);
1237
1208
  };
1238
1209
 
1239
- export function makeTypeXsd(namespaceUri: string): string {
1240
- return `${namespaceUri.replace(/\/$/, "")}/Type.xsd`;
1241
- }
1242
-
1243
1210
  NamespaceImpl.prototype.toNodeset2XML = function (this: NamespaceImpl) {
1244
1211
  const namespaceArrayNode = this.addressSpace.findNode(VariableIds.Server_NamespaceArray);
1245
1212
  const namespaceArray: string[] = namespaceArrayNode
@@ -38,7 +38,7 @@ const debugLog = make_debugLog("finite_state_machine");
38
38
 
39
39
  export class UATransitionImplBase extends UAObjectImpl implements UATransitionEx {
40
40
  /** installed as a child node by the address space, not assigned here - hence `declare` */
41
- public declare readonly transitionNumber: UAProperty<UInt32, DataType.UInt32>;
41
+ declare public readonly transitionNumber: UAProperty<UInt32, DataType.UInt32>;
42
42
  /* intentionnaly empty */
43
43
  }
44
44
  export type UATransitionImpl = UATransitionImplBase;
@@ -137,8 +137,8 @@ export class UAStateMachineImplBase extends UAObjectImpl implements UAStateMachi
137
137
  * cached state this class maintains. Neither is assigned by the constructor - hence
138
138
  * `declare`, which emits nothing.
139
139
  */
140
- public declare readonly currentState: UAStateVariable<LocalizedText>;
141
- public declare _currentStateNode: UAState | null;
140
+ declare public readonly currentState: UAStateVariable<LocalizedText>;
141
+ declare public _currentStateNode: UAState | null;
142
142
 
143
143
  public getStates(): UAState[] {
144
144
  const typeDef = this.typeDefinitionObj;
@@ -55,26 +55,26 @@ export class UAShelvedStateMachineExImplBase extends UAStateMachineImpl implemen
55
55
  * shelving timer this class keeps. None is assigned by the constructor - hence `declare`,
56
56
  * which emits nothing.
57
57
  */
58
- public declare readonly unshelveTime: UAProperty<number, DataType.Double>;
59
- public declare readonly unshelved: UAState;
60
- public declare readonly timedShelved: UAState;
61
- public declare readonly oneShotShelved: UAState;
62
- public declare readonly unshelvedToTimedShelved: UATransitionEx;
63
- public declare readonly unshelvedToOneShotShelved: UATransitionEx;
64
- public declare readonly timedShelvedToUnshelved: UATransitionEx;
65
- public declare readonly timedShelvedToOneShotShelved: UATransitionEx;
66
- public declare readonly oneShotShelvedToUnshelved: UATransitionEx;
67
- public declare readonly oneShotShelvedToTimedShelved: UATransitionEx;
68
- public declare readonly timedShelve: UAMethod;
69
- public declare readonly timedShelve2?: UAMethod;
70
- public declare readonly unshelve: UAMethod;
71
- public declare readonly unshelve2?: UAMethod;
72
- public declare readonly oneShotShelve: UAMethod;
73
- public declare readonly oneShotShelve2?: UAMethod;
74
- public declare _timer: NodeJS.Timeout | null;
75
- public declare _shelvedTime: Date;
76
- public declare _unshelvedTime: Date;
77
- public declare _duration: number;
58
+ declare public readonly unshelveTime: UAProperty<number, DataType.Double>;
59
+ declare public readonly unshelved: UAState;
60
+ declare public readonly timedShelved: UAState;
61
+ declare public readonly oneShotShelved: UAState;
62
+ declare public readonly unshelvedToTimedShelved: UATransitionEx;
63
+ declare public readonly unshelvedToOneShotShelved: UATransitionEx;
64
+ declare public readonly timedShelvedToUnshelved: UATransitionEx;
65
+ declare public readonly timedShelvedToOneShotShelved: UATransitionEx;
66
+ declare public readonly oneShotShelvedToUnshelved: UATransitionEx;
67
+ declare public readonly oneShotShelvedToTimedShelved: UATransitionEx;
68
+ declare public readonly timedShelve: UAMethod;
69
+ declare public readonly timedShelve2?: UAMethod;
70
+ declare public readonly unshelve: UAMethod;
71
+ declare public readonly unshelve2?: UAMethod;
72
+ declare public readonly oneShotShelve: UAMethod;
73
+ declare public readonly oneShotShelve2?: UAMethod;
74
+ declare public _timer: NodeJS.Timeout | null;
75
+ declare public _shelvedTime: Date;
76
+ declare public _unshelvedTime: Date;
77
+ declare public _duration: number;
78
78
 
79
79
  public static promote(object: UAObject): UAShelvedStateMachineEx {
80
80
  const shelvingState = object as UAShelvedStateMachineExImpl;
@@ -52,6 +52,10 @@ export interface EnumerationInfo {
52
52
  export interface UADataTypeOptions extends InternalBaseNodeOptions {
53
53
  partialDefinition: StructureFieldOptions[] | EnumFieldOptions[];
54
54
  isUnion?: boolean;
55
+ /** the DataType is a bit mask, not an enumeration; see UADataTypeImpl.isOptionSet */
56
+ isOptionSet?: boolean;
57
+ /** the <Definition> Name as declared; see UADataTypeImpl.declaredDefinitionName */
58
+ definitionName?: string;
55
59
  isAbstract?: boolean;
56
60
  symbolicName?: string;
57
61
  }
@@ -89,16 +93,42 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
89
93
  // the EnumStrings / EnumValues children, reached through the child accessors: declared for the
90
94
  // type checker only, since an emitted class field would sit on the instance and hide the
91
95
  // accessor inherited from the prototype
92
- private declare enumStrings?: UAVariable;
93
- private declare enumValues?: UAVariable;
96
+ declare private enumStrings?: UAVariable;
97
+ declare private enumValues?: UAVariable;
94
98
  private $partialDefinition?: StructureFieldOptionsEx[] | EnumFieldOptions[];
95
99
  private $fullDefinition?: StructureDefinition | EnumDefinition;
96
100
 
101
+ /**
102
+ * whether the nodeset declared this DataType with `IsOptionSet="true"`: a bit mask whose fields
103
+ * are bit positions rather than an enumeration whose fields are values. node-opcua builds no
104
+ * enumeration and no structure for one -- it is a plain UInt16 or UInt32 subtype -- so without
105
+ * this flag and the fields kept in $partialDefinition, an OptionSet that goes into the address
106
+ * space cannot come out again as the DataType the document declared.
107
+ */
108
+ public isOptionSetDataType = false;
109
+
110
+ /**
111
+ * the Name attribute of the <Definition> element as the nodeset spelled it. The published
112
+ * catalogue uses both spellings -- 467 bare names and 211 namespace-prefixed ones -- so there
113
+ * is no form to normalise to: recomputing it from the browse name rewrote every bare one.
114
+ * Undefined for a DataType this address space built itself, which has no declaration to keep.
115
+ */
116
+ public declaredDefinitionName?: string;
117
+
118
+ /** the fields of the <Definition> as declared, for a DataType the address space models no further */
119
+ public get declaredDefinitionFields(): StructureFieldOptionsEx[] | EnumFieldOptions[] | undefined {
120
+ return this.$partialDefinition;
121
+ }
122
+
97
123
  constructor(options: UADataTypeOptions) {
98
124
  super(options);
99
125
  if (options.partialDefinition) {
100
126
  this.$partialDefinition = options.partialDefinition;
101
127
  }
128
+ this.isOptionSetDataType = !!options.isOptionSet;
129
+ if (options.definitionName) {
130
+ this.declaredDefinitionName = options.definitionName;
131
+ }
102
132
  this.isAbstract = options.isAbstract === undefined || options.isAbstract === null ? false : options.isAbstract;
103
133
  this.symbolicName = options.symbolicName || this.browseName.name || "";
104
134
  }
@@ -261,7 +291,12 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
261
291
  }
262
292
  getStructureDefinition(): StructureDefinition {
263
293
  const definition = this._getDefinition();
264
- assert(definition instanceof StructureDefinition);
294
+ // named: a bare assert here threw an Error with an empty message, which told a caller
295
+ // nothing at all about which DataType had refused and what it had instead
296
+ assert(
297
+ definition instanceof StructureDefinition,
298
+ `${this.browseName.toString()} (${this.nodeId.toString()}) has no StructureDefinition: ${definition ? definition.constructor.name : "none"}`
299
+ );
265
300
  return definition as StructureDefinition;
266
301
  }
267
302
 
@@ -271,7 +306,10 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
271
306
  }
272
307
  getEnumDefinition(): EnumDefinition {
273
308
  const definition = this._getDefinition();
274
- assert(definition instanceof EnumDefinition);
309
+ assert(
310
+ definition instanceof EnumDefinition,
311
+ `${this.browseName.toString()} (${this.nodeId.toString()}) has no EnumDefinition: ${definition ? definition.constructor.name : "none"}`
312
+ );
275
313
  return definition as EnumDefinition;
276
314
  }
277
315
 
@@ -64,7 +64,7 @@ export class UAMethodImpl extends BaseNodeImpl<UAMethodEvents> implements UAMeth
64
64
  return super.parent as UAObject;
65
65
  }
66
66
 
67
- public declare value?: unknown;
67
+ declare public value?: unknown;
68
68
  public methodDeclarationId: NodeId;
69
69
  public _getExecutableFlag?: (this: UAMethod, context: ISessionContext | null) => boolean;
70
70
 
@@ -4,12 +4,12 @@
4
4
 
5
5
  import type {
6
6
  AddObjectOptions,
7
+ AddReferenceOpts,
7
8
  BaseNodeEvents,
8
9
  InstantiateObjectOptions,
9
10
  ISessionContext,
10
11
  UAObject,
11
- UAObjectType,
12
- UAReference
12
+ UAObjectType
13
13
  } from "node-opcua-address-space-base";
14
14
  import { assert } from "node-opcua-assert";
15
15
  import { AttributeIds, type LocalizedTextLike, NodeClass } from "node-opcua-data-model";
@@ -28,11 +28,20 @@ import { assertUnusedChildBrowseName, topMostParentIsObjectTypeOrVariableType }
28
28
 
29
29
  export interface UAObjectTypeOptions extends InternalBaseNodeOptions {
30
30
  isAbstract?: boolean | null;
31
+ /** the EventNotifier the nodeset declared; see UAObjectTypeImpl.eventNotifier */
32
+ eventNotifier?: number;
31
33
  }
32
34
 
33
35
  export class UAObjectTypeImpl extends BaseNodeImpl<BaseNodeEvents> implements UAObjectType {
34
36
  public readonly nodeClass = NodeClass.ObjectType;
35
37
  public readonly isAbstract: boolean;
38
+ /**
39
+ * the EventNotifier the nodeset declared. The XSD allows it on a UAObjectType and nodesets use
40
+ * it, but nothing here held it: the attribute was dropped at load, and -- because the name was
41
+ * then free -- a child called EventNotifier claimed `eventNotifier` through the shared child
42
+ * accessors, so reading it handed back a BaseNode. PublishedEventsType is such a type.
43
+ */
44
+ public readonly eventNotifier: number;
36
45
  /**
37
46
  * returns true if the object has some opcua methods
38
47
  */
@@ -54,6 +63,7 @@ export class UAObjectTypeImpl extends BaseNodeImpl<BaseNodeEvents> implements UA
54
63
  constructor(options: UAObjectTypeOptions) {
55
64
  super(options);
56
65
  this.isAbstract = options.isAbstract ?? false;
66
+ this.eventNotifier = options.eventNotifier ?? 0;
57
67
  }
58
68
 
59
69
  public readAttribute(context: ISessionContext, attributeId: AttributeIds): DataValue {
@@ -118,7 +128,10 @@ export class UAObjectTypeImpl extends BaseNodeImpl<BaseNodeEvents> implements UA
118
128
  const baseObjectType = addressSpace.findObjectType("BaseObjectType") as UAObjectType;
119
129
  assert(baseObjectType, "BaseObjectType must be defined in the address space");
120
130
 
121
- const references: UAReference[] = [];
131
+ // caller-supplied references (typically an inverse aggregate reference to a
132
+ // parent that has no dedicated option, e.g. HasOrderedComponent) are created
133
+ // with the node, so the NodeIdManager sees the parent when naming it.
134
+ const references: AddReferenceOpts[] = options.references ? [...options.references] : [];
122
135
 
123
136
  const copyAlsoModellingRules = topMostParentIsObjectTypeOrVariableType(addressSpace, options);
124
137