node-opcua-client-dynamic-extension-object 2.127.0 → 2.128.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 +14 -3
- package/dist/convert_data_type_definition_to_structuretype_schema.js +285 -204
- package/dist/convert_data_type_definition_to_structuretype_schema.js.map +1 -1
- package/dist/convert_structuretype_schema_to_structure_definition.js +1 -2
- package/dist/convert_structuretype_schema_to_structure_definition.js.map +1 -1
- package/dist/get_extension_object_constructor.js +1 -2
- package/dist/get_extension_object_constructor.js.map +1 -1
- package/dist/get_extra_data_type_manager.d.ts +3 -1
- package/dist/get_extra_data_type_manager.js +18 -7
- package/dist/get_extra_data_type_manager.js.map +1 -1
- package/dist/populate_data_type_manager.d.ts +7 -1
- package/dist/populate_data_type_manager.js +26 -9
- package/dist/populate_data_type_manager.js.map +1 -1
- package/dist/private/find_encodings.js +1 -2
- package/dist/private/find_encodings.js.map +1 -1
- package/dist/private/populate_data_type_manager_103.js +49 -46
- package/dist/private/populate_data_type_manager_103.js.map +1 -1
- package/dist/private/populate_data_type_manager_104.d.ts +2 -4
- package/dist/private/populate_data_type_manager_104.js +29 -102
- package/dist/private/populate_data_type_manager_104.js.map +1 -1
- package/dist/promote_opaque_structure.js +2 -3
- package/dist/promote_opaque_structure.js.map +1 -1
- package/dist/promote_opaque_structure_in_notification_data.js +1 -2
- package/dist/promote_opaque_structure_in_notification_data.js.map +1 -1
- package/dist/resolve_dynamic_extension_object.js +2 -3
- package/dist/resolve_dynamic_extension_object.js.map +1 -1
- package/package.json +16 -16
- package/source/convert_data_type_definition_to_structuretype_schema.ts +361 -236
- package/source/get_extra_data_type_manager.ts +17 -5
- package/source/populate_data_type_manager.ts +26 -6
- package/source/private/populate_data_type_manager_103.ts +68 -60
- package/source/private/populate_data_type_manager_104.ts +46 -119
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
findBasicDataType,
|
|
23
23
|
IBasicSessionAsync,
|
|
24
24
|
IBasicSessionAsync2,
|
|
25
|
+
IBasicSessionBrowseAsyncSimple,
|
|
25
26
|
IBasicSessionBrowseNextAsync
|
|
26
27
|
} from "node-opcua-pseudo-session";
|
|
27
28
|
import {
|
|
@@ -41,49 +42,102 @@ import { _findEncodings } from "./private/find_encodings";
|
|
|
41
42
|
const debugLog = make_debugLog(__filename);
|
|
42
43
|
const errorLog = make_errorLog(__filename);
|
|
43
44
|
const warningLog = make_warningLog(__filename);
|
|
45
|
+
const doDebug = false;
|
|
46
|
+
|
|
47
|
+
export interface CacheForFieldResolution {
|
|
48
|
+
fieldTypeName: string;
|
|
49
|
+
schema: TypeDefinition;
|
|
50
|
+
category: FieldCategory;
|
|
51
|
+
allowSubType?: boolean;
|
|
52
|
+
dataType?: NodeId;
|
|
53
|
+
}
|
|
54
|
+
export type ResolveReject = [
|
|
55
|
+
resolve: (value: any) => void,
|
|
56
|
+
reject: (err: Error) => void
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
export interface ICache {
|
|
60
|
+
superType?: Map<string, NodeId>;
|
|
61
|
+
fieldResolution?: Map<string, CacheForFieldResolution>;
|
|
62
|
+
dataTypes?: Map<string, DataType>;
|
|
63
|
+
browseNameCache?: Map<string, string>;
|
|
64
|
+
hitCount?: number;
|
|
65
|
+
|
|
66
|
+
$$resolveStuff?: Map<string, ResolveReject[]>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function memoize<T>(cache: ICache, cacheName: keyof Omit<ICache, "hitCount">, nodeId: NodeId, func: () => Promise<T>): Promise<T> {
|
|
70
|
+
const key = nodeId.toString();
|
|
71
|
+
if (cache[cacheName]?.has(key)) {
|
|
72
|
+
cache.hitCount = cache.hitCount === undefined ? 0 : cache.hitCount + 1;
|
|
73
|
+
return cache[cacheName]?.get(key)! as T;
|
|
74
|
+
}
|
|
75
|
+
const value = await func();
|
|
76
|
+
if (!cache[cacheName]) {
|
|
77
|
+
cache[cacheName] = new Map();
|
|
78
|
+
}
|
|
79
|
+
(cache[cacheName] as Map<string,T>).set(key, value);
|
|
80
|
+
return value as T;
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
function fromCache<T>(cache: ICache, cacheName: keyof Omit<ICache, "hitCount">, nodeId: NodeId): T | null
|
|
84
|
+
{
|
|
85
|
+
const key = nodeId.toString();
|
|
86
|
+
if (cache[cacheName]?.has(key)) {
|
|
87
|
+
cache.hitCount = cache.hitCount === undefined ? 0 : cache.hitCount + 1;
|
|
88
|
+
return cache[cacheName]?.get(key)! as T;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function findSuperType(session: IBasicSessionAsync2, dataTypeNodeId: NodeId, cache: ICache): Promise<NodeId> {
|
|
44
94
|
|
|
45
|
-
async function findSuperType(session: IBasicSessionAsync2, dataTypeNodeId: NodeId): Promise<NodeId> {
|
|
46
95
|
if (dataTypeNodeId.namespace === 0 && dataTypeNodeId.value === 24) {
|
|
47
96
|
// BaseDataType !
|
|
48
97
|
return coerceNodeId(0);
|
|
49
98
|
}
|
|
99
|
+
return await memoize(cache, "superType", dataTypeNodeId, async () => {
|
|
100
|
+
|
|
101
|
+
const nodeToBrowse3: BrowseDescriptionLike = {
|
|
102
|
+
browseDirection: BrowseDirection.Inverse,
|
|
103
|
+
includeSubtypes: false,
|
|
104
|
+
nodeClassMask: NodeClassMask.DataType,
|
|
105
|
+
nodeId: dataTypeNodeId,
|
|
106
|
+
referenceTypeId: resolveNodeId("HasSubtype"),
|
|
107
|
+
resultMask: makeResultMask("NodeId | ReferenceType | BrowseName | NodeClass")
|
|
108
|
+
};
|
|
109
|
+
const result3 = await browseAll(session, nodeToBrowse3);
|
|
50
110
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
/* istanbul ignore next */
|
|
68
|
-
if (result3.references.length !== 1) {
|
|
69
|
-
errorLog("Invalid dataType with more than one (or 0) superType", result3.toString());
|
|
70
|
-
throw new Error(
|
|
71
|
-
"Invalid dataType with more than one (or 0) superType " + dataTypeNodeId.toString() + " l=" + result3.references.length
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
return result3.references[0].nodeId;
|
|
111
|
+
/* istanbul ignore next */
|
|
112
|
+
if (result3.statusCode.isNotGood()) {
|
|
113
|
+
throw new Error("Cannot find superType for " + dataTypeNodeId.toString());
|
|
114
|
+
}
|
|
115
|
+
result3.references = result3.references || [];
|
|
116
|
+
|
|
117
|
+
/* istanbul ignore next */
|
|
118
|
+
if (result3.references.length !== 1) {
|
|
119
|
+
errorLog("Invalid dataType with more than one (or 0) superType", result3.toString());
|
|
120
|
+
throw new Error(
|
|
121
|
+
"Invalid dataType with more than one (or 0) superType " + dataTypeNodeId.toString() + " l=" + result3.references.length
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return result3.references[0].nodeId;
|
|
125
|
+
});
|
|
75
126
|
}
|
|
76
127
|
async function findDataTypeCategory(
|
|
77
128
|
session: IBasicSessionAsync2,
|
|
78
|
-
|
|
79
|
-
|
|
129
|
+
dataTypeFactory: DataTypeFactory,
|
|
130
|
+
cache: ICache,
|
|
131
|
+
dataTypeNodeId: NodeId,
|
|
80
132
|
): Promise<FieldCategory> {
|
|
81
|
-
const subTypeNodeId = await findSuperType(session, dataTypeNodeId);
|
|
82
|
-
debugLog("subTypeNodeId of ", dataTypeNodeId.toString(), " is ", subTypeNodeId.toString());
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
133
|
+
const subTypeNodeId = await findSuperType(session, dataTypeNodeId, cache);
|
|
134
|
+
doDebug && debugLog("subTypeNodeId of ", dataTypeNodeId.toString(), " is ", subTypeNodeId.toString());
|
|
135
|
+
|
|
136
|
+
const fieldResolution = fromCache<CacheForFieldResolution>(cache, "fieldResolution", subTypeNodeId);
|
|
137
|
+
if (fieldResolution) {
|
|
138
|
+
return fieldResolution.category;
|
|
86
139
|
}
|
|
140
|
+
|
|
87
141
|
let category: FieldCategory;
|
|
88
142
|
const n = subTypeNodeId as INodeId;
|
|
89
143
|
if (n.identifierType === NodeIdType.NUMERIC && n.namespace === 0 && n.value <= 29) {
|
|
@@ -102,22 +156,23 @@ async function findDataTypeCategory(
|
|
|
102
156
|
return category;
|
|
103
157
|
}
|
|
104
158
|
// must drill down ...
|
|
105
|
-
return await findDataTypeCategory(session, cache, subTypeNodeId);
|
|
159
|
+
return await findDataTypeCategory(session, dataTypeFactory, cache, subTypeNodeId);
|
|
106
160
|
}
|
|
107
161
|
|
|
108
162
|
async function findDataTypeBasicType(
|
|
109
163
|
session: IBasicSessionAsync2,
|
|
110
|
-
cache:
|
|
164
|
+
cache: ICache,
|
|
111
165
|
dataTypeNodeId: NodeId
|
|
112
166
|
): Promise<TypeDefinition> {
|
|
113
|
-
const subTypeNodeId = await findSuperType(session, dataTypeNodeId);
|
|
167
|
+
const subTypeNodeId = await findSuperType(session, dataTypeNodeId, cache);
|
|
114
168
|
|
|
115
169
|
debugLog("subTypeNodeId of ", dataTypeNodeId.toString(), " is ", subTypeNodeId.toString());
|
|
116
170
|
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
return
|
|
171
|
+
const fieldResolution = fromCache<CacheForFieldResolution>(cache,"fieldResolution", subTypeNodeId);
|
|
172
|
+
if (fieldResolution) {
|
|
173
|
+
return fieldResolution.schema;
|
|
120
174
|
}
|
|
175
|
+
|
|
121
176
|
const n = subTypeNodeId as INodeId;
|
|
122
177
|
if (n.identifierType === NodeIdType.NUMERIC && n.namespace === 0 && n.value < 29) {
|
|
123
178
|
switch (n.value) {
|
|
@@ -135,26 +190,22 @@ async function findDataTypeBasicType(
|
|
|
135
190
|
return getBuiltInType(name);
|
|
136
191
|
}
|
|
137
192
|
// must drill down ...
|
|
138
|
-
|
|
193
|
+
const td = await findDataTypeBasicType(session, cache, subTypeNodeId);
|
|
194
|
+
return td;
|
|
139
195
|
}
|
|
140
196
|
|
|
141
|
-
export interface CacheForFieldResolution {
|
|
142
|
-
fieldTypeName: string;
|
|
143
|
-
schema: TypeDefinition;
|
|
144
|
-
category: FieldCategory;
|
|
145
|
-
allowSubType?: boolean;
|
|
146
|
-
dataType?: NodeId;
|
|
147
|
-
}
|
|
148
197
|
|
|
149
|
-
async function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
198
|
+
async function readBrowseNameWithCache(session: IBasicSessionAsync, nodeId: NodeId, cache: ICache): Promise<string> {
|
|
199
|
+
return await memoize(cache, "browseNameCache", nodeId, async () => {
|
|
200
|
+
const dataValue = await session.read({ nodeId, attributeId: AttributeIds.BrowseName });
|
|
201
|
+
if (dataValue.statusCode.isNotGood()) {
|
|
202
|
+
const message =
|
|
203
|
+
"cannot extract BrowseName of nodeId = " + nodeId.toString() + " statusCode = " + dataValue.statusCode.toString();
|
|
204
|
+
debugLog(message);
|
|
205
|
+
throw new Error(message);
|
|
206
|
+
}
|
|
207
|
+
return dataValue.value!.value.name;
|
|
208
|
+
})
|
|
158
209
|
}
|
|
159
210
|
|
|
160
211
|
async function resolve2(
|
|
@@ -162,9 +213,10 @@ async function resolve2(
|
|
|
162
213
|
dataTypeNodeId: NodeId,
|
|
163
214
|
dataTypeFactory: DataTypeFactory,
|
|
164
215
|
fieldTypeName: string,
|
|
165
|
-
cache:
|
|
216
|
+
cache: ICache
|
|
166
217
|
): Promise<{ schema: TypeDefinition | undefined; category: FieldCategory }> {
|
|
167
|
-
|
|
218
|
+
|
|
219
|
+
const category = await findDataTypeCategory(session, dataTypeFactory,cache, dataTypeNodeId);
|
|
168
220
|
debugLog(" type " + fieldTypeName + " has not been seen yet, let resolve it => (category = ", category, " )");
|
|
169
221
|
|
|
170
222
|
let schema: TypeDefinition | undefined = undefined;
|
|
@@ -227,11 +279,11 @@ async function resolve2(
|
|
|
227
279
|
return { schema, category };
|
|
228
280
|
}
|
|
229
281
|
|
|
230
|
-
const isExtensionObject = async (session: IBasicSessionAsync2, dataTypeNodeId: NodeId): Promise<boolean> => {
|
|
282
|
+
const isExtensionObject = async (session: IBasicSessionAsync2, dataTypeNodeId: NodeId, cache: ICache): Promise<boolean> => {
|
|
231
283
|
if (dataTypeNodeId.namespace === 0 && dataTypeNodeId.value === DataType.ExtensionObject) {
|
|
232
284
|
return true;
|
|
233
285
|
}
|
|
234
|
-
const baseDataType = await findSuperType(session, dataTypeNodeId);
|
|
286
|
+
const baseDataType = await findSuperType(session, dataTypeNodeId, cache);
|
|
235
287
|
|
|
236
288
|
const bn = baseDataType as INodeId;
|
|
237
289
|
if (bn.identifierType === NodeIdType.NUMERIC) {
|
|
@@ -242,7 +294,7 @@ const isExtensionObject = async (session: IBasicSessionAsync2, dataTypeNodeId: N
|
|
|
242
294
|
return false;
|
|
243
295
|
}
|
|
244
296
|
}
|
|
245
|
-
return await isExtensionObject(session, baseDataType);
|
|
297
|
+
return await isExtensionObject(session, baseDataType, cache);
|
|
246
298
|
};
|
|
247
299
|
|
|
248
300
|
// eslint-disable-next-line max-statements
|
|
@@ -250,103 +302,105 @@ async function resolveFieldType(
|
|
|
250
302
|
session: IBasicSessionAsync2,
|
|
251
303
|
dataTypeNodeId: NodeId,
|
|
252
304
|
dataTypeFactory: DataTypeFactory,
|
|
253
|
-
cache:
|
|
305
|
+
cache: ICache
|
|
254
306
|
): Promise<CacheForFieldResolution | null> {
|
|
255
|
-
if (dataTypeNodeId.namespace === 0 && dataTypeNodeId.value === 22) {
|
|
256
|
-
// ERN return null;
|
|
257
|
-
const category: FieldCategory = FieldCategory.complex;
|
|
258
|
-
const fieldTypeName = "Structure";
|
|
259
|
-
const schema = ExtensionObject.schema;
|
|
260
|
-
return {
|
|
261
|
-
category,
|
|
262
|
-
fieldTypeName,
|
|
263
|
-
schema,
|
|
264
|
-
allowSubType: true,
|
|
265
|
-
dataType: coerceNodeId(DataType.ExtensionObject)
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
const key = dataTypeNodeId.toString();
|
|
269
|
-
const v = cache[key];
|
|
270
|
-
if (v) {
|
|
271
|
-
return v;
|
|
272
|
-
}
|
|
273
307
|
|
|
274
|
-
if (dataTypeNodeId.value === 0) {
|
|
275
|
-
const v3: CacheForFieldResolution = {
|
|
276
|
-
category: FieldCategory.basic,
|
|
277
|
-
fieldTypeName: "Variant",
|
|
278
|
-
schema: dataTypeFactory.getBuiltInType("Variant")
|
|
279
|
-
};
|
|
280
|
-
cache[key] = v3;
|
|
281
|
-
return v3;
|
|
282
|
-
}
|
|
283
308
|
|
|
284
|
-
|
|
285
|
-
const fieldTypeName = await readBrowseName(session, dataTypeNodeId);
|
|
309
|
+
return await memoize(cache, "fieldResolution", dataTypeNodeId, async () => {
|
|
286
310
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
fieldTypeName: fieldTypeName,
|
|
297
|
-
schema: ExtensionObject.schema,
|
|
311
|
+
if (dataTypeNodeId.namespace === 0 && dataTypeNodeId.value === 22) {
|
|
312
|
+
// ERN return null;
|
|
313
|
+
const category: FieldCategory = FieldCategory.complex;
|
|
314
|
+
const fieldTypeName = "Structure";
|
|
315
|
+
const schema = ExtensionObject.schema;
|
|
316
|
+
return {
|
|
317
|
+
category,
|
|
318
|
+
fieldTypeName,
|
|
319
|
+
schema,
|
|
298
320
|
allowSubType: true,
|
|
299
|
-
dataType:
|
|
321
|
+
dataType: coerceNodeId(DataType.ExtensionObject)
|
|
300
322
|
};
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
// we could have basic => Variant
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (dataTypeNodeId.value === 0) {
|
|
305
326
|
const v3: CacheForFieldResolution = {
|
|
306
327
|
category: FieldCategory.basic,
|
|
307
|
-
fieldTypeName:
|
|
308
|
-
schema: dataTypeFactory.getBuiltInType("Variant")
|
|
309
|
-
allowSubType: true,
|
|
310
|
-
dataType: dataTypeNodeId
|
|
328
|
+
fieldTypeName: "Variant",
|
|
329
|
+
schema: dataTypeFactory.getBuiltInType("Variant")
|
|
311
330
|
};
|
|
312
|
-
cache[key] = v3;
|
|
313
331
|
return v3;
|
|
314
332
|
}
|
|
315
|
-
}
|
|
316
333
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (dataTypeFactory.hasStructureByTypeName(fieldTypeName!)) {
|
|
321
|
-
schema = dataTypeFactory.getStructuredTypeSchema(fieldTypeName);
|
|
322
|
-
category = FieldCategory.complex;
|
|
323
|
-
} else if (dataTypeFactory.hasBuiltInType(fieldTypeName!)) {
|
|
324
|
-
category = FieldCategory.basic;
|
|
325
|
-
schema = dataTypeFactory.getBuiltInType(fieldTypeName!);
|
|
326
|
-
} else if (dataTypeFactory.hasEnumeration(fieldTypeName!)) {
|
|
327
|
-
category = FieldCategory.enumeration;
|
|
328
|
-
schema = dataTypeFactory.getEnumeration(fieldTypeName!)!;
|
|
329
|
-
} else {
|
|
330
|
-
debugLog(" type " + fieldTypeName + " has not been seen yet, let resolve it");
|
|
331
|
-
const res = await resolve2(session, dataTypeNodeId, dataTypeFactory, fieldTypeName, cache);
|
|
332
|
-
schema = res.schema;
|
|
333
|
-
category = res.category;
|
|
334
|
-
}
|
|
334
|
+
const readIsAbstract = async (dataTypeNodeId: NodeId): Promise<boolean> => {
|
|
335
|
+
return (await session.read({ nodeId: dataTypeNodeId, attributeId: AttributeIds.IsAbstract })).value.value
|
|
336
|
+
};
|
|
335
337
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
338
|
+
const [isAbstract, fieldTypeName] = await Promise.all([
|
|
339
|
+
readIsAbstract(dataTypeNodeId),
|
|
340
|
+
readBrowseNameWithCache(session, dataTypeNodeId, cache)
|
|
341
|
+
]);
|
|
342
|
+
|
|
343
|
+
if (isAbstract) {
|
|
344
|
+
const _isExtensionObject = await isExtensionObject(session, dataTypeNodeId, cache);
|
|
345
|
+
debugLog(
|
|
346
|
+
" dataType " + dataTypeNodeId.toString() + " " + fieldTypeName + " is abstract => extObj ?= " + _isExtensionObject
|
|
347
|
+
);
|
|
348
|
+
if (_isExtensionObject) {
|
|
349
|
+
// we could have complex => Structure
|
|
350
|
+
const v3: CacheForFieldResolution = {
|
|
351
|
+
category: FieldCategory.complex,
|
|
352
|
+
fieldTypeName: fieldTypeName,
|
|
353
|
+
schema: ExtensionObject.schema,
|
|
354
|
+
allowSubType: true,
|
|
355
|
+
dataType: dataTypeNodeId
|
|
356
|
+
};
|
|
357
|
+
return v3;
|
|
358
|
+
} else {
|
|
359
|
+
// we could have basic => Variant
|
|
360
|
+
const v3: CacheForFieldResolution = {
|
|
361
|
+
category: FieldCategory.basic,
|
|
362
|
+
fieldTypeName: fieldTypeName,
|
|
363
|
+
schema: dataTypeFactory.getBuiltInType("Variant"),
|
|
364
|
+
allowSubType: true,
|
|
365
|
+
dataType: dataTypeNodeId
|
|
366
|
+
};
|
|
367
|
+
return v3;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
342
370
|
|
|
343
|
-
|
|
344
|
-
category
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
371
|
+
let schema: TypeDefinition | undefined;
|
|
372
|
+
let category: FieldCategory = FieldCategory.enumeration;
|
|
373
|
+
|
|
374
|
+
if (dataTypeFactory.hasStructureByTypeName(fieldTypeName!)) {
|
|
375
|
+
schema = dataTypeFactory.getStructuredTypeSchema(fieldTypeName);
|
|
376
|
+
category = FieldCategory.complex;
|
|
377
|
+
} else if (dataTypeFactory.hasBuiltInType(fieldTypeName!)) {
|
|
378
|
+
category = FieldCategory.basic;
|
|
379
|
+
schema = dataTypeFactory.getBuiltInType(fieldTypeName!);
|
|
380
|
+
} else if (dataTypeFactory.hasEnumeration(fieldTypeName!)) {
|
|
381
|
+
category = FieldCategory.enumeration;
|
|
382
|
+
schema = dataTypeFactory.getEnumeration(fieldTypeName!)!;
|
|
383
|
+
} else {
|
|
384
|
+
debugLog(" type " + fieldTypeName + " has not been seen yet, let resolve it");
|
|
385
|
+
const res = await resolve2(session, dataTypeNodeId, dataTypeFactory, fieldTypeName, cache);
|
|
386
|
+
schema = res.schema;
|
|
387
|
+
category = res.category;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* istanbul ignore next */
|
|
391
|
+
if (!schema) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
"expecting a schema here fieldTypeName=" + fieldTypeName + " " + dataTypeNodeId.toString() + " category = " + category
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const v2: CacheForFieldResolution = {
|
|
398
|
+
category,
|
|
399
|
+
fieldTypeName,
|
|
400
|
+
schema
|
|
401
|
+
};
|
|
402
|
+
return v2;
|
|
403
|
+
});
|
|
350
404
|
}
|
|
351
405
|
|
|
352
406
|
async function _setupEncodings(
|
|
@@ -375,6 +429,64 @@ export interface IDataTypeDescriptionMini {
|
|
|
375
429
|
isAbstract?: boolean;
|
|
376
430
|
}
|
|
377
431
|
|
|
432
|
+
interface SessionWithCache {
|
|
433
|
+
_$$cache2?: Map<string, DataType>;
|
|
434
|
+
_$$cacheHits?: number;
|
|
435
|
+
}
|
|
436
|
+
async function findBasicDataTypeEx(session: IBasicSessionBrowseAsyncSimple, dataTypeNodeId: NodeId, cache: ICache): Promise<DataType> {
|
|
437
|
+
return await memoize(cache, "dataTypes", dataTypeNodeId, async () => {
|
|
438
|
+
const sessionEx = session as SessionWithCache;
|
|
439
|
+
if (!sessionEx._$$cache2) { sessionEx._$$cache2 = new Map(); }
|
|
440
|
+
const key = dataTypeNodeId.toString();
|
|
441
|
+
if (sessionEx._$$cache2.has(key)) {
|
|
442
|
+
sessionEx._$$cacheHits = sessionEx._$$cacheHits == undefined ? 0 : sessionEx._$$cacheHits + 1;
|
|
443
|
+
// console.log("cache hit 2", key);
|
|
444
|
+
return sessionEx._$$cache2.get(key)!;
|
|
445
|
+
}
|
|
446
|
+
const d = await findBasicDataType(session, dataTypeNodeId);
|
|
447
|
+
sessionEx._$$cache2.set(key, d);
|
|
448
|
+
return d;
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
async function nonReentrant<T>(cache:ICache, prefix: string, dataTypeNodeId:NodeId, func: ()=>Promise<T>):Promise<T> {
|
|
454
|
+
|
|
455
|
+
const key = prefix + dataTypeNodeId.toString();
|
|
456
|
+
|
|
457
|
+
if (cache.$$resolveStuff?.has(key)) {
|
|
458
|
+
doDebug && console.log(" re-entering !" + key);
|
|
459
|
+
return await new Promise<T>((resolve, reject) => {
|
|
460
|
+
cache.$$resolveStuff?.get(key)!.push([resolve, reject]);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
cache.$$resolveStuff = cache.$$resolveStuff || new Map();
|
|
464
|
+
cache.$$resolveStuff.set(key, []);
|
|
465
|
+
|
|
466
|
+
return await new Promise<T>((_resolve, _reject) => {
|
|
467
|
+
cache.$$resolveStuff!.get(key)!.push([_resolve, _reject]);
|
|
468
|
+
|
|
469
|
+
(async () => {
|
|
470
|
+
try {
|
|
471
|
+
const result = await func();
|
|
472
|
+
|
|
473
|
+
const tmp = cache.$$resolveStuff!.get(key)!;
|
|
474
|
+
cache.$$resolveStuff!.delete(key);
|
|
475
|
+
for (const [resolve] of tmp) {
|
|
476
|
+
resolve(result);
|
|
477
|
+
}
|
|
478
|
+
} catch (err) {
|
|
479
|
+
const tmp = cache.$$resolveStuff!.get(key)!;
|
|
480
|
+
cache.$$resolveStuff!.delete(key);
|
|
481
|
+
for (const [_resolve, reject] of tmp) {
|
|
482
|
+
reject(err as Error);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
})();
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
|
|
378
490
|
// eslint-disable-next-line max-statements, max-params
|
|
379
491
|
export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
380
492
|
session: IBasicSessionAsync2,
|
|
@@ -384,111 +496,124 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
|
|
|
384
496
|
dataTypeDescription: IDataTypeDescriptionMini | null,
|
|
385
497
|
dataTypeFactory: DataTypeFactory,
|
|
386
498
|
isAbstract: boolean,
|
|
387
|
-
cache:
|
|
499
|
+
cache: ICache
|
|
388
500
|
): Promise<IStructuredTypeSchema> {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
switch (definition.structureType) {
|
|
408
|
-
case StructureType.Union:
|
|
409
|
-
fields.push({
|
|
410
|
-
fieldType: "UInt32",
|
|
411
|
-
name: "SwitchField"
|
|
412
|
-
});
|
|
413
|
-
break;
|
|
414
|
-
case StructureType.Structure:
|
|
415
|
-
case StructureType.StructureWithOptionalFields:
|
|
416
|
-
break;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
let switchValue = 1;
|
|
420
|
-
let switchBit = 0;
|
|
501
|
+
|
|
502
|
+
return await nonReentrant(cache, "convertDataTypeDefinitionToStructureTypeSchema", dataTypeNodeId, async () => {
|
|
503
|
+
|
|
504
|
+
// warningLog(">> convertDataTypeDefinitionToStructureTypeSchema = ", dataTypeNodeId.toString());
|
|
505
|
+
if (definition instanceof StructureDefinition) {
|
|
506
|
+
let fieldCountToIgnore = 0;
|
|
507
|
+
const structureInfo = dataTypeFactory.getStructureInfoForDataType(definition.baseDataType);
|
|
508
|
+
const baseSchema: IStructuredTypeSchema | undefined | null = structureInfo?.schema;
|
|
509
|
+
|
|
510
|
+
if (baseSchema) {
|
|
511
|
+
const possibleFields = extractAllPossibleFields(baseSchema);
|
|
512
|
+
fieldCountToIgnore += possibleFields.length;
|
|
513
|
+
}
|
|
514
|
+
// while (base && !(base.dataTypeNodeId.value === DataType.ExtensionObject && base.dataTypeNodeId.namespace === 0)) {
|
|
515
|
+
// fieldCountToIgnore += base..length;
|
|
516
|
+
// base = base.getBaseSchema();
|
|
517
|
+
// }
|
|
421
518
|
|
|
422
|
-
|
|
519
|
+
const fields: FieldInterfaceOptions[] = [];
|
|
423
520
|
|
|
424
|
-
|
|
521
|
+
const isUnion = definition.structureType === StructureType.Union;
|
|
425
522
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
523
|
+
switch (definition.structureType) {
|
|
524
|
+
case StructureType.Union:
|
|
525
|
+
fields.push({
|
|
526
|
+
fieldType: "UInt32",
|
|
527
|
+
name: "SwitchField"
|
|
528
|
+
});
|
|
529
|
+
break;
|
|
530
|
+
case StructureType.Structure:
|
|
531
|
+
case StructureType.StructureWithOptionalFields:
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
430
534
|
|
|
431
|
-
|
|
432
|
-
|
|
535
|
+
let switchValue = 1;
|
|
536
|
+
let switchBit = 0;
|
|
537
|
+
|
|
538
|
+
const bitFields: BitField[] | undefined = isUnion ? undefined : [];
|
|
539
|
+
|
|
540
|
+
const postActions: ((schema: IStructuredTypeSchema) => void)[] = [];
|
|
541
|
+
|
|
542
|
+
if (definition.fields) {
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
for (let i = fieldCountToIgnore; i < definition.fields.length; i++) {
|
|
546
|
+
const fieldD = definition.fields[i];
|
|
547
|
+
// we need to skip fields that have already been handled in base class
|
|
548
|
+
// promises.push((
|
|
549
|
+
await (async () => {
|
|
550
|
+
|
|
551
|
+
let field: FieldInterfaceOptions | undefined;
|
|
552
|
+
({ field, switchBit, switchValue } = createField(fieldD, switchBit, bitFields, isUnion, switchValue));
|
|
553
|
+
|
|
554
|
+
if (fieldD.dataType.value === dataTypeNodeId.value && fieldD.dataType.namespace === dataTypeNodeId.namespace) {
|
|
555
|
+
// this is a structure with a field of the same type
|
|
556
|
+
// push an empty placeholder that we will fill later
|
|
557
|
+
const fieldTypeName = await readBrowseNameWithCache(session, dataTypeNodeId, cache);
|
|
558
|
+
(field.fieldType = fieldTypeName!), (field.category = FieldCategory.complex);
|
|
559
|
+
fields.push(field);
|
|
560
|
+
const capturedField = field;
|
|
561
|
+
postActions.push((schema: IStructuredTypeSchema) => {
|
|
562
|
+
capturedField.schema = schema;
|
|
563
|
+
});
|
|
564
|
+
return;;
|
|
565
|
+
}
|
|
566
|
+
const rt = (await resolveFieldType(session, fieldD.dataType, dataTypeFactory, cache))!;
|
|
567
|
+
if (!rt) {
|
|
568
|
+
errorLog(
|
|
569
|
+
"convertDataTypeDefinitionToStructureTypeSchema cannot handle field",
|
|
570
|
+
fieldD.name,
|
|
571
|
+
"in",
|
|
572
|
+
name,
|
|
573
|
+
"because " + fieldD.dataType.toString() + " cannot be resolved"
|
|
574
|
+
);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const { schema, category, fieldTypeName, dataType, allowSubType } = rt;
|
|
578
|
+
|
|
579
|
+
field.fieldType = fieldTypeName!;
|
|
580
|
+
field.category = category;
|
|
581
|
+
field.schema = schema;
|
|
582
|
+
field.dataType = dataType || fieldD.dataType;
|
|
583
|
+
field.allowSubType = allowSubType || false;
|
|
584
|
+
field.basicDataType = await findBasicDataTypeEx(session, field.dataType, cache);
|
|
585
|
+
fields.push(field);
|
|
586
|
+
})();
|
|
587
|
+
// ));
|
|
433
588
|
|
|
434
|
-
if (fieldD.dataType.value === dataTypeNodeId.value && fieldD.dataType.namespace === dataTypeNodeId.namespace) {
|
|
435
|
-
// this is a structure with a field of the same type
|
|
436
|
-
// push an empty placeholder that we will fill later
|
|
437
|
-
const fieldTypeName = await readBrowseName(session, dataTypeNodeId);
|
|
438
|
-
(field.fieldType = fieldTypeName!), (field.category = FieldCategory.complex);
|
|
439
|
-
fields.push(field);
|
|
440
|
-
const capturedField = field;
|
|
441
|
-
postActions.push((schema: IStructuredTypeSchema) => {
|
|
442
|
-
capturedField.schema = schema;
|
|
443
|
-
});
|
|
444
|
-
continue;
|
|
445
589
|
}
|
|
446
|
-
const rt = (await resolveFieldType(session, fieldD.dataType, dataTypeFactory, cache))!;
|
|
447
|
-
if (!rt) {
|
|
448
|
-
errorLog(
|
|
449
|
-
"convertDataTypeDefinitionToStructureTypeSchema cannot handle field",
|
|
450
|
-
fieldD.name,
|
|
451
|
-
"in",
|
|
452
|
-
name,
|
|
453
|
-
"because " + fieldD.dataType.toString() + " cannot be resolved"
|
|
454
|
-
);
|
|
455
|
-
continue;
|
|
456
|
-
}
|
|
457
|
-
const { schema, category, fieldTypeName, dataType, allowSubType } = rt;
|
|
458
|
-
|
|
459
|
-
field.fieldType = fieldTypeName!;
|
|
460
|
-
field.category = category;
|
|
461
|
-
field.schema = schema;
|
|
462
|
-
field.dataType = dataType || fieldD.dataType;
|
|
463
|
-
field.allowSubType = allowSubType || false;
|
|
464
|
-
field.basicDataType = await findBasicDataType(session, field.dataType);
|
|
465
|
-
fields.push(field);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
/// some server may provide definition.baseDataType to be i=22 (ExtensionObject)
|
|
469
|
-
/// instead of 12756 Union;
|
|
470
|
-
if (isUnion && sameNodeId(definition.baseDataType, coerceNodeId("i=22"))) {
|
|
471
|
-
definition.baseDataType = resolveNodeId("i=1276"); // aka DataTypeIds.Union
|
|
472
|
-
}
|
|
473
590
|
|
|
474
|
-
|
|
475
|
-
|
|
591
|
+
}
|
|
592
|
+
/// some server may provide definition.baseDataType to be i=22 (ExtensionObject)
|
|
593
|
+
/// instead of 12756 Union;
|
|
594
|
+
if (isUnion && sameNodeId(definition.baseDataType, coerceNodeId("i=22"))) {
|
|
595
|
+
definition.baseDataType = resolveNodeId("i=1276"); // aka DataTypeIds.Union
|
|
596
|
+
}
|
|
476
597
|
|
|
477
|
-
|
|
478
|
-
baseType
|
|
479
|
-
bitFields,
|
|
480
|
-
fields,
|
|
481
|
-
name,
|
|
482
|
-
dataTypeFactory
|
|
483
|
-
});
|
|
484
|
-
const structuredTypeSchema = await _setupEncodings(session, dataTypeNodeId, dataTypeDescription, os);
|
|
598
|
+
const a = await resolveFieldType(session, definition.baseDataType, dataTypeFactory, cache);
|
|
599
|
+
const baseType = a ? a.fieldTypeName : isUnion ? "Union" : "ExtensionObject";
|
|
485
600
|
|
|
486
|
-
|
|
601
|
+
const os = new StructuredTypeSchema({
|
|
602
|
+
baseType,
|
|
603
|
+
bitFields,
|
|
604
|
+
fields,
|
|
605
|
+
name,
|
|
606
|
+
dataTypeFactory
|
|
607
|
+
});
|
|
608
|
+
const structuredTypeSchema = await _setupEncodings(session, dataTypeNodeId, dataTypeDescription, os);
|
|
487
609
|
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
throw new Error("Not Implemented");
|
|
610
|
+
postActions.forEach((action) => action(structuredTypeSchema));
|
|
491
611
|
|
|
612
|
+
doDebug && console.log("DONE ! convertDataTypeDefinitionToStructureTypeSchema = ", dataTypeNodeId.toString());
|
|
613
|
+
return structuredTypeSchema;
|
|
614
|
+
}
|
|
615
|
+
throw new Error("Not Implemented");
|
|
616
|
+
});
|
|
492
617
|
function createField(
|
|
493
618
|
fieldD: StructureField,
|
|
494
619
|
switchBit: number,
|