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.
- package/api/helpers/ensure_secure_access.ts +53 -2
- package/api/index.ts +30 -1
- package/api/loader/generateAddressSpaceRaw.ts +44 -139
- package/api/loader/load_nodeset2.ts +5 -0
- package/api/loader/narrow_instance_datatypes.ts +264 -0
- package/api/loader/nodeset_builtin_formats.ts +143 -0
- package/api/loader/nodeset_format.ts +153 -0
- package/api/loader/nodeset_image.ts +37 -4
- package/api/loader/nodeset_image_codec.ts +153 -34
- package/api/loader/nodeset_record.ts +26 -2
- package/api/loader/nodeset_record_applier.ts +54 -3
- package/api/loader/nodeset_records_to_xml.ts +620 -0
- package/api/loader/nodeset_source.ts +185 -26
- package/api/loader/nodeset_to_image.ts +16 -5
- package/api/loader/nodeset_xml_header.ts +119 -0
- package/api/loader/nodeset_xml_primitives.ts +209 -0
- package/api/loader/nodeset_xml_producer.ts +61 -2
- package/api/loader/parsers/variant_parser.ts +9 -0
- package/bin/nodeset_equivalence.js +750 -0
- package/bin/nodeset_tool_common.js +72 -0
- package/bin/opcua-nodeset-image.js +93 -103
- package/bin/opcua-nodeset-live.js +139 -0
- package/dist/api/helpers/ensure_secure_access.d.ts +20 -0
- package/dist/api/helpers/ensure_secure_access.js +39 -0
- package/dist/api/helpers/ensure_secure_access.js.map +1 -1
- package/dist/api/index.d.ts +7 -2
- package/dist/api/index.js +21 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/loader/generateAddressSpaceRaw.d.ts +1 -7
- package/dist/api/loader/generateAddressSpaceRaw.js +32 -125
- package/dist/api/loader/generateAddressSpaceRaw.js.map +1 -1
- package/dist/api/loader/load_nodeset2.js +4 -0
- package/dist/api/loader/load_nodeset2.js.map +1 -1
- package/dist/api/loader/narrow_instance_datatypes.d.ts +30 -0
- package/dist/api/loader/narrow_instance_datatypes.js +217 -0
- package/dist/api/loader/narrow_instance_datatypes.js.map +1 -0
- package/dist/api/loader/nodeset_builtin_formats.d.ts +27 -0
- package/dist/api/loader/nodeset_builtin_formats.js +133 -0
- package/dist/api/loader/nodeset_builtin_formats.js.map +1 -0
- package/dist/api/loader/nodeset_format.d.ts +123 -0
- package/dist/api/loader/nodeset_format.js +63 -0
- package/dist/api/loader/nodeset_format.js.map +1 -0
- package/dist/api/loader/nodeset_image.d.ts +17 -1
- package/dist/api/loader/nodeset_image.js +32 -4
- package/dist/api/loader/nodeset_image.js.map +1 -1
- package/dist/api/loader/nodeset_image_codec.d.ts +4 -0
- package/dist/api/loader/nodeset_image_codec.js +142 -53
- package/dist/api/loader/nodeset_image_codec.js.map +1 -1
- package/dist/api/loader/nodeset_record.d.ts +26 -2
- package/dist/api/loader/nodeset_record.js +11 -2
- package/dist/api/loader/nodeset_record.js.map +1 -1
- package/dist/api/loader/nodeset_record_applier.js +52 -3
- package/dist/api/loader/nodeset_record_applier.js.map +1 -1
- package/dist/api/loader/nodeset_records_to_xml.d.ts +16 -0
- package/dist/api/loader/nodeset_records_to_xml.js +536 -0
- package/dist/api/loader/nodeset_records_to_xml.js.map +1 -0
- package/dist/api/loader/nodeset_source.d.ts +68 -7
- package/dist/api/loader/nodeset_source.js +157 -20
- package/dist/api/loader/nodeset_source.js.map +1 -1
- package/dist/api/loader/nodeset_to_image.d.ts +4 -4
- package/dist/api/loader/nodeset_to_image.js +14 -4
- package/dist/api/loader/nodeset_to_image.js.map +1 -1
- package/dist/api/loader/nodeset_xml_header.d.ts +22 -0
- package/dist/api/loader/nodeset_xml_header.js +113 -0
- package/dist/api/loader/nodeset_xml_header.js.map +1 -0
- package/dist/api/loader/nodeset_xml_primitives.d.ts +37 -0
- package/dist/api/loader/nodeset_xml_primitives.js +196 -0
- package/dist/api/loader/nodeset_xml_primitives.js.map +1 -0
- package/dist/api/loader/nodeset_xml_producer.js +56 -1
- package/dist/api/loader/nodeset_xml_producer.js.map +1 -1
- package/dist/api/loader/parsers/variant_parser.js +7 -0
- package/dist/api/loader/parsers/variant_parser.js.map +1 -1
- package/dist/impl/_instantiate_helpers.js +1 -0
- package/dist/impl/_instantiate_helpers.js.map +1 -1
- package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.js +4 -0
- package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.js.map +1 -1
- package/dist/impl/alarms_and_conditions/ua_certificate_expiration_alarm_impl.js +14 -0
- package/dist/impl/alarms_and_conditions/ua_certificate_expiration_alarm_impl.js.map +1 -1
- package/dist/impl/base_node_impl.d.ts +9 -0
- package/dist/impl/base_node_impl.js +11 -0
- package/dist/impl/base_node_impl.js.map +1 -1
- package/dist/impl/base_node_private.js +67 -97
- package/dist/impl/base_node_private.js.map +1 -1
- package/dist/impl/nodeset_tools/construct_namespace_dependency.js +5 -1
- package/dist/impl/nodeset_tools/construct_namespace_dependency.js.map +1 -1
- package/dist/impl/nodeset_tools/nodeset_to_records.js +61 -15
- package/dist/impl/nodeset_tools/nodeset_to_records.js.map +1 -1
- package/dist/impl/nodeset_tools/nodeset_to_xml.d.ts +1 -8
- package/dist/impl/nodeset_tools/nodeset_to_xml.js +174 -190
- package/dist/impl/nodeset_tools/nodeset_to_xml.js.map +1 -1
- package/dist/impl/ua_data_type_impl.d.ts +21 -0
- package/dist/impl/ua_data_type_impl.js +27 -2
- package/dist/impl/ua_data_type_impl.js.map +1 -1
- package/dist/impl/ua_object_type_impl.d.ts +9 -0
- package/dist/impl/ua_object_type_impl.js +12 -1
- package/dist/impl/ua_object_type_impl.js.map +1 -1
- package/dist/impl/ua_variable_impl.js +27 -10
- package/dist/impl/ua_variable_impl.js.map +1 -1
- package/dist/impl/ua_variable_impl_ext_obj.js +19 -2
- package/dist/impl/ua_variable_impl_ext_obj.js.map +1 -1
- package/dist/impl/ua_variable_type_impl.d.ts +8 -1
- package/dist/impl/ua_variable_type_impl.js +39 -2
- package/dist/impl/ua_variable_type_impl.js.map +1 -1
- package/dist/tsconfig_base.tsbuildinfo +1 -1
- package/distNodeJS/generate_address_space.js +4 -0
- package/distNodeJS/generate_address_space.js.map +1 -1
- package/impl/_instantiate_helpers.ts +3 -0
- package/impl/alarms_and_conditions/ua_acknowledgeable_condition_impl.ts +4 -4
- package/impl/alarms_and_conditions/ua_alarm_condition_impl.ts +19 -9
- package/impl/alarms_and_conditions/ua_base_event_impl.ts +9 -9
- package/impl/alarms_and_conditions/ua_certificate_expiration_alarm_impl.ts +17 -0
- package/impl/alarms_and_conditions/ua_condition_impl.ts +18 -18
- package/impl/alarms_and_conditions/ua_exclusive_deviation_alarm_impl.ts +2 -2
- package/impl/alarms_and_conditions/ua_exclusive_limit_alarm_impl.ts +1 -1
- package/impl/alarms_and_conditions/ua_limit_alarm_impl.ts +16 -16
- package/impl/alarms_and_conditions/ua_non_exclusive_deviation_alarm_impl.ts +2 -2
- package/impl/alarms_and_conditions/ua_non_exclusive_limit_alarm_impl.ts +4 -4
- package/impl/alarms_and_conditions/ua_off_normal_alarm_impl.ts +1 -1
- package/impl/base_node_impl.ts +15 -0
- package/impl/base_node_private.ts +97 -146
- package/impl/data_access/ua_multistate_discrete_impl.ts +1 -1
- package/impl/data_access/ua_multistate_value_discrete_impl.ts +2 -2
- package/impl/data_access/ua_two_state_discrete_impl.ts +2 -2
- package/impl/nodeset_tools/construct_namespace_dependency.ts +5 -1
- package/impl/nodeset_tools/nodeset_to_records.ts +66 -16
- package/impl/nodeset_tools/nodeset_to_xml.ts +127 -160
- package/impl/state_machine/finite_state_machine.ts +3 -3
- package/impl/state_machine/ua_shelving_state_machine_ex.ts +20 -20
- package/impl/ua_data_type_impl.ts +42 -4
- package/impl/ua_method_impl.ts +1 -1
- package/impl/ua_object_type_impl.ts +16 -3
- package/impl/ua_variable_impl.ts +27 -10
- package/impl/ua_variable_impl_ext_obj.ts +20 -2
- package/impl/ua_variable_type_impl.ts +49 -3
- package/package.json +29 -27
- 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":";;AAgMA,0DAuEC;AA1PD,iEAAuF;AACvF,uDAAiE;AACjE,yDAA4D;AAC5D,2DAA8C;AAG9C,MAAM,OAAO,GAAG,IAAA,iCAAc,EAAC,2BAA2B,CAAC,CAAC;AAC5D,MAAM,QAAQ,GAAG,IAAA,gCAAa,EAAC,2BAA2B,CAAC,CAAC;AAK5D,SAAS,MAAM,CAAC,IAAc;IAC1B,OAAO,IAAI,CAAC,SAAS,KAAK,iCAAS,CAAC,UAAU,IAAI,IAAI,CAAC,SAAS,KAAK,iCAAS,CAAC,YAAY,CAAC;AAChG,CAAC;AACD,SAAS,UAAU,CAAC,IAAc;IAC9B,OAAO,IAAI,CAAC,SAAS,KAAK,iCAAS,CAAC,MAAM,IAAI,IAAI,CAAC,SAAS,KAAK,iCAAS,CAAC,QAAQ,CAAC;AACxF,CAAC;AAED;;;GAGG;AACH,SAAS,gBAAgB,CAAC,YAA2B,EAAE,IAAgB;IACnE,MAAM,cAAc,GAAuB,IAAI,CAAC,cAAc,CAAC;IAC/D,IAAI,CAAC,cAAc,IAAI,cAAc,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IACvD,OAAO,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACH,MAAM,gBAAgB;IAOG;IANJ,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC5D,+FAA+F;IAC9E,YAAY,CAAyB;IACrC,gBAAgB,CAAkB;IAEnD,YACqB,YAA2B,EAC5C,gBAAiC;QADhB,iBAAY,GAAZ,YAAY,CAAe;QAG5C,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC,iBAAiB,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC;QAC3E,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;IAC7C,CAAC;IAED;;;;OAIG;IACI,qBAAqB,CAAC,QAAkB;QAC3C,OAAO,CACH,QAAQ,CAAC,SAAS,KAAK,iCAAS,CAAC,QAAQ;YACzC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,gBAAgB,EAAE,uCAAe,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CACvF,CAAC;IACN,CAAC;IAEM,IAAI,CAAC,IAAY,EAAE,UAAyB;QAC/C,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC;QAClB,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,GAAG,EAAU,CAAC,CAAC;QAC7D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC3B,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,IAAI,CAAC,IAAY,EAAE,UAAyB,EAAE,OAAoB;QACtE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QAC9C,IAAI,KAAK,EAAE,CAAC;YACR,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,YAAY,EAAE,uCAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9G,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACjC,MAAM,aAAa,GAAG,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YACrF,IAAI,aAAa,IAAI,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;gBACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;gBAC/D,IAAI,QAAQ,EAAE,CAAC;oBACX,OAAO,QAAQ,CAAC;gBACpB,CAAC;YACL,CAAC;QACL,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,YAA6B,CAAC;QACrD,OAAO,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,CAAC;CACJ;AAED,8EAA8E;AAC9E,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB;;;;;;;;GAQG;AACH,SAAS,aAAa,CAAC,YAA2B,EAAE,KAAuB,EAAE,IAAgB,EAAE,KAAa;IACxG,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,gBAAgB,EAAE,CAAC;QACnB,MAAM,UAAU,GAAG,gBAAgB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QAC1D,IAAI,UAAU,EAAE,CAAC;YACb,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YACzD,IAAI,QAAQ,EAAE,CAAC;gBACX,OAAO,QAAQ,CAAC;YACpB,CAAC;QACL,CAAC;IACL,CAAC;IACD,6FAA6F;IAC7F,mFAAmF;IACnF,IAAI,gBAAgB,IAAI,MAAM,CAAC,SAAS,KAAK,iCAAS,CAAC,MAAM,EAAE,CAAC;QAC5D,MAAM,iBAAiB,GAAG,aAAa,CAAC,YAAY,EAAE,KAAK,EAAE,MAAoB,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAC9F,IAAI,iBAAiB,EAAE,CAAC;YACpB,OAAO,iBAAiB,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7D,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,YAA2B,EAAE,MAAc;IACjE,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,YAAY,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;AACrD,CAAC;AAaD;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,uBAAuB,CAAC,YAA2B;IAC/D,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,oFAAoF;IACpF,2FAA2F;IAC3F,yDAAyD;IACzD,MAAM,gBAAgB,GAAG,YAAY,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;IAC5E,IACI,CAAC,gBAAgB;QACjB,CAAC,YAAY,CAAC,iBAAiB,CAAC,YAAY,CAAC;QAC7C,CAAC,YAAY,CAAC,iBAAiB,CAAC,UAAU,CAAC;QAC3C,CAAC,YAAY,CAAC,iBAAiB,CAAC,wBAAwB,CAAC,EAC3D,CAAC;QACC,OAAO,QAAQ,CAAC;IACpB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;IAEnE,KAAK,MAAM,SAAS,IAAI,YAAY,CAAC,iBAAiB,EAAE,EAAE,CAAC;QACvD,KAAK,MAAM,IAAI,IAAK,SAA8B,CAAC,YAAY,EAAE,EAAE,CAAC;YAChE,IAAI,IAAI,CAAC,SAAS,KAAK,iCAAS,CAAC,QAAQ,EAAE,CAAC;gBACxC,SAAS;YACb,CAAC;YACD,MAAM,QAAQ,GAAG,IAAkB,CAAC;YACpC,MAAM,QAAQ,GAAG,aAAa,CAAC,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;YACjE,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACtD,SAAS;YACb,CAAC;YACD,IAAI,IAAA,8BAAU,EAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACnD,SAAS;YACb,CAAC;YACD,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC3E,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC3E,IAAI,CAAC,gBAAgB,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACzC,SAAS;YACb,CAAC;YACD,gFAAgF;YAChF,IAAI,CAAC,gBAAgB,CAAC,WAAW,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBAClD,SAAS;YACb,CAAC;YACD,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC;YACzC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,aAAa,CAAC;YAC3D,MAAM,SAAS,GACX,KAAK,CAAC,QAAQ,KAAK,6BAAQ,CAAC,IAAI;gBAChC,mBAAmB,KAAK,6BAAQ,CAAC,OAAO;gBACxC,KAAK,CAAC,QAAQ,KAAK,mBAAmB,CAAC;YAC3C,IAAI,CAAC,SAAS,EAAE,CAAC;gBACb,QAAQ,CACJ,iBAAiB,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI;oBAC7E,kBAAkB,6BAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,kBAAkB,gBAAgB,CAAC,UAAU,CAAC,QAAQ,EAAE,GAAG;oBACrG,QAAQ,6BAAQ,CAAC,mBAAmB,CAAC,EAAE,CAC9C,CAAC;gBACF,SAAS;YACb,CAAC;YACD,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC;YAC/B,QAAQ,CAAC,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC;YAC3C,QAAsD,CAAC,cAAc,GAAG,IAAI,CAAC;YAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC;YACrE,iBAAiB;YACjB,IAAI,OAAO,EAAE,CAAC;gBACV,QAAQ,CACJ,4BAA4B,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,EAAE,GAAG;oBACvF,QAAQ,gBAAgB,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,QAAQ,EAAE,IAAI;oBACtE,mBAAmB,gBAAgB,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,gBAAgB,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAC1G,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IACD,iBAAiB;IACjB,IAAI,OAAO,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,QAAQ,CAAC,4BAA4B,QAAQ,CAAC,MAAM,sDAAsD,CAAC,CAAC;IAChH,CAAC;IACD,OAAO,QAAQ,CAAC;AACpB,CAAC"}
|
|
@@ -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
|
-
/**
|
|
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 */
|