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
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.narrowInstanceDataTypes = narrowInstanceDataTypes;
4
+ const node_opcua_data_model_1 = require("node-opcua-data-model");
5
+ const node_opcua_debug_1 = require("node-opcua-debug");
6
+ const node_opcua_nodeid_1 = require("node-opcua-nodeid");
7
+ const node_opcua_variant_1 = require("node-opcua-variant");
8
+ const doDebug = (0, node_opcua_debug_1.checkDebugFlag)("narrow_instance_datatypes");
9
+ const debugLog = (0, node_opcua_debug_1.make_debugLog)("narrow_instance_datatypes");
10
+ function isType(node) {
11
+ return node.nodeClass === node_opcua_data_model_1.NodeClass.ObjectType || node.nodeClass === node_opcua_data_model_1.NodeClass.VariableType;
12
+ }
13
+ function isInstance(node) {
14
+ return node.nodeClass === node_opcua_data_model_1.NodeClass.Object || node.nodeClass === node_opcua_data_model_1.NodeClass.Variable;
15
+ }
16
+ /**
17
+ * the type definition of an instance, resolved without the ObjectType/VariableType guards of the
18
+ * typed getters: a companion nodeset with a wrong type definition must not abort the sweep
19
+ */
20
+ function typeDefinitionOf(addressSpace, node) {
21
+ const typeDefinition = node.typeDefinition;
22
+ if (!typeDefinition || typeDefinition.isEmpty()) {
23
+ return null;
24
+ }
25
+ const typeNode = addressSpace.findNode(typeDefinition);
26
+ return typeNode && isType(typeNode) ? typeNode : null;
27
+ }
28
+ /**
29
+ * the members a type declares, by browse name, looked up along its supertype chain and its
30
+ * interfaces the way `instantiate` collects them (see impl/_instantiate_helpers.ts); the answer
31
+ * is cached per (type, browse name) so a sweep over thousands of instances of the same type
32
+ * walks the chain once per member
33
+ */
34
+ class DeclarationIndex {
35
+ addressSpace;
36
+ cache = new Map();
37
+ /** absent from a standard nodeset older than 1.04: then no type has interfaces to look into */
38
+ hasInterface;
39
+ hasModellingRule;
40
+ constructor(addressSpace, hasModellingRule) {
41
+ this.addressSpace = addressSpace;
42
+ this.hasInterface = addressSpace.findReferenceType("HasInterface") ?? null;
43
+ this.hasModellingRule = hasModellingRule;
44
+ }
45
+ /**
46
+ * whether `declared` is a member declaration a variable instance must honour: a variable with
47
+ * a modelling rule (Mandatory, Optional, ...). A type child without one is not instantiated by
48
+ * Part 3 and is left alone.
49
+ */
50
+ isVariableDeclaration(declared) {
51
+ return (declared.nodeClass === node_opcua_data_model_1.NodeClass.Variable &&
52
+ declared.findReferencesEx(this.hasModellingRule, node_opcua_data_model_1.BrowseDirection.Forward).length > 0);
53
+ }
54
+ find(type, browseName) {
55
+ const key = `${type.nodeId.toString()}|${browseName.toString()}`;
56
+ const cached = this.cache.get(key);
57
+ if (cached !== undefined) {
58
+ return cached;
59
+ }
60
+ const found = this.walk(type, browseName, new Set());
61
+ this.cache.set(key, found);
62
+ return found;
63
+ }
64
+ walk(type, browseName, visited) {
65
+ const typeKey = type.nodeId.toString();
66
+ if (visited.has(typeKey)) {
67
+ return null;
68
+ }
69
+ visited.add(typeKey);
70
+ const child = type.getChildByName(browseName);
71
+ if (child) {
72
+ return child;
73
+ }
74
+ const interfaces = this.hasInterface ? type.findReferencesEx(this.hasInterface, node_opcua_data_model_1.BrowseDirection.Forward) : [];
75
+ for (const reference of interfaces) {
76
+ const interfaceType = reference.node ?? this.addressSpace.findNode(reference.nodeId);
77
+ if (interfaceType && isType(interfaceType)) {
78
+ const declared = this.walk(interfaceType, browseName, visited);
79
+ if (declared) {
80
+ return declared;
81
+ }
82
+ }
83
+ }
84
+ const superType = type.subtypeOfObj;
85
+ return superType ? this.walk(superType, browseName, visited) : null;
86
+ }
87
+ }
88
+ // a parent chain deeper than this is a cycle in a broken nodeset, not a model
89
+ const MAX_DEPTH = 64;
90
+ /**
91
+ * the node of a type definition that declares this instance member, or null when no type in
92
+ * the instance's ancestry declares one under its browse name.
93
+ *
94
+ * The declaration is looked up in the type definition of the containing instance first; a
95
+ * member nested below a declared member (an object declared by the type with children of its
96
+ * own, whose own type definition says nothing about them) is found through the declaration of
97
+ * its container instead.
98
+ */
99
+ function declarationOf(addressSpace, index, node, depth) {
100
+ if (depth > MAX_DEPTH) {
101
+ return null;
102
+ }
103
+ const parent = node.parent;
104
+ // a node under a type is a declaration itself, never an instance to narrow
105
+ if (!parent || isType(parent)) {
106
+ return null;
107
+ }
108
+ const parentIsInstance = isInstance(parent);
109
+ if (parentIsInstance) {
110
+ const parentType = typeDefinitionOf(addressSpace, parent);
111
+ if (parentType) {
112
+ const declared = index.find(parentType, node.browseName);
113
+ if (declared) {
114
+ return declared;
115
+ }
116
+ }
117
+ }
118
+ // the container may itself be a declared member (an instance, or a method) whose declaration
119
+ // carries the member; a method has no type definition and is resolved the same way
120
+ if (parentIsInstance || parent.nodeClass === node_opcua_data_model_1.NodeClass.Method) {
121
+ const parentDeclaration = declarationOf(addressSpace, index, parent, depth + 1);
122
+ if (parentDeclaration) {
123
+ return parentDeclaration.getChildByName(node.browseName);
124
+ }
125
+ }
126
+ return null;
127
+ }
128
+ function findDataTypeNode(addressSpace, nodeId) {
129
+ if (!nodeId || nodeId.isEmpty()) {
130
+ return null;
131
+ }
132
+ return addressSpace.findDataType(nodeId) ?? null;
133
+ }
134
+ /**
135
+ * make every instance variable's DataType at least as narrow as its declaration.
136
+ *
137
+ * OPC 10000-3 lets an instance use the DataType its type definition declares for the member, or
138
+ * a subtype of it; a supertype is not conformant. Companion nodesets that predate a narrowing
139
+ * of the standard types carry the supertype anyway (every NamespaceMetadata object written
140
+ * against UA 1.04 declares `ModelVersion` as String, which UA 1.05 narrowed to
141
+ * SemanticVersionString), so every server loading them fails the type-conformance check of
142
+ * the CTT (Base Info Core Structure) and no server can fix it per nodeset. The declared type is
143
+ * what the model means: an instance whose DataType is a strict supertype of the declared one is
144
+ * moved to the declared one.
145
+ *
146
+ * Nothing else moves: an instance whose DataType is the declared one, a subtype of it, or
147
+ * unrelated is left alone, values are never touched, and the children of types are declarations,
148
+ * not instances. A variable whose current value would not fit the declared built-in type is
149
+ * left alone as well, and reported: narrowing it would make the value lie about its type.
150
+ *
151
+ * @returns the variables that were narrowed
152
+ */
153
+ function narrowInstanceDataTypes(addressSpace) {
154
+ const narrowed = [];
155
+ // a nodeset without these cannot declare a member, let alone the DataType of one: a
156
+ // minimal fixture, or a standard nodeset still to come. Nothing to narrow, and the lookups
157
+ // below would throw on the first missing reference type.
158
+ const hasModellingRule = addressSpace.findReferenceType("HasModellingRule");
159
+ if (!hasModellingRule ||
160
+ !addressSpace.findReferenceType("HasSubtype") ||
161
+ !addressSpace.findReferenceType("HasChild") ||
162
+ !addressSpace.findReferenceType("HierarchicalReferences")) {
163
+ return narrowed;
164
+ }
165
+ const index = new DeclarationIndex(addressSpace, hasModellingRule);
166
+ for (const namespace of addressSpace.getNamespaceArray()) {
167
+ for (const node of namespace.nodeIterator()) {
168
+ if (node.nodeClass !== node_opcua_data_model_1.NodeClass.Variable) {
169
+ continue;
170
+ }
171
+ const variable = node;
172
+ const declared = declarationOf(addressSpace, index, variable, 0);
173
+ if (!declared || !index.isVariableDeclaration(declared)) {
174
+ continue;
175
+ }
176
+ if ((0, node_opcua_nodeid_1.sameNodeId)(variable.dataType, declared.dataType)) {
177
+ continue;
178
+ }
179
+ const instanceDataType = findDataTypeNode(addressSpace, variable.dataType);
180
+ const declaredDataType = findDataTypeNode(addressSpace, declared.dataType);
181
+ if (!instanceDataType || !declaredDataType) {
182
+ continue;
183
+ }
184
+ // a subtype of the declaration, or an unrelated type: not this sweep's business
185
+ if (!declaredDataType.isSubtypeOf(instanceDataType)) {
186
+ continue;
187
+ }
188
+ const value = variable.readValue().value;
189
+ const declaredBuiltInType = declaredDataType.basicDataType;
190
+ const valueFits = value.dataType === node_opcua_variant_1.DataType.Null ||
191
+ declaredBuiltInType === node_opcua_variant_1.DataType.Variant ||
192
+ value.dataType === declaredBuiltInType;
193
+ if (!valueFits) {
194
+ debugLog(`not narrowing ${variable.nodeId.toString()} ${variable.browseName.toString()}: ` +
195
+ `its value is a ${node_opcua_variant_1.DataType[value.dataType]}, the declared ${declaredDataType.browseName.toString()} ` +
196
+ `is a ${node_opcua_variant_1.DataType[declaredBuiltInType]}`);
197
+ continue;
198
+ }
199
+ const from = variable.dataType;
200
+ variable.dataType = declaredDataType.nodeId;
201
+ variable._basicDataType = null;
202
+ narrowed.push({ node: variable, from, to: declaredDataType.nodeId });
203
+ // c8 ignore next
204
+ if (doDebug) {
205
+ debugLog(`narrowed the DataType of ${variable.nodeId.toString()} ${variable.browseName.toString()} ` +
206
+ `from ${instanceDataType.browseName.toString()} (${from.toString()}) ` +
207
+ `to the declared ${declaredDataType.browseName.toString()} (${declaredDataType.nodeId.toString()})`);
208
+ }
209
+ }
210
+ }
211
+ // c8 ignore next
212
+ if (doDebug && narrowed.length > 0) {
213
+ debugLog(`narrowed the DataType of ${narrowed.length} instance variable(s) to the one their type declares`);
214
+ }
215
+ return narrowed;
216
+ }
217
+ //# sourceMappingURL=narrow_instance_datatypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"narrow_instance_datatypes.js","sourceRoot":"","sources":["../../../api/loader/narrow_instance_datatypes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @module node-opcua-address-space
3
+ *
4
+ * The two serialisations the loader has always read, expressed as {@link NodesetFormat}s.
5
+ *
6
+ * Registering them here rather than branching on a boolean inside the loader is the whole point
7
+ * of the registry: the built-ins get no privilege a third-party format does not also get, so the
8
+ * contract cannot quietly grow a dependency on something only the built-ins can reach. If these
9
+ * two can be written against it, so can a fourth.
10
+ *
11
+ * Importing this module registers them. The loader does that once, at its own import.
12
+ */
13
+ /** the name the NodeSet2 XML format is registered under */
14
+ export declare const NODESET2_XML_FORMAT = "nodeset2-xml";
15
+ /** the name the NDJSON image format is registered under */
16
+ export declare const NDJSON_IMAGE_FORMAT = "ndjson";
17
+ /**
18
+ * whether this line is the header record of an NDJSON image.
19
+ *
20
+ * Deliberately a string test before it is a parse: the discriminating fields are written first
21
+ * and the line can be hundreds of kilobytes, so there is no reason to build an object to answer
22
+ * a question the first thirty characters already settle. The parse is the confirmation, not the
23
+ * test, and it is only reached for a line that already looks like ours.
24
+ */
25
+ export declare function isNdjsonHeaderLine(line: string): boolean;
26
+ /** register the built-in formats; called by the loader, and safe to call more than once */
27
+ export declare function registerBuiltinNodesetFormats(): void;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ /**
3
+ * @module node-opcua-address-space
4
+ *
5
+ * The two serialisations the loader has always read, expressed as {@link NodesetFormat}s.
6
+ *
7
+ * Registering them here rather than branching on a boolean inside the loader is the whole point
8
+ * of the registry: the built-ins get no privilege a third-party format does not also get, so the
9
+ * contract cannot quietly grow a dependency on something only the built-ins can reach. If these
10
+ * two can be written against it, so can a fourth.
11
+ *
12
+ * Importing this module registers them. The loader does that once, at its own import.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.NDJSON_IMAGE_FORMAT = exports.NODESET2_XML_FORMAT = void 0;
16
+ exports.isNdjsonHeaderLine = isNdjsonHeaderLine;
17
+ exports.registerBuiltinNodesetFormats = registerBuiltinNodesetFormats;
18
+ const node_opcua_date_time_1 = require("node-opcua-date-time");
19
+ const nodeset_format_js_1 = require("./nodeset_format.js");
20
+ const nodeset_image_js_1 = require("./nodeset_image.js");
21
+ const nodeset_xml_header_js_1 = require("./nodeset_xml_header.js");
22
+ const nodeset_xml_producer_js_1 = require("./nodeset_xml_producer.js");
23
+ /** the name the NodeSet2 XML format is registered under */
24
+ exports.NODESET2_XML_FORMAT = "nodeset2-xml";
25
+ /** the name the NDJSON image format is registered under */
26
+ exports.NDJSON_IMAGE_FORMAT = "ndjson";
27
+ /**
28
+ * NodeSet2 XML: the fallback.
29
+ *
30
+ * It claims every document no other format recognised, which is exactly what the loader did
31
+ * before there was a registry, and it is the right default for a reason that outlives this
32
+ * refactor: XML has no compact magic number worth sniffing. A document may open with a byte-order
33
+ * mark, an XML declaration, a comment, a doctype or whitespace before `<UANodeSet` ever appears,
34
+ * and a sniffer strict enough to be sure would reject documents the loader reads today. So it
35
+ * does not guess. It takes what is left, and reports a real parse error if it was wrong, which is
36
+ * a better diagnostic than "unrecognised format" ever was.
37
+ */
38
+ const nodeset2XmlFormat = {
39
+ name: exports.NODESET2_XML_FORMAT,
40
+ extensions: [".xml"],
41
+ priority: -100,
42
+ sniff(_head) {
43
+ return true;
44
+ },
45
+ async readModels(document) {
46
+ const head = await document.readHead(nodeset_xml_header_js_1.headerComplete);
47
+ return await (0, nodeset_xml_header_js_1.parseDependencies)((0, nodeset_xml_header_js_1.sliceHeader)(head, document.name));
48
+ },
49
+ records(document, _options) {
50
+ return (0, nodeset_xml_producer_js_1.xmlNodesetRecords)(textChunks(document));
51
+ }
52
+ };
53
+ /** the chunks of a document as text: the XML producer takes text, and a source may deliver bytes */
54
+ async function* textChunks(document) {
55
+ const decoder = new TextDecoder("utf-8");
56
+ for await (const chunk of document.chunks()) {
57
+ yield typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
58
+ }
59
+ }
60
+ /**
61
+ * The NDJSON image: node-opcua's own cache of a document it has already parsed.
62
+ *
63
+ * Its first line is a header record, so it is told from every other line-delimited nodeset format
64
+ * by the two fields that open it: `{"kind":"header","schema":<n>`. That is a stronger test than
65
+ * "begins with a brace", which is all the loader used to do, and it is what lets a foreign
66
+ * `.jsonl` sit in the same directory without being mistaken for one of ours.
67
+ */
68
+ const ndjsonImageFormat = {
69
+ name: exports.NDJSON_IMAGE_FORMAT,
70
+ extensions: [".ndjson", ".ndjson.gz"],
71
+ priority: 100,
72
+ sniff(head) {
73
+ return isNdjsonHeaderLine(head.firstLine);
74
+ },
75
+ async readModels(document) {
76
+ // an image is small, a few hundred KB, and its header is line 1: reading it whole costs
77
+ // nothing that the load itself was not about to spend
78
+ const { header } = await (0, nodeset_image_js_1.readNodesetImageInfo)(await document.rawBytes());
79
+ const models = header.models.map((m) => ({
80
+ modelUri: m.modelUri,
81
+ version: m.version,
82
+ publicationDate: m.publicationDate ? new Date(m.publicationDate) : (0, node_opcua_date_time_1.getMinOPCUADate)(),
83
+ requiredModel: m.requiredModels.map((r) => ({
84
+ ...r,
85
+ publicationDate: new Date(r.publicationDate ?? Number.NaN)
86
+ }))
87
+ }));
88
+ if (models.length === 0 && header.namespaceUris.length >= 1) {
89
+ models.push({
90
+ modelUri: header.namespaceUris[0],
91
+ version: "1",
92
+ publicationDate: (0, node_opcua_date_time_1.getMinOPCUADate)(),
93
+ requiredModel: []
94
+ });
95
+ }
96
+ return { models, namespaceUris: header.namespaceUris };
97
+ },
98
+ async *records(document, _options) {
99
+ yield* (0, nodeset_image_js_1.imageLinesToRecords)(await document.lines());
100
+ }
101
+ };
102
+ /**
103
+ * whether this line is the header record of an NDJSON image.
104
+ *
105
+ * Deliberately a string test before it is a parse: the discriminating fields are written first
106
+ * and the line can be hundreds of kilobytes, so there is no reason to build an object to answer
107
+ * a question the first thirty characters already settle. The parse is the confirmation, not the
108
+ * test, and it is only reached for a line that already looks like ours.
109
+ */
110
+ function isNdjsonHeaderLine(line) {
111
+ const start = line.slice(0, 200);
112
+ if (!start.startsWith("{") || !start.includes('"kind"') || !start.includes('"header"')) {
113
+ return false;
114
+ }
115
+ try {
116
+ const parsed = JSON.parse(line);
117
+ return parsed?.kind === "header" && typeof parsed.schema === "number";
118
+ }
119
+ catch {
120
+ return false;
121
+ }
122
+ }
123
+ let registered = false;
124
+ /** register the built-in formats; called by the loader, and safe to call more than once */
125
+ function registerBuiltinNodesetFormats() {
126
+ if (registered)
127
+ return;
128
+ registered = true;
129
+ (0, nodeset_format_js_1.registerNodesetFormat)(nodeset2XmlFormat);
130
+ (0, nodeset_format_js_1.registerNodesetFormat)(ndjsonImageFormat);
131
+ }
132
+ registerBuiltinNodesetFormats();
133
+ //# sourceMappingURL=nodeset_builtin_formats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeset_builtin_formats.js","sourceRoot":"","sources":["../../../api/loader/nodeset_builtin_formats.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @module node-opcua-address-space
3
+ *
4
+ * Which serialisation a nodeset document is written in, and who can read it.
5
+ *
6
+ * The loader is built as `producer -> NodesetRecord -> consumer`: whatever a document is written
7
+ * in, it becomes a stream of records, and everything downstream of that -- the address space, the
8
+ * NodeSet2 XML writer, the image writer -- neither knows nor asks. What was missing is the other
9
+ * half of that seam. The *choice* of producer was a hard-coded pair, XML or image, decided on one
10
+ * byte, so a third serialisation had no way in short of editing the loader.
11
+ *
12
+ * A {@link NodesetFormat} is that way in. It answers three questions and nothing else: are these
13
+ * bytes mine, what does this document depend on, and what are its records. Formats are found by
14
+ * sniffing the document, never by its file name: an extension is a claim about a file, and the
15
+ * loader reads sources that are not files at all.
16
+ *
17
+ * Two formats are built in and registered by the loader itself, so the default behaviour is
18
+ * exactly what it always was: NodeSet2 XML, and the NDJSON image. Anything else is registered by
19
+ * the code that implements it, the way the Node.js entry point installs its zlib codecs.
20
+ */
21
+ import type { RequiredModel } from "node-opcua-address-space-base";
22
+ import type { NodeSetLoaderOptions } from "../interfaces/nodeset_loader_options.js";
23
+ import type { NodesetRecord } from "./nodeset_record.js";
24
+ import type { NodesetChunk } from "./nodeset_source.js";
25
+ /** a model a document defines, together with the models it says it needs */
26
+ export interface NodesetModel extends RequiredModel {
27
+ requiredModel: RequiredModel[];
28
+ }
29
+ /**
30
+ * what a document says about itself before its body is read: enough, and only enough, to sort a
31
+ * set of documents into the order they have to be loaded in.
32
+ */
33
+ export interface NodesetModelInfo {
34
+ namespaceUris: string[];
35
+ models: NodesetModel[];
36
+ }
37
+ /**
38
+ * the beginning of a document, for {@link NodesetFormat.sniff}.
39
+ *
40
+ * `text` is inflated when the document arrived gzipped, so a sniffer never has to care whether it
41
+ * is looking at a compressed file: compression is a property of how a document was stored, never
42
+ * of what it says. It is decoded as UTF-8, which is lossless for the text formats and, for a
43
+ * binary container, faithful across the ASCII and NUL runs that container formats put their magic
44
+ * in (a tar header, for instance, is ASCII and NUL for its whole first block).
45
+ */
46
+ export interface NodesetHead {
47
+ /** the first bytes of the document, decoded as UTF-8, inflated if the document was gzipped */
48
+ readonly text: string;
49
+ /** `text` up to the first line feed: what the line-delimited formats decide on */
50
+ readonly firstLine: string;
51
+ /** whether the document was gzip on the wire */
52
+ readonly gzip: boolean;
53
+ /** the source's name, for diagnostics only; never a basis for the decision */
54
+ readonly name: string;
55
+ }
56
+ /**
57
+ * a document handed to a format, in whichever shape that format finds convenient. Every accessor
58
+ * inflates first when the document was gzipped, and the results are computed once and shared, so
59
+ * a format that asks for both the lines and the bytes pays for one inflate.
60
+ */
61
+ export interface NodesetDocument {
62
+ readonly name: string;
63
+ /** the whole document, inflated */
64
+ bytes(): Promise<Uint8Array>;
65
+ /** the whole document as text, inflated and decoded */
66
+ text(): Promise<string>;
67
+ /** the document's lines, for the line-delimited formats */
68
+ lines(): Promise<string[]>;
69
+ /** the bytes exactly as they arrived, still compressed if they were */
70
+ rawBytes(): Promise<Uint8Array>;
71
+ /** the document as it arrives, for a format that parses incrementally rather than whole */
72
+ chunks(): AsyncIterable<NodesetChunk>;
73
+ /**
74
+ * read from the start until `complete(text)` holds, or the document ends. What was read stays
75
+ * in the document and is delivered again, first, by {@link NodesetDocument.chunks}, so a
76
+ * format that reads a header this way has not spent the source.
77
+ *
78
+ * This is how a format avoids reading a body it does not need: the XML header pre-pass stops
79
+ * after a few hundred bytes of a four megabyte file.
80
+ */
81
+ readHead(complete: (text: string) => boolean): Promise<string>;
82
+ /** the SHA-256 of the bytes as they arrived, when the loader was asked to compute one */
83
+ digest(): string | undefined;
84
+ }
85
+ /** a serialisation the loader can read */
86
+ export interface NodesetFormat {
87
+ /** how this format is named in diagnostics and in the tools; unique among registered formats */
88
+ readonly name: string;
89
+ /**
90
+ * the file extensions this format is usually written with, lowercase and dotted.
91
+ * Documentation and tooling only: the loader never decides on a name.
92
+ */
93
+ readonly extensions?: readonly string[];
94
+ /**
95
+ * which format wins when more than one claims a document. Higher is stronger; the default is
96
+ * 0. NodeSet2 XML registers at -100 because it is the fallback: it claims anything no other
97
+ * format recognised, which is the behaviour the loader has always had.
98
+ */
99
+ readonly priority?: number;
100
+ /** whether this format claims the document that begins like this */
101
+ sniff(head: NodesetHead): boolean;
102
+ /** what the document depends on, read without parsing its body */
103
+ readModels(document: NodesetDocument): Promise<NodesetModelInfo>;
104
+ /** the document's records, in document order */
105
+ records(document: NodesetDocument, options?: NodeSetLoaderOptions): AsyncIterable<NodesetRecord>;
106
+ }
107
+ /**
108
+ * make a format available to the loader. Returns a function that removes it again, which is what
109
+ * a test uses to put the registry back as it found it; production code registers once, at import.
110
+ *
111
+ * Registering a name twice replaces the earlier format, so a caller can override a built-in.
112
+ */
113
+ export declare function registerNodesetFormat(format: NodesetFormat): () => void;
114
+ /** every registered format, strongest claim first */
115
+ export declare function nodesetFormats(): readonly NodesetFormat[];
116
+ /** the registered format of that name, if any */
117
+ export declare function nodesetFormatByName(name: string): NodesetFormat | undefined;
118
+ /**
119
+ * which format claims a document that begins like this, or `undefined` when none does. Formats
120
+ * are asked strongest first, so a specific format is always given the chance to claim a document
121
+ * before the XML fallback sees it.
122
+ */
123
+ export declare function findNodesetFormat(head: NodesetHead): NodesetFormat | undefined;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /**
3
+ * @module node-opcua-address-space
4
+ *
5
+ * Which serialisation a nodeset document is written in, and who can read it.
6
+ *
7
+ * The loader is built as `producer -> NodesetRecord -> consumer`: whatever a document is written
8
+ * in, it becomes a stream of records, and everything downstream of that -- the address space, the
9
+ * NodeSet2 XML writer, the image writer -- neither knows nor asks. What was missing is the other
10
+ * half of that seam. The *choice* of producer was a hard-coded pair, XML or image, decided on one
11
+ * byte, so a third serialisation had no way in short of editing the loader.
12
+ *
13
+ * A {@link NodesetFormat} is that way in. It answers three questions and nothing else: are these
14
+ * bytes mine, what does this document depend on, and what are its records. Formats are found by
15
+ * sniffing the document, never by its file name: an extension is a claim about a file, and the
16
+ * loader reads sources that are not files at all.
17
+ *
18
+ * Two formats are built in and registered by the loader itself, so the default behaviour is
19
+ * exactly what it always was: NodeSet2 XML, and the NDJSON image. Anything else is registered by
20
+ * the code that implements it, the way the Node.js entry point installs its zlib codecs.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.registerNodesetFormat = registerNodesetFormat;
24
+ exports.nodesetFormats = nodesetFormats;
25
+ exports.nodesetFormatByName = nodesetFormatByName;
26
+ exports.findNodesetFormat = findNodesetFormat;
27
+ const formats = new Map();
28
+ /**
29
+ * make a format available to the loader. Returns a function that removes it again, which is what
30
+ * a test uses to put the registry back as it found it; production code registers once, at import.
31
+ *
32
+ * Registering a name twice replaces the earlier format, so a caller can override a built-in.
33
+ */
34
+ function registerNodesetFormat(format) {
35
+ formats.set(format.name, format);
36
+ return () => {
37
+ if (formats.get(format.name) === format) {
38
+ formats.delete(format.name);
39
+ }
40
+ };
41
+ }
42
+ /** every registered format, strongest claim first */
43
+ function nodesetFormats() {
44
+ return [...formats.values()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
45
+ }
46
+ /** the registered format of that name, if any */
47
+ function nodesetFormatByName(name) {
48
+ return formats.get(name);
49
+ }
50
+ /**
51
+ * which format claims a document that begins like this, or `undefined` when none does. Formats
52
+ * are asked strongest first, so a specific format is always given the chance to claim a document
53
+ * before the XML fallback sees it.
54
+ */
55
+ function findNodesetFormat(head) {
56
+ for (const format of nodesetFormats()) {
57
+ if (format.sniff(head)) {
58
+ return format;
59
+ }
60
+ }
61
+ return undefined;
62
+ }
63
+ //# sourceMappingURL=nodeset_format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeset_format.js","sourceRoot":"","sources":["../../../api/loader/nodeset_format.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;AAsGH,sDAOC;AAGD,wCAEC;AAGD,kDAEC;AAOD,8CAOC;AAvCD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;AAEjD;;;;;GAKG;AACH,SAAgB,qBAAqB,CAAC,MAAqB;IACvD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,GAAG,EAAE;QACR,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,MAAM,EAAE,CAAC;YACtC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;IACL,CAAC,CAAC;AACN,CAAC;AAED,qDAAqD;AACrD,SAAgB,cAAc;IAC1B,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC;AACvF,CAAC;AAED,iDAAiD;AACjD,SAAgB,mBAAmB,CAAC,IAAY;IAC5C,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,IAAiB;IAC/C,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,EAAE,CAAC;QACpC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,OAAO,MAAM,CAAC;QAClB,CAAC;IACL,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC"}
@@ -10,8 +10,24 @@ import { type EncodeHeaderOptions, type NodesetImageHeader, type NodesetImageTra
10
10
  import { type NodesetRecord, type NodesetRecordConsumer } from "./nodeset_record.js";
11
11
  import type { NodesetChunkStream } from "./nodeset_source.js";
12
12
  export { NodesetImageError, type NodesetImageHeader, type NodesetImageTrailer } from "./nodeset_image_codec.js";
13
- /** the two bytes every gzip stream starts with: how an image is told from XML */
13
+ /** whether these bytes are gzip, and so need inflating before they are text */
14
+ export declare function isGzip(firstBytes: Uint8Array): boolean;
15
+ /**
16
+ * a NodeSet-NDJSON document, compressed or not, told from NodeSet2 XML by its first byte: a gzip
17
+ * stream starts `1f 8b`, an XML document `<`, and an uncompressed document `{`. Compression is a
18
+ * property of how a document was stored, never of what it says, so a reader that took only the
19
+ * compressed form would be refusing the same document written plainly.
20
+ */
14
21
  export declare function isNodesetImage(firstBytes: Uint8Array): boolean;
22
+ /** how the text of an image is compressed; see {@link setImageDeflater} */
23
+ export type ImageDeflater = (text: string) => Promise<Uint8Array>;
24
+ /**
25
+ * how an image is compressed: CompressionStream everywhere by default, which offers no way to ask
26
+ * for a compression level and so writes at the default one; the Node.js entry point installs zlib
27
+ * at level 9, four percent smaller on the published nodesets for no change to the reader -- every
28
+ * gzip stream inflates the same whatever level wrote it. Returns the deflater installed before.
29
+ */
30
+ export declare function setImageDeflater(deflater: ImageDeflater): ImageDeflater;
15
31
  /** forget the inflated lines of `image`; the next reader inflates again */
16
32
  export declare function releaseInflatedImageLines(image: Uint8Array): void;
17
33
  /** whether the inflated lines of `image` are being kept, for the tests */