node-opcua-client-dynamic-extension-object 2.127.1 → 2.129.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.
@@ -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
- const nodeToBrowse3: BrowseDescriptionLike = {
52
- browseDirection: BrowseDirection.Inverse,
53
- includeSubtypes: false,
54
- nodeClassMask: NodeClassMask.DataType,
55
- nodeId: dataTypeNodeId,
56
- referenceTypeId: resolveNodeId("HasSubtype"),
57
- resultMask: makeResultMask("NodeId | ReferenceType | BrowseName | NodeClass")
58
- };
59
- const result3 = await browseAll(session, nodeToBrowse3);
60
-
61
- /* istanbul ignore next */
62
- if (result3.statusCode.isNotGood()) {
63
- throw new Error("Cannot find superType for " + dataTypeNodeId.toString());
64
- }
65
- result3.references = result3.references || [];
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
- cache: { [key: string]: CacheForFieldResolution },
79
- dataTypeNodeId: NodeId
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
- const key = subTypeNodeId.toString();
84
- if (cache[key]) {
85
- return cache[key].category;
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: { [key: string]: CacheForFieldResolution },
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 key = subTypeNodeId.toString();
118
- if (cache[key]) {
119
- return cache[key].schema;
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
- return await findDataTypeBasicType(session, cache, subTypeNodeId);
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 readBrowseName(session: IBasicSessionAsync, nodeId: NodeId): Promise<string> {
150
- const dataValue = await session.read({ nodeId, attributeId: AttributeIds.BrowseName });
151
- if (dataValue.statusCode.isNotGood()) {
152
- const message =
153
- "cannot extract BrowseName of nodeId = " + nodeId.toString() + " statusCode = " + dataValue.statusCode.toString();
154
- debugLog(message);
155
- throw new Error(message);
156
- }
157
- return dataValue.value!.value.name;
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: { [key: string]: CacheForFieldResolution }
216
+ cache: ICache
166
217
  ): Promise<{ schema: TypeDefinition | undefined; category: FieldCategory }> {
167
- const category = await findDataTypeCategory(session, cache, dataTypeNodeId);
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: { [key: string]: CacheForFieldResolution }
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
- const isAbstract = (await session.read({ nodeId: dataTypeNodeId, attributeId: AttributeIds.IsAbstract })).value.value;
285
- const fieldTypeName = await readBrowseName(session, dataTypeNodeId);
309
+ return await memoize(cache, "fieldResolution", dataTypeNodeId, async () => {
286
310
 
287
- if (isAbstract) {
288
- const _isExtensionObject = await isExtensionObject(session, dataTypeNodeId);
289
- debugLog(
290
- " dataType " + dataTypeNodeId.toString() + " " + fieldTypeName + " is abstract => extObj ?= " + _isExtensionObject
291
- );
292
- if (_isExtensionObject) {
293
- // we could have complex => Structure
294
- const v3: CacheForFieldResolution = {
295
- category: FieldCategory.complex,
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: dataTypeNodeId
321
+ dataType: coerceNodeId(DataType.ExtensionObject)
300
322
  };
301
- cache[key] = v3;
302
- return v3;
303
- } else {
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: 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
- let schema: TypeDefinition | undefined;
318
- let category: FieldCategory = FieldCategory.enumeration;
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
- /* istanbul ignore next */
337
- if (!schema) {
338
- throw new Error(
339
- "expecting a schema here fieldTypeName=" + fieldTypeName + " " + dataTypeNodeId.toString() + " category = " + category
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
- const v2: CacheForFieldResolution = {
344
- category,
345
- fieldTypeName,
346
- schema
347
- };
348
- cache[key] = v2;
349
- return v2;
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: { [key: string]: CacheForFieldResolution }
499
+ cache: ICache
388
500
  ): Promise<IStructuredTypeSchema> {
389
- if (definition instanceof StructureDefinition) {
390
- let fieldCountToIgnore = 0;
391
- const structureInfo = dataTypeFactory.getStructureInfoForDataType(definition.baseDataType);
392
- const baseSchema: IStructuredTypeSchema | undefined | null = structureInfo?.schema;
393
-
394
- if (baseSchema) {
395
- const possibleFields = extractAllPossibleFields(baseSchema);
396
- fieldCountToIgnore += possibleFields.length;
397
- }
398
- // while (base && !(base.dataTypeNodeId.value === DataType.ExtensionObject && base.dataTypeNodeId.namespace === 0)) {
399
- // fieldCountToIgnore += base..length;
400
- // base = base.getBaseSchema();
401
- // }
402
-
403
- const fields: FieldInterfaceOptions[] = [];
404
-
405
- const isUnion = definition.structureType === StructureType.Union;
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
- const bitFields: BitField[] | undefined = isUnion ? undefined : [];
519
+ const fields: FieldInterfaceOptions[] = [];
423
520
 
424
- const postActions: ((schema: IStructuredTypeSchema) => void)[] = [];
521
+ const isUnion = definition.structureType === StructureType.Union;
425
522
 
426
- if (definition.fields) {
427
- for (let i = fieldCountToIgnore; i < definition.fields.length; i++) {
428
- const fieldD = definition.fields[i];
429
- // we need to skip fields that have already been handled in base class
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
- let field: FieldInterfaceOptions | undefined;
432
- ({ field, switchBit, switchValue } = createField(fieldD, switchBit, bitFields, isUnion, switchValue));
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
- const a = await resolveFieldType(session, definition.baseDataType, dataTypeFactory, cache);
475
- const baseType = a ? a.fieldTypeName : isUnion ? "Union" : "ExtensionObject";
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
- const os = new StructuredTypeSchema({
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
- postActions.forEach((action) => action(structuredTypeSchema));
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
- return structuredTypeSchema;
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,