node-opcua-client-dynamic-extension-object 2.178.0 → 2.179.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/dist/convert_data_type_definition_to_structuretype_schema.d.ts +8 -11
- package/dist/convert_data_type_definition_to_structuretype_schema.js +81 -48
- package/dist/convert_data_type_definition_to_structuretype_schema.js.map +1 -1
- package/dist/convert_structuretype_schema_to_structure_definition.d.ts +1 -1
- package/dist/convert_structuretype_schema_to_structure_definition.js +5 -3
- package/dist/convert_structuretype_schema_to_structure_definition.js.map +1 -1
- package/dist/extra_data_type_manager.d.ts +4 -5
- package/dist/extra_data_type_manager.js +15 -16
- package/dist/extra_data_type_manager.js.map +1 -1
- package/dist/get_extension_object_constructor.d.ts +3 -3
- package/dist/get_extension_object_constructor.js +3 -0
- package/dist/get_extension_object_constructor.js.map +1 -1
- package/dist/get_extra_data_type_manager.d.ts +2 -2
- package/dist/get_extra_data_type_manager.js +8 -8
- package/dist/get_extra_data_type_manager.js.map +1 -1
- package/dist/index.d.ts +5 -6
- package/dist/index.js +20 -21
- package/dist/index.js.map +1 -1
- package/dist/populate_data_type_manager.d.ts +2 -2
- package/dist/populate_data_type_manager.js +10 -10
- package/dist/populate_data_type_manager.js.map +1 -1
- package/dist/private/find_encodings.d.ts +2 -2
- package/dist/private/find_encodings.js.map +1 -1
- package/dist/private/populate_data_type_manager_103.d.ts +4 -4
- package/dist/private/populate_data_type_manager_103.js +97 -53
- package/dist/private/populate_data_type_manager_103.js.map +1 -1
- package/dist/private/populate_data_type_manager_104.d.ts +18 -4
- package/dist/private/populate_data_type_manager_104.js +48 -17
- package/dist/private/populate_data_type_manager_104.js.map +1 -1
- package/dist/promote_opaque_structure.d.ts +2 -2
- package/dist/promote_opaque_structure.js.map +1 -1
- package/dist/promote_opaque_structure_in_notification_data.d.ts +2 -2
- package/dist/promote_opaque_structure_in_notification_data.js +0 -2
- package/dist/promote_opaque_structure_in_notification_data.js.map +1 -1
- package/dist/resolve_dynamic_extension_object.d.ts +4 -4
- package/dist/resolve_dynamic_extension_object.js +15 -12
- package/dist/resolve_dynamic_extension_object.js.map +1 -1
- package/package.json +18 -18
- package/source/convert_data_type_definition_to_structuretype_schema.ts +146 -123
- package/source/convert_structuretype_schema_to_structure_definition.ts +9 -9
- package/source/extra_data_type_manager.ts +24 -22
- package/source/get_extension_object_constructor.ts +12 -7
- package/source/get_extra_data_type_manager.ts +21 -24
- package/source/index.ts +8 -8
- package/source/populate_data_type_manager.ts +17 -25
- package/source/private/find_encodings.ts +2 -2
- package/source/private/populate_data_type_manager_103.ts +166 -105
- package/source/private/populate_data_type_manager_104.ts +102 -45
- package/source/promote_opaque_structure.ts +3 -3
- package/source/promote_opaque_structure_in_notification_data.ts +3 -5
- package/source/resolve_dynamic_extension_object.ts +28 -29
|
@@ -1,47 +1,54 @@
|
|
|
1
1
|
import { DataTypeIds } from "node-opcua-constants";
|
|
2
2
|
import { AttributeIds, BrowseDirection, makeResultMask, NodeClassMask } from "node-opcua-data-model";
|
|
3
|
-
import { DataValue } from "node-opcua-data-value";
|
|
4
|
-
import { checkDebugFlag, make_debugLog, make_errorLog
|
|
3
|
+
import type { DataValue } from "node-opcua-data-value";
|
|
4
|
+
import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
|
|
5
5
|
import { ExtensionObject } from "node-opcua-extension-object";
|
|
6
6
|
import {
|
|
7
|
-
BitField,
|
|
7
|
+
type BitField,
|
|
8
8
|
EnumerationDefinitionSchema,
|
|
9
9
|
extractAllPossibleFields,
|
|
10
10
|
FieldCategory,
|
|
11
|
-
FieldInterfaceOptions,
|
|
11
|
+
type FieldInterfaceOptions,
|
|
12
12
|
getBuiltInType,
|
|
13
|
-
IStructuredTypeSchema,
|
|
13
|
+
type IStructuredTypeSchema,
|
|
14
14
|
StructuredTypeSchema,
|
|
15
|
-
TypeDefinition
|
|
15
|
+
type TypeDefinition
|
|
16
16
|
} from "node-opcua-factory";
|
|
17
|
-
import { coerceNodeId, INodeId, makeExpandedNodeId, NodeId, NodeIdType, resolveNodeId, sameNodeId } from "node-opcua-nodeid";
|
|
18
17
|
import {
|
|
19
|
-
|
|
18
|
+
coerceNodeId,
|
|
19
|
+
type INodeId,
|
|
20
|
+
makeExpandedNodeId,
|
|
21
|
+
type NodeId,
|
|
22
|
+
NodeIdType,
|
|
23
|
+
resolveNodeId,
|
|
24
|
+
sameNodeId
|
|
25
|
+
} from "node-opcua-nodeid";
|
|
26
|
+
import {
|
|
27
|
+
type BrowseDescriptionLike,
|
|
20
28
|
browseAll,
|
|
21
29
|
findBasicDataType,
|
|
22
|
-
IBasicSessionAsync,
|
|
23
|
-
IBasicSessionAsync2,
|
|
24
|
-
IBasicSessionBrowseAsyncSimple,
|
|
25
|
-
IBasicSessionBrowseNextAsync
|
|
30
|
+
type IBasicSessionAsync,
|
|
31
|
+
type IBasicSessionAsync2,
|
|
32
|
+
type IBasicSessionBrowseAsyncSimple,
|
|
33
|
+
type IBasicSessionBrowseNextAsync
|
|
26
34
|
} from "node-opcua-pseudo-session";
|
|
27
|
-
import {
|
|
35
|
+
import { createDynamicObjectConstructor, type DataTypeAndEncodingId } from "node-opcua-schemas";
|
|
28
36
|
import {
|
|
29
|
-
DataTypeDefinition,
|
|
37
|
+
type DataTypeDefinition,
|
|
30
38
|
EnumDefinition,
|
|
31
|
-
EnumField,
|
|
39
|
+
type EnumField,
|
|
32
40
|
StructureDefinition,
|
|
33
|
-
StructureField,
|
|
41
|
+
type StructureField,
|
|
34
42
|
StructureType
|
|
35
43
|
} from "node-opcua-types";
|
|
36
44
|
//
|
|
37
45
|
import { DataType } from "node-opcua-variant";
|
|
38
|
-
import { ExtraDataTypeManager } from "./extra_data_type_manager";
|
|
46
|
+
import type { ExtraDataTypeManager } from "./extra_data_type_manager";
|
|
39
47
|
import { _findEncodings } from "./private/find_encodings";
|
|
40
48
|
|
|
41
49
|
const debugLog = make_debugLog(__filename);
|
|
42
50
|
const doDebug = checkDebugFlag(__filename);
|
|
43
51
|
const errorLog = make_errorLog(__filename);
|
|
44
|
-
const warningLog = make_warningLog(__filename);
|
|
45
52
|
|
|
46
53
|
export interface CacheForFieldResolution {
|
|
47
54
|
fieldTypeName: string;
|
|
@@ -50,10 +57,7 @@ export interface CacheForFieldResolution {
|
|
|
50
57
|
allowSubTypes?: boolean;
|
|
51
58
|
dataType?: NodeId;
|
|
52
59
|
}
|
|
53
|
-
export type ResolveReject<T> = [
|
|
54
|
-
resolve: (value: T) => void,
|
|
55
|
-
reject: (err: Error) => void
|
|
56
|
-
];
|
|
60
|
+
export type ResolveReject<T> = [resolve: (value: T) => void, reject: (err: Error) => void];
|
|
57
61
|
|
|
58
62
|
export interface ICache {
|
|
59
63
|
superType?: Map<string, NodeId>;
|
|
@@ -62,14 +66,20 @@ export interface ICache {
|
|
|
62
66
|
browseNameCache?: Map<string, string>;
|
|
63
67
|
hitCount?: number;
|
|
64
68
|
|
|
65
|
-
$$resolveStuff?: Map<string, ResolveReject<
|
|
69
|
+
$$resolveStuff?: Map<string, ResolveReject<unknown>[]>;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
async function memoize<T>(
|
|
72
|
+
async function memoize<T>(
|
|
73
|
+
cache: ICache,
|
|
74
|
+
cacheName: keyof Omit<ICache, "hitCount">,
|
|
75
|
+
nodeId: NodeId,
|
|
76
|
+
func: () => Promise<T>
|
|
77
|
+
): Promise<T> {
|
|
69
78
|
const key = nodeId.toString();
|
|
70
|
-
|
|
79
|
+
const m = cache[cacheName];
|
|
80
|
+
if (m?.has(key)) {
|
|
71
81
|
cache.hitCount = cache.hitCount === undefined ? 0 : cache.hitCount + 1;
|
|
72
|
-
return
|
|
82
|
+
return m.get(key) as T;
|
|
73
83
|
}
|
|
74
84
|
const value = await func();
|
|
75
85
|
if (!cache[cacheName]) {
|
|
@@ -77,25 +87,23 @@ async function memoize<T>(cache: ICache, cacheName: keyof Omit<ICache, "hitCount
|
|
|
77
87
|
}
|
|
78
88
|
(cache[cacheName] as Map<string, T>).set(key, value);
|
|
79
89
|
return value as T;
|
|
80
|
-
|
|
81
90
|
}
|
|
82
91
|
function fromCache<T>(cache: ICache, cacheName: keyof Omit<ICache, "hitCount">, nodeId: NodeId): T | null {
|
|
83
92
|
const key = nodeId.toString();
|
|
84
|
-
|
|
93
|
+
const m = cache[cacheName];
|
|
94
|
+
if (m?.has(key)) {
|
|
85
95
|
cache.hitCount = cache.hitCount === undefined ? 0 : cache.hitCount + 1;
|
|
86
|
-
return
|
|
96
|
+
return m.get(key) as T;
|
|
87
97
|
}
|
|
88
98
|
return null;
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
async function findSuperType(session: IBasicSessionAsync2, dataTypeNodeId: NodeId, cache: ICache): Promise<NodeId> {
|
|
92
|
-
|
|
93
102
|
if (dataTypeNodeId.namespace === 0 && dataTypeNodeId.value === DataTypeIds.BaseDataType) {
|
|
94
103
|
// BaseDataType !
|
|
95
104
|
return coerceNodeId(0);
|
|
96
105
|
}
|
|
97
106
|
return await memoize(cache, "superType", dataTypeNodeId, async () => {
|
|
98
|
-
|
|
99
107
|
const nodeToBrowse3: BrowseDescriptionLike = {
|
|
100
108
|
browseDirection: BrowseDirection.Inverse,
|
|
101
109
|
includeSubtypes: false,
|
|
@@ -108,7 +116,7 @@ async function findSuperType(session: IBasicSessionAsync2, dataTypeNodeId: NodeI
|
|
|
108
116
|
|
|
109
117
|
/* c8 ignore next */
|
|
110
118
|
if (result3.statusCode.isNotGood()) {
|
|
111
|
-
throw new Error(
|
|
119
|
+
throw new Error(`Cannot find superType for ${dataTypeNodeId.toString()}`);
|
|
112
120
|
}
|
|
113
121
|
result3.references = result3.references || [];
|
|
114
122
|
|
|
@@ -116,17 +124,13 @@ async function findSuperType(session: IBasicSessionAsync2, dataTypeNodeId: NodeI
|
|
|
116
124
|
if (result3.references.length !== 1) {
|
|
117
125
|
errorLog("Invalid dataType with more than one (or 0) superType", result3.toString());
|
|
118
126
|
throw new Error(
|
|
119
|
-
|
|
127
|
+
`Invalid dataType with more than one (or 0) superType ${dataTypeNodeId.toString()} l=${result3.references.length}`
|
|
120
128
|
);
|
|
121
129
|
}
|
|
122
130
|
return result3.references[0].nodeId;
|
|
123
131
|
});
|
|
124
132
|
}
|
|
125
|
-
async function findDataTypeCategory(
|
|
126
|
-
session: IBasicSessionAsync2,
|
|
127
|
-
cache: ICache,
|
|
128
|
-
dataTypeNodeId: NodeId,
|
|
129
|
-
): Promise<FieldCategory> {
|
|
133
|
+
async function findDataTypeCategory(session: IBasicSessionAsync2, cache: ICache, dataTypeNodeId: NodeId): Promise<FieldCategory> {
|
|
130
134
|
const subTypeNodeId = await findSuperType(session, dataTypeNodeId, cache);
|
|
131
135
|
doDebug && debugLog("subTypeNodeId of ", dataTypeNodeId.toString(), " is ", subTypeNodeId.toString());
|
|
132
136
|
|
|
@@ -156,11 +160,7 @@ async function findDataTypeCategory(
|
|
|
156
160
|
return await findDataTypeCategory(session, cache, subTypeNodeId);
|
|
157
161
|
}
|
|
158
162
|
|
|
159
|
-
async function findDataTypeBasicType(
|
|
160
|
-
session: IBasicSessionAsync2,
|
|
161
|
-
cache: ICache,
|
|
162
|
-
dataTypeNodeId: NodeId
|
|
163
|
-
): Promise<TypeDefinition> {
|
|
163
|
+
async function findDataTypeBasicType(session: IBasicSessionAsync2, cache: ICache, dataTypeNodeId: NodeId): Promise<TypeDefinition> {
|
|
164
164
|
const subTypeNodeId = await findSuperType(session, dataTypeNodeId, cache);
|
|
165
165
|
|
|
166
166
|
doDebug && debugLog("subTypeNodeId of ", dataTypeNodeId.toString(), " is ", subTypeNodeId.toString());
|
|
@@ -183,7 +183,10 @@ async function findDataTypeBasicType(
|
|
|
183
183
|
attributeId: AttributeIds.BrowseName,
|
|
184
184
|
nodeId: subTypeNodeId
|
|
185
185
|
});
|
|
186
|
-
const name = nameDataValue.value.value.name
|
|
186
|
+
const name = nameDataValue.value.value.name;
|
|
187
|
+
if (!name) {
|
|
188
|
+
throw new Error(`Unexpected: BrowseName has no name for nodeId ${subTypeNodeId.toString()}`);
|
|
189
|
+
}
|
|
187
190
|
return getBuiltInType(name);
|
|
188
191
|
}
|
|
189
192
|
// must drill down ...
|
|
@@ -191,18 +194,17 @@ async function findDataTypeBasicType(
|
|
|
191
194
|
return td;
|
|
192
195
|
}
|
|
193
196
|
|
|
194
|
-
|
|
195
197
|
async function readBrowseNameWithCache(session: IBasicSessionAsync, nodeId: NodeId, cache: ICache): Promise<string> {
|
|
196
198
|
return await memoize(cache, "browseNameCache", nodeId, async () => {
|
|
197
199
|
const dataValue = await session.read({ nodeId, attributeId: AttributeIds.BrowseName });
|
|
198
200
|
if (dataValue.statusCode.isNotGood()) {
|
|
199
|
-
const message =
|
|
200
|
-
|
|
201
|
-
debugLog(message);
|
|
201
|
+
const message = `cannot extract BrowseName of nodeId = ${nodeId.toString()} statusCode = ${dataValue.statusCode.toString()}`;
|
|
202
|
+
// c8 ignore next
|
|
203
|
+
doDebug && debugLog(message);
|
|
202
204
|
throw new Error(message);
|
|
203
205
|
}
|
|
204
|
-
return dataValue.value
|
|
205
|
-
})
|
|
206
|
+
return dataValue.value.value.name;
|
|
207
|
+
});
|
|
206
208
|
}
|
|
207
209
|
|
|
208
210
|
async function resolve2(
|
|
@@ -212,22 +214,20 @@ async function resolve2(
|
|
|
212
214
|
fieldTypeName: string,
|
|
213
215
|
cache: ICache
|
|
214
216
|
): Promise<{ schema: TypeDefinition | undefined; category: FieldCategory }> {
|
|
215
|
-
|
|
216
217
|
const dataTypeFactory = dataTypeManager.getDataTypeFactoryForNamespace(dataTypeNodeId.namespace);
|
|
217
218
|
const category = await findDataTypeCategory(session, cache, dataTypeNodeId);
|
|
218
|
-
doDebug && debugLog(
|
|
219
|
+
doDebug && debugLog(` type ${fieldTypeName} has not been seen yet, let resolve it => (category = `, category, " )");
|
|
219
220
|
|
|
220
|
-
let schema: TypeDefinition | undefined
|
|
221
|
+
let schema: TypeDefinition | undefined;
|
|
221
222
|
switch (category) {
|
|
222
223
|
case FieldCategory.basic:
|
|
223
224
|
schema = await findDataTypeBasicType(session, cache, dataTypeNodeId);
|
|
224
225
|
/* c8 ignore next */
|
|
225
226
|
if (!schema) {
|
|
226
|
-
errorLog(
|
|
227
|
+
errorLog(`Cannot find basic type ${fieldTypeName}`);
|
|
227
228
|
}
|
|
228
229
|
break;
|
|
229
230
|
default:
|
|
230
|
-
case FieldCategory.complex:
|
|
231
231
|
{
|
|
232
232
|
const dataTypeDefinitionDataValue = await session.read({
|
|
233
233
|
attributeId: AttributeIds.DataTypeDefinition,
|
|
@@ -236,7 +236,7 @@ async function resolve2(
|
|
|
236
236
|
|
|
237
237
|
/* c8 ignore next */
|
|
238
238
|
if (dataTypeDefinitionDataValue.statusCode.isNotGood()) {
|
|
239
|
-
throw new Error(
|
|
239
|
+
throw new Error(` Cannot find dataType Definition ! with nodeId =${dataTypeNodeId.toString()}`);
|
|
240
240
|
}
|
|
241
241
|
const definition = dataTypeDefinitionDataValue.value.value;
|
|
242
242
|
|
|
@@ -244,17 +244,23 @@ async function resolve2(
|
|
|
244
244
|
// Enumeration values are represented as a Int32 in the EnumValueType structure.
|
|
245
245
|
// but on 64 bit platform this value could be returned as a [ high, low] tuple.
|
|
246
246
|
// note that this is not correct as per the spec.
|
|
247
|
-
const convertIn64ToInteger = (a: number[] | number) => Array.isArray(a) ? a[1] : a;
|
|
247
|
+
const convertIn64ToInteger = (a: number[] | number) => (Array.isArray(a) ? a[1] : a);
|
|
248
248
|
|
|
249
249
|
const convert = (fields: EnumField[] | null) => {
|
|
250
250
|
const retVal: Record<string, number> = {};
|
|
251
|
-
fields
|
|
251
|
+
fields?.forEach((field: EnumField) => {
|
|
252
|
+
retVal[field.name || ""] = convertIn64ToInteger(field.value as number[]);
|
|
253
|
+
});
|
|
252
254
|
return retVal;
|
|
253
255
|
};
|
|
254
256
|
if (category === FieldCategory.enumeration) {
|
|
255
257
|
if (dataTypeFactory.hasEnumeration(fieldTypeName)) {
|
|
256
258
|
// skipping already known enumeration
|
|
257
|
-
|
|
259
|
+
const existingEnumeration = dataTypeFactory.getEnumeration(fieldTypeName);
|
|
260
|
+
if (!existingEnumeration) {
|
|
261
|
+
throw new Error(`Internal error: expecting enumeration ${fieldTypeName} to be registered`);
|
|
262
|
+
}
|
|
263
|
+
schema = existingEnumeration;
|
|
258
264
|
} else {
|
|
259
265
|
if (definition instanceof EnumDefinition) {
|
|
260
266
|
const e = new EnumerationDefinitionSchema(dataTypeNodeId, {
|
|
@@ -278,7 +284,10 @@ async function resolve2(
|
|
|
278
284
|
cache
|
|
279
285
|
);
|
|
280
286
|
const structuredTypeSchema = schema as IStructuredTypeSchema;
|
|
281
|
-
if (
|
|
287
|
+
if (
|
|
288
|
+
structuredTypeSchema.encodingDefaultBinary &&
|
|
289
|
+
!dataTypeFactory.hasConstructor(structuredTypeSchema.encodingDefaultBinary)
|
|
290
|
+
) {
|
|
282
291
|
createDynamicObjectConstructor(structuredTypeSchema, dataTypeFactory);
|
|
283
292
|
}
|
|
284
293
|
}
|
|
@@ -312,10 +321,7 @@ async function resolveFieldType(
|
|
|
312
321
|
dataTypeManager: ExtraDataTypeManager,
|
|
313
322
|
cache: ICache
|
|
314
323
|
): Promise<CacheForFieldResolution | null> {
|
|
315
|
-
|
|
316
|
-
|
|
317
324
|
return await memoize(cache, "fieldResolution", dataTypeNodeId, async () => {
|
|
318
|
-
|
|
319
325
|
const dataTypeFactory = dataTypeManager.getDataTypeFactoryForNamespace(dataTypeNodeId.namespace);
|
|
320
326
|
|
|
321
327
|
if (dataTypeNodeId.namespace === 0 && dataTypeNodeId.value === DataTypeIds.Structure) {
|
|
@@ -341,7 +347,7 @@ async function resolveFieldType(
|
|
|
341
347
|
}
|
|
342
348
|
|
|
343
349
|
const readIsAbstract = async (dataTypeNodeId: NodeId): Promise<boolean> => {
|
|
344
|
-
return (await session.read({ nodeId: dataTypeNodeId, attributeId: AttributeIds.IsAbstract })).value.value
|
|
350
|
+
return (await session.read({ nodeId: dataTypeNodeId, attributeId: AttributeIds.IsAbstract })).value.value;
|
|
345
351
|
};
|
|
346
352
|
|
|
347
353
|
const [isAbstract, fieldTypeName, _unusedSuperType] = await Promise.all([
|
|
@@ -352,9 +358,8 @@ async function resolveFieldType(
|
|
|
352
358
|
|
|
353
359
|
if (isAbstract) {
|
|
354
360
|
const _isExtensionObject = await isExtensionObject(session, dataTypeNodeId, cache);
|
|
355
|
-
doDebug &&
|
|
356
|
-
|
|
357
|
-
);
|
|
361
|
+
doDebug &&
|
|
362
|
+
debugLog(` dataType ${dataTypeNodeId.toString()} ${fieldTypeName} is abstract => extObj ?= ${_isExtensionObject}`);
|
|
358
363
|
if (_isExtensionObject) {
|
|
359
364
|
// we could have complex => Structure
|
|
360
365
|
const v3: CacheForFieldResolution = {
|
|
@@ -370,8 +375,7 @@ async function resolveFieldType(
|
|
|
370
375
|
// Note: Only BaseDataType (i=24) should be normalized to "Variant"
|
|
371
376
|
// Other abstract basic types (e.g. Number, Integer) must keep their name
|
|
372
377
|
// for proper subtype validation
|
|
373
|
-
const isBaseDataType = dataTypeNodeId.namespace === 0
|
|
374
|
-
&& dataTypeNodeId.value === DataTypeIds.BaseDataType;
|
|
378
|
+
const isBaseDataType = dataTypeNodeId.namespace === 0 && dataTypeNodeId.value === DataTypeIds.BaseDataType;
|
|
375
379
|
const v3: CacheForFieldResolution = {
|
|
376
380
|
category: FieldCategory.basic,
|
|
377
381
|
fieldTypeName: isBaseDataType ? "Variant" : fieldTypeName,
|
|
@@ -405,7 +409,7 @@ async function resolveFieldType(
|
|
|
405
409
|
/* c8 ignore next */
|
|
406
410
|
if (!schema) {
|
|
407
411
|
throw new Error(
|
|
408
|
-
|
|
412
|
+
`expecting a schema here fieldTypeName=${fieldTypeName} ${dataTypeNodeId.toString()} category = ${category}`
|
|
409
413
|
);
|
|
410
414
|
}
|
|
411
415
|
|
|
@@ -428,7 +432,7 @@ async function _setupEncodings(
|
|
|
428
432
|
schema.dataTypeNodeId = dataTypeNodeId;
|
|
429
433
|
|
|
430
434
|
if (!isAbstract) {
|
|
431
|
-
const encodings =
|
|
435
|
+
const encodings = dataTypeDescription?.encodings || (await _findEncodings(session, dataTypeNodeId));
|
|
432
436
|
schema.encodingDefaultBinary = makeExpandedNodeId(encodings.binaryEncodingNodeId);
|
|
433
437
|
schema.encodingDefaultXml = makeExpandedNodeId(encodings.xmlEncodingNodeId);
|
|
434
438
|
schema.encodingDefaultJson = makeExpandedNodeId(encodings.jsonEncodingNodeId);
|
|
@@ -447,52 +451,58 @@ interface SessionWithCache {
|
|
|
447
451
|
_$$cache2?: Map<string, DataType>;
|
|
448
452
|
_$$cacheHits?: number;
|
|
449
453
|
}
|
|
450
|
-
async function findBasicDataTypeEx(
|
|
454
|
+
async function findBasicDataTypeEx(
|
|
455
|
+
session: IBasicSessionBrowseAsyncSimple,
|
|
456
|
+
dataTypeNodeId: NodeId,
|
|
457
|
+
cache: ICache
|
|
458
|
+
): Promise<DataType> {
|
|
451
459
|
return await memoize(cache, "dataTypes", dataTypeNodeId, async () => {
|
|
452
460
|
const sessionEx = session as SessionWithCache;
|
|
453
|
-
if (!sessionEx._$$cache2) {
|
|
461
|
+
if (!sessionEx._$$cache2) {
|
|
462
|
+
sessionEx._$$cache2 = new Map();
|
|
463
|
+
}
|
|
464
|
+
const cache2 = sessionEx._$$cache2;
|
|
454
465
|
const key = dataTypeNodeId.toString();
|
|
455
|
-
|
|
456
|
-
|
|
466
|
+
const cached = cache2.get(key);
|
|
467
|
+
if (cached !== undefined) {
|
|
468
|
+
sessionEx._$$cacheHits = sessionEx._$$cacheHits === undefined ? 0 : sessionEx._$$cacheHits + 1;
|
|
457
469
|
// debugLog("cache hit 2", key);
|
|
458
|
-
return
|
|
470
|
+
return cached;
|
|
459
471
|
}
|
|
460
472
|
const d = await findBasicDataType(session, dataTypeNodeId);
|
|
461
|
-
|
|
473
|
+
cache2.set(key, d);
|
|
462
474
|
return d;
|
|
463
475
|
});
|
|
464
476
|
}
|
|
465
477
|
|
|
466
|
-
|
|
467
478
|
async function nonReentrant<T>(cache: ICache, prefix: string, dataTypeNodeId: NodeId, func: () => Promise<T>): Promise<T> {
|
|
468
|
-
|
|
469
479
|
const key = prefix + dataTypeNodeId.toString();
|
|
470
480
|
|
|
471
|
-
|
|
472
|
-
|
|
481
|
+
const existingArr = cache.$$resolveStuff?.get(key);
|
|
482
|
+
if (existingArr) {
|
|
483
|
+
doDebug && debugLog(` re-entering !${key}`);
|
|
473
484
|
return await new Promise<T>((resolve, reject) => {
|
|
474
|
-
|
|
485
|
+
existingArr.push([resolve, reject] as ResolveReject<unknown>);
|
|
475
486
|
});
|
|
476
487
|
}
|
|
488
|
+
const arr: ResolveReject<unknown>[] = [];
|
|
477
489
|
cache.$$resolveStuff = cache.$$resolveStuff || new Map();
|
|
478
|
-
cache.$$resolveStuff.set(key,
|
|
490
|
+
cache.$$resolveStuff.set(key, arr);
|
|
479
491
|
|
|
480
492
|
return await new Promise<T>((_resolve, _reject) => {
|
|
481
|
-
|
|
493
|
+
arr.push([_resolve, _reject] as ResolveReject<unknown>);
|
|
482
494
|
|
|
483
495
|
(async () => {
|
|
484
496
|
try {
|
|
485
497
|
const result = await func();
|
|
486
498
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
for (const [resolve] of tmp) {
|
|
499
|
+
cache.$$resolveStuff?.delete(key);
|
|
500
|
+
for (const [resolve] of arr) {
|
|
490
501
|
resolve(result);
|
|
491
502
|
}
|
|
492
503
|
} catch (err) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
for (const [_resolve, reject] of tmp) {
|
|
504
|
+
cache.$$resolveStuff?.delete(key);
|
|
505
|
+
for (const [_resolve, reject] of arr) {
|
|
496
506
|
reject(err as Error);
|
|
497
507
|
}
|
|
498
508
|
}
|
|
@@ -500,8 +510,6 @@ async function nonReentrant<T>(cache: ICache, prefix: string, dataTypeNodeId: No
|
|
|
500
510
|
});
|
|
501
511
|
}
|
|
502
512
|
|
|
503
|
-
|
|
504
|
-
// eslint-disable-next-line max-statements, max-params
|
|
505
513
|
export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
506
514
|
session: IBasicSessionAsync2,
|
|
507
515
|
dataTypeNodeId: NodeId,
|
|
@@ -512,13 +520,8 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
|
512
520
|
isAbstract: boolean,
|
|
513
521
|
cache: ICache
|
|
514
522
|
): Promise<IStructuredTypeSchema> {
|
|
515
|
-
|
|
516
523
|
return await nonReentrant(cache, "convertDataTypeDefinitionToStructureTypeSchema", dataTypeNodeId, async () => {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
524
|
if (definition instanceof StructureDefinition) {
|
|
521
|
-
|
|
522
525
|
const dataTypeFactory = dataTypeManager.getDataTypeFactoryForNamespace(dataTypeNodeId.namespace);
|
|
523
526
|
|
|
524
527
|
let fieldCountToIgnore = 0;
|
|
@@ -552,7 +555,7 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
|
552
555
|
|
|
553
556
|
let switchValue = 1;
|
|
554
557
|
|
|
555
|
-
// let's start at fieldCountToIgnore bits
|
|
558
|
+
// let's start at fieldCountToIgnore bits
|
|
556
559
|
let switchBit = fieldCountToIgnore;
|
|
557
560
|
|
|
558
561
|
const bitFields: BitField[] | undefined = isUnion ? undefined : [];
|
|
@@ -560,7 +563,6 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
|
560
563
|
const postActions: ((schema: IStructuredTypeSchema) => void)[] = [];
|
|
561
564
|
|
|
562
565
|
if (definition.fields) {
|
|
563
|
-
|
|
564
566
|
// pre-fetch all dataTypes in parallel
|
|
565
567
|
const dataTypesToResolve: NodeId[] = [];
|
|
566
568
|
const seen = new Set<string>();
|
|
@@ -582,29 +584,44 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
|
582
584
|
!isAbstract ? _findEncodings(session, dataTypeNodeId) : Promise.resolve(null)
|
|
583
585
|
]);
|
|
584
586
|
|
|
585
|
-
const results = await Promise.all(
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
587
|
+
const results = await Promise.all(
|
|
588
|
+
definition.fields.slice(fieldCountToIgnore).map(async (fieldD) => {
|
|
589
|
+
const isSelf = sameNodeId(fieldD.dataType, dataTypeNodeId);
|
|
590
|
+
// Pre-fetch async data for each field in parallel
|
|
591
|
+
const rt = isSelf ? null : await resolveFieldType(session, fieldD.dataType, dataTypeManager, cache);
|
|
592
|
+
const basicDataType = rt
|
|
593
|
+
? await findBasicDataTypeEx(session, rt.dataType || fieldD.dataType, cache)
|
|
594
|
+
: isSelf
|
|
595
|
+
? DataType.ExtensionObject
|
|
596
|
+
: undefined;
|
|
597
|
+
const fieldTypeNameForSelfRef = isSelf
|
|
598
|
+
? await readBrowseNameWithCache(session, dataTypeNodeId, cache)
|
|
599
|
+
: undefined;
|
|
600
|
+
return { fieldD, rt, basicDataType, fieldTypeNameForSelfRef };
|
|
601
|
+
})
|
|
602
|
+
);
|
|
596
603
|
|
|
597
604
|
for (const { fieldD, rt, basicDataType, fieldTypeNameForSelfRef } of results) {
|
|
598
|
-
|
|
599
605
|
let field: FieldInterfaceOptions | undefined;
|
|
600
606
|
let currentAllowSubTypes: boolean | undefined;
|
|
601
607
|
|
|
602
|
-
({
|
|
608
|
+
({
|
|
609
|
+
field,
|
|
610
|
+
switchBit,
|
|
611
|
+
switchValue,
|
|
612
|
+
allowSubTypes: currentAllowSubTypes
|
|
613
|
+
} = createField(definition, fieldD, switchBit, bitFields, isUnion, switchValue));
|
|
603
614
|
|
|
604
615
|
if (fieldD.dataType.value === dataTypeNodeId.value && fieldD.dataType.namespace === dataTypeNodeId.namespace) {
|
|
605
616
|
// this is a structure with a field of the same type
|
|
606
617
|
// push an empty placeholder that we will fill later
|
|
607
|
-
(
|
|
618
|
+
if (!fieldTypeNameForSelfRef) {
|
|
619
|
+
throw new Error(
|
|
620
|
+
`Internal error: expecting fieldTypeNameForSelfRef to be defined for self-referencing field ${fieldD.name}`
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
field.fieldType = fieldTypeNameForSelfRef;
|
|
624
|
+
field.category = FieldCategory.complex;
|
|
608
625
|
fields.push(field);
|
|
609
626
|
const capturedField = field;
|
|
610
627
|
postActions.push((schema: IStructuredTypeSchema) => {
|
|
@@ -612,11 +629,14 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
|
612
629
|
});
|
|
613
630
|
continue;
|
|
614
631
|
}
|
|
615
|
-
|
|
632
|
+
if (!rt) {
|
|
633
|
+
throw new Error(`Internal error: expecting resolved field type for field ${fieldD.name}`);
|
|
634
|
+
}
|
|
635
|
+
const resolvedField = rt;
|
|
616
636
|
|
|
617
637
|
const { schema, category, fieldTypeName, dataType } = resolvedField;
|
|
618
638
|
|
|
619
|
-
field.fieldType = fieldTypeName
|
|
639
|
+
field.fieldType = fieldTypeName;
|
|
620
640
|
field.category = category;
|
|
621
641
|
field.schema = schema;
|
|
622
642
|
field.dataType = dataType || fieldD.dataType;
|
|
@@ -624,7 +644,6 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
|
624
644
|
field.basicDataType = basicDataType;
|
|
625
645
|
fields.push(field);
|
|
626
646
|
}
|
|
627
|
-
|
|
628
647
|
}
|
|
629
648
|
/// some server may provide definition.baseDataType to be i=22 (ExtensionObject)
|
|
630
649
|
/// instead of 12756 Union;
|
|
@@ -645,7 +664,9 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
|
645
664
|
});
|
|
646
665
|
const structuredTypeSchema = await _setupEncodings(session, dataTypeNodeId, dataTypeDescription, os, isAbstract);
|
|
647
666
|
|
|
648
|
-
postActions.forEach((action) =>
|
|
667
|
+
postActions.forEach((action) => {
|
|
668
|
+
action(structuredTypeSchema);
|
|
669
|
+
});
|
|
649
670
|
|
|
650
671
|
doDebug && debugLog("DONE ! convertDataTypeDefinitionToStructureTypeSchema = ", dataTypeNodeId.toString());
|
|
651
672
|
return structuredTypeSchema;
|
|
@@ -661,24 +682,26 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
|
661
682
|
bitFields: BitField[] | undefined,
|
|
662
683
|
isUnion: boolean,
|
|
663
684
|
switchValue: number
|
|
664
|
-
): { field: FieldInterfaceOptions; switchBit: number; switchValue: number
|
|
665
|
-
|
|
685
|
+
): { field: FieldInterfaceOptions; switchBit: number; switchValue: number; allowSubTypes?: boolean } {
|
|
686
|
+
if (!fieldD.name) {
|
|
687
|
+
throw new Error("Internal error: expecting field name to be defined");
|
|
688
|
+
}
|
|
666
689
|
const field: FieldInterfaceOptions = {
|
|
667
690
|
fieldType: "",
|
|
668
|
-
name: fieldD.name
|
|
691
|
+
name: fieldD.name,
|
|
669
692
|
schema: undefined
|
|
670
693
|
};
|
|
671
694
|
|
|
672
695
|
const definitionAllowSubTypes =
|
|
673
|
-
definition.structureType === StructureType.StructureWithSubtypedValues
|
|
674
|
-
|
|
696
|
+
definition.structureType === StructureType.StructureWithSubtypedValues ||
|
|
697
|
+
definition.structureType === StructureType.UnionWithSubtypedValues;
|
|
675
698
|
|
|
676
699
|
if (fieldD.isOptional) {
|
|
677
700
|
// Optional has a special handling
|
|
678
701
|
if (!definitionAllowSubTypes) {
|
|
679
702
|
// we are in a true optional field structure
|
|
680
703
|
field.switchBit = switchBit++;
|
|
681
|
-
bitFields?.push({ name: fieldD.name
|
|
704
|
+
bitFields?.push({ name: `${fieldD.name}Specified`, length: 1 });
|
|
682
705
|
}
|
|
683
706
|
}
|
|
684
707
|
if (isUnion) {
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { StructureDefinition, StructureType, StructureDefinitionOptions, StructureFieldOptions } from "node-opcua-types";
|
|
1
|
+
import { FieldCategory, type FieldType, type IStructuredTypeSchema } from "node-opcua-factory";
|
|
3
2
|
import { NodeId, resolveNodeId } from "node-opcua-nodeid";
|
|
4
|
-
import {
|
|
3
|
+
import { StructureDefinition, type StructureDefinitionOptions, type StructureFieldOptions, StructureType } from "node-opcua-types";
|
|
5
4
|
|
|
6
5
|
function _getDataType(field: FieldType): NodeId {
|
|
7
6
|
switch (field.category) {
|
|
8
7
|
case FieldCategory.complex:
|
|
9
|
-
return resolveNodeId((field.schema as
|
|
8
|
+
return resolveNodeId((field.schema as unknown as IStructuredTypeSchema).dataTypeNodeId);
|
|
10
9
|
case FieldCategory.basic:
|
|
11
10
|
return resolveNodeId(field.fieldType);
|
|
12
|
-
case FieldCategory.enumeration:
|
|
13
11
|
default:
|
|
14
|
-
|
|
12
|
+
// FieldCategory.enumeration: enumeration fields don't carry a resolvable
|
|
13
|
+
// dataType NodeId here; fall back to the null NodeId, matching the
|
|
14
|
+
// pre-existing (test-verified) behaviour.
|
|
15
|
+
return field.dataType ? resolveNodeId(field.dataType) : new NodeId();
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function convertStructureTypeSchemaToStructureDefinition(st: IStructuredTypeSchema): StructureDefinition {
|
|
19
|
-
|
|
20
20
|
let structureType = StructureType.Invalid;
|
|
21
21
|
let isUnion = false;
|
|
22
22
|
if (st.baseType === "Union") {
|
|
23
23
|
structureType = StructureType.Union;
|
|
24
24
|
isUnion = true;
|
|
25
25
|
} else {
|
|
26
|
-
structureType =
|
|
26
|
+
structureType = StructureType.Structure;
|
|
27
27
|
}
|
|
28
28
|
// convert partial field (not including base class)
|
|
29
29
|
const structureDefinition: StructureDefinitionOptions = {
|
|
@@ -42,7 +42,7 @@ export function convertStructureTypeSchemaToStructureDefinition(st: IStructuredT
|
|
|
42
42
|
arrayDimensions: f.isArray ? [] : undefined,
|
|
43
43
|
valueRank: f.isArray ? 1 : -1,
|
|
44
44
|
dataType,
|
|
45
|
-
isOptional: isUnion ? undefined: f.switchValue !== undefined,
|
|
45
|
+
isOptional: isUnion ? undefined : f.switchValue !== undefined,
|
|
46
46
|
description: f.documentation || undefined,
|
|
47
47
|
name: f.originalName
|
|
48
48
|
});
|