node-opcua-client-dynamic-extension-object 2.162.0 → 2.163.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/convert_data_type_definition_to_structuretype_schema.d.ts +4 -3
  2. package/dist/convert_data_type_definition_to_structuretype_schema.js +96 -66
  3. package/dist/convert_data_type_definition_to_structuretype_schema.js.map +1 -1
  4. package/dist/extra_data_type_manager.d.ts +10 -1
  5. package/dist/extra_data_type_manager.js +96 -2
  6. package/dist/extra_data_type_manager.js.map +1 -1
  7. package/dist/get_extension_object_constructor.js +4 -4
  8. package/dist/get_extension_object_constructor.js.map +1 -1
  9. package/dist/get_extra_data_type_manager.d.ts +2 -1
  10. package/dist/get_extra_data_type_manager.js +8 -2
  11. package/dist/get_extra_data_type_manager.js.map +1 -1
  12. package/dist/populate_data_type_manager.d.ts +2 -4
  13. package/dist/populate_data_type_manager.js +88 -81
  14. package/dist/populate_data_type_manager.js.map +1 -1
  15. package/dist/private/populate_data_type_manager_103.js +5 -4
  16. package/dist/private/populate_data_type_manager_103.js.map +1 -1
  17. package/dist/private/populate_data_type_manager_104.d.ts +1 -2
  18. package/dist/private/populate_data_type_manager_104.js +41 -18
  19. package/dist/private/populate_data_type_manager_104.js.map +1 -1
  20. package/dist/resolve_dynamic_extension_object.d.ts +2 -2
  21. package/dist/resolve_dynamic_extension_object.js +66 -91
  22. package/dist/resolve_dynamic_extension_object.js.map +1 -1
  23. package/package.json +12 -12
  24. package/source/convert_data_type_definition_to_structuretype_schema.ts +116 -79
  25. package/source/extra_data_type_manager.ts +113 -4
  26. package/source/get_extension_object_constructor.ts +5 -4
  27. package/source/get_extra_data_type_manager.ts +15 -7
  28. package/source/populate_data_type_manager.ts +99 -89
  29. package/source/private/populate_data_type_manager_103.ts +6 -4
  30. package/source/private/populate_data_type_manager_104.ts +67 -28
  31. package/source/resolve_dynamic_extension_object.ts +79 -110
@@ -4,7 +4,6 @@ import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "n
4
4
  import { INodeId, NodeIdType, sameNodeId } from "node-opcua-nodeid";
5
5
  import {
6
6
  BitField,
7
- DataTypeFactory,
8
7
  EnumerationDefinitionSchema,
9
8
  extractAllPossibleFields,
10
9
  FieldCategory,
@@ -33,11 +32,12 @@ import {
33
32
  EnumField
34
33
  } from "node-opcua-types";
35
34
  import { ExtensionObject } from "node-opcua-extension-object";
36
- import { DataTypeAndEncodingId } from "node-opcua-schemas";
35
+ import { DataTypeAndEncodingId, createDynamicObjectConstructor } from "node-opcua-schemas";
37
36
  //
38
37
  import { DataType } from "node-opcua-variant";
39
38
  import { DataTypeIds } from "node-opcua-constants";
40
39
  import { _findEncodings } from "./private/find_encodings";
40
+ import { ExtraDataTypeManager } from "./extra_data_type_manager";
41
41
 
42
42
  const debugLog = make_debugLog(__filename);
43
43
  const doDebug = checkDebugFlag(__filename);
@@ -48,7 +48,7 @@ export interface CacheForFieldResolution {
48
48
  fieldTypeName: string;
49
49
  schema: TypeDefinition;
50
50
  category: FieldCategory;
51
- allowSubType?: boolean;
51
+ allowSubTypes?: boolean;
52
52
  dataType?: NodeId;
53
53
  }
54
54
  export type ResolveReject<T> = [
@@ -125,7 +125,6 @@ async function findSuperType(session: IBasicSessionAsync2, dataTypeNodeId: NodeI
125
125
  }
126
126
  async function findDataTypeCategory(
127
127
  session: IBasicSessionAsync2,
128
- dataTypeFactory: DataTypeFactory,
129
128
  cache: ICache,
130
129
  dataTypeNodeId: NodeId,
131
130
  ): Promise<FieldCategory> {
@@ -155,7 +154,7 @@ async function findDataTypeCategory(
155
154
  return category;
156
155
  }
157
156
  // must drill down ...
158
- return await findDataTypeCategory(session, dataTypeFactory, cache, subTypeNodeId);
157
+ return await findDataTypeCategory(session, cache, subTypeNodeId);
159
158
  }
160
159
 
161
160
  async function findDataTypeBasicType(
@@ -210,12 +209,13 @@ async function readBrowseNameWithCache(session: IBasicSessionAsync, nodeId: Node
210
209
  async function resolve2(
211
210
  session: IBasicSessionAsync2,
212
211
  dataTypeNodeId: NodeId,
213
- dataTypeFactory: DataTypeFactory,
212
+ dataTypeManager: ExtraDataTypeManager,
214
213
  fieldTypeName: string,
215
214
  cache: ICache
216
215
  ): Promise<{ schema: TypeDefinition | undefined; category: FieldCategory }> {
217
216
 
218
- const category = await findDataTypeCategory(session, dataTypeFactory, cache, dataTypeNodeId);
217
+ const dataTypeFactory = dataTypeManager.getDataTypeFactoryForNamespace(dataTypeNodeId.namespace);
218
+ const category = await findDataTypeCategory(session, cache, dataTypeNodeId);
219
219
  doDebug && debugLog(" type " + fieldTypeName + " has not been seen yet, let resolve it => (category = ", category, " )");
220
220
 
221
221
  let schema: TypeDefinition | undefined = undefined;
@@ -274,12 +274,15 @@ async function resolve2(
274
274
  fieldTypeName,
275
275
  definition,
276
276
  null,
277
- dataTypeFactory,
277
+ dataTypeManager,
278
278
  isAbstract,
279
279
  cache
280
280
  );
281
+ const structuredTypeSchema = schema as IStructuredTypeSchema;
282
+ if (structuredTypeSchema.encodingDefaultBinary && !dataTypeFactory.hasConstructor(structuredTypeSchema.encodingDefaultBinary)) {
283
+ createDynamicObjectConstructor(structuredTypeSchema, dataTypeFactory);
284
+ }
281
285
  }
282
- // xx const schema1 = dataTypeFactory.getStructuredTypeSchema(fieldTypeName);
283
286
  }
284
287
  break;
285
288
  }
@@ -304,19 +307,19 @@ const isExtensionObject = async (session: IBasicSessionAsync2, dataTypeNodeId: N
304
307
  return await isExtensionObject(session, baseDataType, cache);
305
308
  };
306
309
 
307
- // eslint-disable-next-line max-statements
308
310
  async function resolveFieldType(
309
311
  session: IBasicSessionAsync2,
310
312
  dataTypeNodeId: NodeId,
311
- dataTypeFactory: DataTypeFactory,
313
+ dataTypeManager: ExtraDataTypeManager,
312
314
  cache: ICache
313
315
  ): Promise<CacheForFieldResolution | null> {
314
316
 
315
317
 
316
318
  return await memoize(cache, "fieldResolution", dataTypeNodeId, async () => {
317
319
 
320
+ const dataTypeFactory = dataTypeManager.getDataTypeFactoryForNamespace(dataTypeNodeId.namespace);
321
+
318
322
  if (dataTypeNodeId.namespace === 0 && dataTypeNodeId.value === DataTypeIds.Structure) {
319
- // ERN return null;
320
323
  const category: FieldCategory = FieldCategory.complex;
321
324
  const fieldTypeName = "Structure";
322
325
  const schema = ExtensionObject.schema;
@@ -324,7 +327,7 @@ async function resolveFieldType(
324
327
  category,
325
328
  fieldTypeName,
326
329
  schema,
327
- allowSubType: true,
330
+ allowSubTypes: true,
328
331
  dataType: coerceNodeId(DataType.ExtensionObject)
329
332
  };
330
333
  }
@@ -333,7 +336,7 @@ async function resolveFieldType(
333
336
  const v3: CacheForFieldResolution = {
334
337
  category: FieldCategory.basic,
335
338
  fieldTypeName: "Variant",
336
- schema: dataTypeFactory.getBuiltInType("Variant")
339
+ schema: dataTypeManager.getBuiltInType("Variant")
337
340
  };
338
341
  return v3;
339
342
  }
@@ -342,9 +345,10 @@ async function resolveFieldType(
342
345
  return (await session.read({ nodeId: dataTypeNodeId, attributeId: AttributeIds.IsAbstract })).value.value
343
346
  };
344
347
 
345
- const [isAbstract, fieldTypeName] = await Promise.all([
348
+ const [isAbstract, fieldTypeName, _unusedSuperType] = await Promise.all([
346
349
  readIsAbstract(dataTypeNodeId),
347
- readBrowseNameWithCache(session, dataTypeNodeId, cache)
350
+ readBrowseNameWithCache(session, dataTypeNodeId, cache),
351
+ findSuperType(session, dataTypeNodeId, cache)
348
352
  ]);
349
353
 
350
354
  if (isAbstract) {
@@ -358,7 +362,7 @@ async function resolveFieldType(
358
362
  category: FieldCategory.complex,
359
363
  fieldTypeName: fieldTypeName,
360
364
  schema: ExtensionObject.schema,
361
- allowSubType: true,
365
+ allowSubTypes: true,
362
366
  dataType: dataTypeNodeId
363
367
  };
364
368
  return v3;
@@ -367,8 +371,8 @@ async function resolveFieldType(
367
371
  const v3: CacheForFieldResolution = {
368
372
  category: FieldCategory.basic,
369
373
  fieldTypeName: fieldTypeName,
370
- schema: dataTypeFactory.getBuiltInType("Variant"),
371
- allowSubType: true,
374
+ schema: dataTypeManager.getBuiltInType("Variant"),
375
+ allowSubTypes: true,
372
376
  dataType: dataTypeNodeId
373
377
  };
374
378
  return v3;
@@ -389,7 +393,7 @@ async function resolveFieldType(
389
393
  schema = dataTypeFactory.getEnumeration(fieldTypeName!)!;
390
394
  } else {
391
395
  doDebug && debugLog(" type " + fieldTypeName + " has not been seen yet, let resolve it");
392
- const res = await resolve2(session, dataTypeNodeId, dataTypeFactory, fieldTypeName, cache);
396
+ const res = await resolve2(session, dataTypeNodeId, dataTypeManager, fieldTypeName, cache);
393
397
  schema = res.schema;
394
398
  category = res.category;
395
399
  }
@@ -414,13 +418,12 @@ async function _setupEncodings(
414
418
  session: IBasicSessionAsync & IBasicSessionBrowseNextAsync,
415
419
  dataTypeNodeId: NodeId,
416
420
  dataTypeDescription: IDataTypeDescriptionMini | null,
417
- schema: IStructuredTypeSchema
421
+ schema: IStructuredTypeSchema,
422
+ isAbstract: boolean
418
423
  ): Promise<IStructuredTypeSchema> {
419
- // read abstract flag
420
- const isAbstractDV = await session.read({ nodeId: dataTypeNodeId, attributeId: AttributeIds.IsAbstract });
421
424
  schema.dataTypeNodeId = dataTypeNodeId;
422
425
 
423
- if (isAbstractDV.statusCode.isGood() && isAbstractDV.value.value === false) {
426
+ if (!isAbstract) {
424
427
  const encodings = (dataTypeDescription && dataTypeDescription.encodings) || (await _findEncodings(session, dataTypeNodeId));
425
428
  schema.encodingDefaultBinary = makeExpandedNodeId(encodings.binaryEncodingNodeId);
426
429
  schema.encodingDefaultXml = makeExpandedNodeId(encodings.xmlEncodingNodeId);
@@ -501,27 +504,27 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
501
504
  name: string,
502
505
  definition: DataTypeDefinition,
503
506
  dataTypeDescription: IDataTypeDescriptionMini | null,
504
- dataTypeFactory: DataTypeFactory,
507
+ dataTypeManager: ExtraDataTypeManager,
505
508
  isAbstract: boolean,
506
509
  cache: ICache
507
510
  ): Promise<IStructuredTypeSchema> {
508
-
511
+
509
512
  return await nonReentrant(cache, "convertDataTypeDefinitionToStructureTypeSchema", dataTypeNodeId, async () => {
510
513
 
511
- // warningLog(">> convertDataTypeDefinitionToStructureTypeSchema = ", dataTypeNodeId.toString());
514
+
512
515
  if (definition instanceof StructureDefinition) {
516
+
517
+ const dataTypeFactory = dataTypeManager.getDataTypeFactoryForNamespace(dataTypeNodeId.namespace);
518
+
513
519
  let fieldCountToIgnore = 0;
514
- const structureInfo = dataTypeFactory.getStructureInfoForDataType(definition.baseDataType);
520
+
521
+ const structureInfo = dataTypeManager.getStructureInfoForDataType(definition.baseDataType);
515
522
  const baseSchema: IStructuredTypeSchema | undefined | null = structureInfo?.schema;
516
523
 
517
524
  if (baseSchema) {
518
525
  const possibleFields = extractAllPossibleFields(baseSchema);
519
526
  fieldCountToIgnore += possibleFields.length;
520
527
  }
521
- // while (base && !(base.dataTypeNodeId.value === DataType.ExtensionObject && base.dataTypeNodeId.namespace === 0)) {
522
- // fieldCountToIgnore += base..length;
523
- // base = base.getBaseSchema();
524
- // }
525
528
 
526
529
  const fields: FieldInterfaceOptions[] = [];
527
530
 
@@ -536,6 +539,9 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
536
539
  break;
537
540
  case StructureType.Structure:
538
541
  case StructureType.StructureWithOptionalFields:
542
+ // new in 1.05
543
+ case StructureType.StructureWithSubtypedValues:
544
+ case StructureType.UnionWithSubtypedValues:
539
545
  break;
540
546
  }
541
547
 
@@ -548,51 +554,67 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
548
554
 
549
555
  if (definition.fields) {
550
556
 
551
-
557
+ // pre-fetch all dataTypes in parallel
558
+ const dataTypesToResolve: NodeId[] = [];
559
+ const seen = new Set<string>();
560
+ if (!sameNodeId(definition.baseDataType, dataTypeNodeId)) {
561
+ dataTypesToResolve.push(definition.baseDataType);
562
+ seen.add(definition.baseDataType.toString());
563
+ }
552
564
  for (let i = fieldCountToIgnore; i < definition.fields.length; i++) {
553
565
  const fieldD = definition.fields[i];
554
- // we need to skip fields that have already been handled in base class
555
- // promises.push((
556
- await (async () => {
557
-
558
- let field: FieldInterfaceOptions | undefined;
559
- ({ field, switchBit, switchValue } = createField(fieldD, switchBit, bitFields, isUnion, switchValue));
560
-
561
- if (fieldD.dataType.value === dataTypeNodeId.value && fieldD.dataType.namespace === dataTypeNodeId.namespace) {
562
- // this is a structure with a field of the same type
563
- // push an empty placeholder that we will fill later
564
- const fieldTypeName = await readBrowseNameWithCache(session, dataTypeNodeId, cache);
565
- (field.fieldType = fieldTypeName!), (field.category = FieldCategory.complex);
566
- fields.push(field);
567
- const capturedField = field;
568
- postActions.push((schema: IStructuredTypeSchema) => {
569
- capturedField.schema = schema;
570
- });
571
- return;;
572
- }
573
- const rt = (await resolveFieldType(session, fieldD.dataType, dataTypeFactory, cache))!;
574
- if (!rt) {
575
- errorLog(
576
- "convertDataTypeDefinitionToStructureTypeSchema cannot handle field",
577
- fieldD.name,
578
- "in",
579
- name,
580
- "because " + fieldD.dataType.toString() + " cannot be resolved"
581
- );
582
- return;
583
- }
584
- const { schema, category, fieldTypeName, dataType, allowSubType } = rt;
585
-
586
- field.fieldType = fieldTypeName!;
587
- field.category = category;
588
- field.schema = schema;
589
- field.dataType = dataType || fieldD.dataType;
590
- field.allowSubType = allowSubType || false;
591
- field.basicDataType = await findBasicDataTypeEx(session, field.dataType, cache);
566
+ const key = fieldD.dataType.toString();
567
+ if (seen.has(key)) continue;
568
+ seen.add(key);
569
+ if (sameNodeId(fieldD.dataType, dataTypeNodeId)) continue;
570
+ dataTypesToResolve.push(fieldD.dataType);
571
+ }
572
+ await Promise.all([
573
+ ...dataTypesToResolve.map((dataType) => resolveFieldType(session, dataType, dataTypeManager, cache)),
574
+ ...dataTypesToResolve.map((dataType) => findBasicDataTypeEx(session, dataType, cache)),
575
+ !isAbstract ? _findEncodings(session, dataTypeNodeId) : Promise.resolve(null)
576
+ ]);
577
+
578
+ const results = await Promise.all(definition.fields.slice(fieldCountToIgnore).map(async (fieldD) => {
579
+ const isSelf = sameNodeId(fieldD.dataType, dataTypeNodeId);
580
+ // Pre-fetch async data for each field in parallel
581
+ const rt = isSelf ? null : await resolveFieldType(session, fieldD.dataType, dataTypeManager, cache);
582
+ const basicDataType = rt ? await findBasicDataTypeEx(session, rt.dataType || fieldD.dataType, cache) : (isSelf ? DataType.ExtensionObject : undefined);
583
+ const fieldTypeNameForSelfRef = (isSelf)
584
+ ? await readBrowseNameWithCache(session, dataTypeNodeId, cache)
585
+ : undefined;
586
+ return { fieldD, rt, basicDataType, fieldTypeNameForSelfRef };
587
+ }));
588
+
589
+ for (const { fieldD, rt, basicDataType, fieldTypeNameForSelfRef } of results) {
590
+
591
+ let field: FieldInterfaceOptions | undefined;
592
+ let currentAllowSubTypes: boolean | undefined;
593
+
594
+ ({ field, switchBit, switchValue, allowSubTypes: currentAllowSubTypes } = createField(definition, fieldD, switchBit, bitFields, isUnion, switchValue));
595
+
596
+ if (fieldD.dataType.value === dataTypeNodeId.value && fieldD.dataType.namespace === dataTypeNodeId.namespace) {
597
+ // this is a structure with a field of the same type
598
+ // push an empty placeholder that we will fill later
599
+ (field.fieldType = fieldTypeNameForSelfRef!), (field.category = FieldCategory.complex);
592
600
  fields.push(field);
593
- })();
594
- // ));
601
+ const capturedField = field;
602
+ postActions.push((schema: IStructuredTypeSchema) => {
603
+ capturedField.schema = schema;
604
+ });
605
+ continue;
606
+ }
607
+ const resolvedField = rt!;
595
608
 
609
+ const { schema, category, fieldTypeName, dataType } = resolvedField;
610
+
611
+ field.fieldType = fieldTypeName!;
612
+ field.category = category;
613
+ field.schema = schema;
614
+ field.dataType = dataType || fieldD.dataType;
615
+ field.allowSubTypes = currentAllowSubTypes;
616
+ field.basicDataType = basicDataType;
617
+ fields.push(field);
596
618
  }
597
619
 
598
620
  }
@@ -602,7 +624,8 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
602
624
  definition.baseDataType = resolveNodeId(DataTypeIds.Union); // aka DataTypeIds.Union
603
625
  }
604
626
 
605
- const a = await resolveFieldType(session, definition.baseDataType, dataTypeFactory, cache);
627
+ const a = await resolveFieldType(session, definition.baseDataType, dataTypeManager, cache);
628
+
606
629
  const baseType = a ? a.fieldTypeName : isUnion ? "Union" : "ExtensionObject";
607
630
 
608
631
  const os = new StructuredTypeSchema({
@@ -612,7 +635,7 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
612
635
  name,
613
636
  dataTypeFactory
614
637
  });
615
- const structuredTypeSchema = await _setupEncodings(session, dataTypeNodeId, dataTypeDescription, os);
638
+ const structuredTypeSchema = await _setupEncodings(session, dataTypeNodeId, dataTypeDescription, os, isAbstract);
616
639
 
617
640
  postActions.forEach((action) => action(structuredTypeSchema));
618
641
 
@@ -621,28 +644,42 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
621
644
  }
622
645
  throw new Error("Not Implemented");
623
646
  });
647
+
624
648
  function createField(
649
+ definition: StructureDefinition,
625
650
  fieldD: StructureField,
651
+
626
652
  switchBit: number,
627
653
  bitFields: BitField[] | undefined,
628
654
  isUnion: boolean,
629
655
  switchValue: number
630
- ): { field: FieldInterfaceOptions; switchBit: number; switchValue: number } {
656
+ ): { field: FieldInterfaceOptions; switchBit: number; switchValue: number, allowSubTypes?: boolean } {
657
+
631
658
  const field: FieldInterfaceOptions = {
632
659
  fieldType: "",
633
660
  name: fieldD.name!,
634
661
  schema: undefined
635
662
  };
636
663
 
664
+ const definitionAllowSubTypes =
665
+ definition.structureType === StructureType.StructureWithSubtypedValues
666
+ || definition.structureType === StructureType.UnionWithSubtypedValues;
667
+
637
668
  if (fieldD.isOptional) {
638
- field.switchBit = switchBit++;
639
- bitFields?.push({ name: fieldD.name! + "Specified", length: 1 });
669
+ // Optional has a special handling
670
+ if (!definitionAllowSubTypes) {
671
+ // we are in a true optional field structure
672
+ field.switchBit = switchBit++;
673
+ bitFields?.push({ name: fieldD.name! + "Specified", length: 1 });
674
+ }
640
675
  }
641
676
  if (isUnion) {
642
677
  field.switchValue = switchValue;
643
678
  switchValue += 1;
644
679
  }
645
680
 
681
+ const allowSubTypes = definitionAllowSubTypes && fieldD.isOptional;
682
+
646
683
  // (fieldD.valueRank === -1 || fieldD.valueRank === 1 || fieldD.valueRank === 0);
647
684
 
648
685
  if (fieldD.valueRank >= 1) {
@@ -651,6 +688,6 @@ export async function convertDataTypeDefinitionToStructureTypeSchema(
651
688
  } else {
652
689
  field.isArray = false;
653
690
  }
654
- return { field, switchBit, switchValue };
691
+ return { field, switchBit, switchValue, allowSubTypes };
655
692
  }
656
693
  }
@@ -4,18 +4,26 @@
4
4
  import { format } from "util";
5
5
 
6
6
  import { assert } from "node-opcua-assert";
7
- import { ConstructorFunc, DataTypeFactory, getStandardDataTypeFactory } from "node-opcua-factory";
7
+ import { BrowseDirection, NodeClassMask, ResultMask } from "node-opcua-data-model";
8
+ import { ConstructorFunc, DataTypeFactory, getStandardDataTypeFactory, StructureInfo } from "node-opcua-factory";
8
9
  import { NodeId } from "node-opcua-nodeid";
10
+ import { IBasicSessionAsync2 } from "node-opcua-pseudo-session";
9
11
  import { AnyConstructorFunc } from "node-opcua-schemas";
10
12
 
11
13
  export class ExtraDataTypeManager {
12
14
  public namespaceArray: string[] = [];
13
15
  private dataTypeFactoryMapByNamespace: { [key: number]: DataTypeFactory } = {};
16
+ private _session: IBasicSessionAsync2 | null = null;
17
+ private _pendingExtractions: Map<string, Promise<StructureInfo>> = new Map();
14
18
 
15
19
  constructor() {
16
20
  /* */
17
21
  }
18
22
 
23
+ public setSession(session: IBasicSessionAsync2): void {
24
+ this._session = session;
25
+ }
26
+
19
27
  public setNamespaceArray(namespaceArray: string[]): void {
20
28
  this.namespaceArray = namespaceArray;
21
29
  }
@@ -34,7 +42,9 @@ export class ExtraDataTypeManager {
34
42
  }
35
43
 
36
44
  public getDataTypeFactoryForNamespace(namespaceIndex: number): DataTypeFactory {
37
- assert(namespaceIndex !== 0, "getTypeDictionaryForNamespace cannot be used for namespace 0");
45
+ if (namespaceIndex === 0) {
46
+ return getStandardDataTypeFactory();
47
+ }
38
48
  return this.dataTypeFactoryMapByNamespace[namespaceIndex];
39
49
  }
40
50
 
@@ -45,31 +55,130 @@ export class ExtraDataTypeManager {
45
55
  return this.dataTypeFactoryMapByNamespace[namespaceIndex];
46
56
  }
47
57
 
58
+ public getBuiltInType(fieldTypeName: string) {
59
+ // fallback to standard factory
60
+ const standardDataTypeFactory = getStandardDataTypeFactory();
61
+ if (standardDataTypeFactory.hasBuiltInType(fieldTypeName)) {
62
+ return standardDataTypeFactory.getBuiltInType(fieldTypeName);
63
+ }
64
+ throw new Error("Cannot find built-in type " + fieldTypeName);
65
+ }
66
+
67
+ public getStructureInfoForDataType(dataTypeNodeId: NodeId): StructureInfo | null {
68
+ const dataTypeFactory = this.getDataTypeFactory(dataTypeNodeId.namespace);
69
+ if (!dataTypeFactory) {
70
+ throw new Error("cannot find dataFactory for namespace=" + dataTypeNodeId.namespace + " when requested for " + dataTypeNodeId.toString());
71
+ }
72
+ return dataTypeFactory.getStructureInfoForDataType(dataTypeNodeId);
73
+ }
74
+
75
+ public async getStructureInfoForDataTypeAsync(dataTypeNodeId: NodeId): Promise<StructureInfo> {
76
+ const structureInfo = this.getStructureInfoForDataType(dataTypeNodeId);
77
+ if (structureInfo) {
78
+ return structureInfo;
79
+ }
80
+ if (!this._session) {
81
+ throw new Error("Session is required for lazy loading. Call setSession first.");
82
+ }
83
+
84
+ const key = dataTypeNodeId.toString();
85
+ if (this._pendingExtractions.has(key)) {
86
+ return await this._pendingExtractions.get(key)!;
87
+ }
88
+
89
+ const promise = (async () => {
90
+ // We'll need to make sure it's accessible and correctly used.
91
+ // For now, let's assume we can import it or move it.
92
+ // Actually, populate_data_type_manager_104.ts exports readDataTypeDefinitionAndBuildType
93
+ const { readDataTypeDefinitionAndBuildType } = require("./private/populate_data_type_manager_104");
94
+ const cache = {}; // local cache for this extraction
95
+ await readDataTypeDefinitionAndBuildType(this._session!, dataTypeNodeId, undefined, this, cache);
96
+
97
+ const info = this.getStructureInfoForDataType(dataTypeNodeId);
98
+ if (!info) {
99
+ throw new Error("Failed to extract data type structure for " + dataTypeNodeId.toString());
100
+ }
101
+ return info;
102
+ })();
103
+
104
+ this._pendingExtractions.set(key, promise);
105
+ try {
106
+ return await promise;
107
+ } finally {
108
+ this._pendingExtractions.delete(key);
109
+ }
110
+ }
111
+
48
112
  public getExtensionObjectConstructorFromDataType(dataTypeNodeId: NodeId): AnyConstructorFunc {
49
113
  const dataTypeFactory = this.getDataTypeFactory(dataTypeNodeId.namespace);
50
114
  if (!dataTypeFactory) {
51
115
  throw new Error("cannot find dataFactory for namespace=" + dataTypeNodeId.namespace + " when requested for " + dataTypeNodeId.toString());
52
116
  }
53
117
  // find schema corresponding to dataTypeNodeId in typeDictionary
54
- const Constructor = dataTypeFactory.findStructureInfoForDataType(dataTypeNodeId).constructor;
118
+ const structureInfo = dataTypeFactory.findStructureInfoForDataType(dataTypeNodeId);
119
+ const Constructor = structureInfo.constructor;
55
120
  if (!Constructor) {
56
121
  throw new Error("Cannot find Extension Object Constructor for Abstract dataType");
57
122
  }
58
123
  return Constructor;
59
124
  }
60
125
 
126
+ public async getExtensionObjectConstructorFromDataTypeAsync(dataTypeNodeId: NodeId): Promise<AnyConstructorFunc> {
127
+ const structureInfo = await this.getStructureInfoForDataTypeAsync(dataTypeNodeId);
128
+ const Constructor = structureInfo.constructor;
129
+ if (!Constructor) {
130
+ throw new Error("Cannot find Extension Object Constructor for Abstract dataType " + dataTypeNodeId.toString());
131
+ }
132
+ return Constructor;
133
+ }
134
+
61
135
  public getExtensionObjectConstructorFromBinaryEncoding(binaryEncodingNodeId: NodeId): ConstructorFunc {
62
136
  const dataTypeFactory = this.getDataTypeFactoryForNamespace(binaryEncodingNodeId.namespace);
63
137
  const Constructor = dataTypeFactory.getConstructor(binaryEncodingNodeId);
64
138
  if (!Constructor) {
65
139
  throw new Error(
66
140
  "getExtensionObjectConstructorFromBinaryEncoding cannot find constructor for binaryEncoding " +
67
- binaryEncodingNodeId.toString()
141
+ binaryEncodingNodeId.toString()
68
142
  );
69
143
  }
70
144
  return Constructor;
71
145
  }
72
146
 
147
+ public async getExtensionObjectConstructorFromBinaryEncodingAsync(binaryEncodingNodeId: NodeId): Promise<ConstructorFunc> {
148
+ const dataTypeFactory = this.getDataTypeFactoryForNamespace(binaryEncodingNodeId.namespace);
149
+ let Constructor = dataTypeFactory.getConstructor(binaryEncodingNodeId);
150
+ if (Constructor) {
151
+ return Constructor;
152
+ }
153
+
154
+ if (!this._session) {
155
+ throw new Error("Session is required for lazy loading. Call setSession first.");
156
+ }
157
+
158
+ // Need to find the DataType for this binary encoding
159
+ const browseResult = await this._session.browse({
160
+ nodeId: binaryEncodingNodeId,
161
+ referenceTypeId: "HasEncoding",
162
+ browseDirection: BrowseDirection.Inverse,
163
+ includeSubtypes: false,
164
+ nodeClassMask: NodeClassMask.DataType,
165
+ resultMask: ResultMask.BrowseName
166
+ });
167
+
168
+ if (browseResult.statusCode.isNotGood() || !browseResult.references || browseResult.references.length !== 1) {
169
+ throw new Error("Cannot find DataType for binary encoding " + binaryEncodingNodeId.toString());
170
+ }
171
+
172
+ const dataTypeNodeId = browseResult.references[0].nodeId;
173
+ await this.getStructureInfoForDataTypeAsync(dataTypeNodeId);
174
+
175
+ Constructor = dataTypeFactory.getConstructor(binaryEncodingNodeId);
176
+ if (!Constructor) {
177
+ throw new Error("Cannot find constructor for binary encoding " + binaryEncodingNodeId.toString() + " after extraction");
178
+ }
179
+ return Constructor;
180
+ }
181
+
73
182
  public toString(): string {
74
183
  const l: string[] = [];
75
184
  function write(...args: [any, ...any[]]) {
@@ -10,9 +10,10 @@ import { readDataTypeDefinitionAndBuildType } from "./private/populate_data_type
10
10
  *
11
11
  */
12
12
  export async function getExtensionObjectConstructor(session: IBasicSessionAsync2, dataTypeNodeId: NodeId): Promise<AnyConstructorFunc> {
13
- const extraDataTypeManager = await getExtraDataTypeManager(session);
13
+
14
+ const dataTypeManager = await getExtraDataTypeManager(session);
14
15
 
15
- const dataTypeFactory = extraDataTypeManager.getDataTypeFactory(dataTypeNodeId.namespace);
16
+ const dataTypeFactory = dataTypeManager.getDataTypeFactory(dataTypeNodeId.namespace);
16
17
  const structureInfo = dataTypeFactory.getStructureInfoForDataType(dataTypeNodeId);
17
18
  if (structureInfo) {
18
19
  return structureInfo.constructor as unknown as AnyConstructorFunc;
@@ -22,7 +23,7 @@ export async function getExtensionObjectConstructor(session: IBasicSessionAsync2
22
23
  attributeId: AttributeIds.BrowseName
23
24
  });
24
25
  const browseName = dataValue.value.value as QualifiedName;
25
- await readDataTypeDefinitionAndBuildType(session, dataTypeNodeId, browseName.name!, dataTypeFactory, {});
26
+ await readDataTypeDefinitionAndBuildType(session, dataTypeNodeId, browseName.name!, dataTypeManager, {});
26
27
 
27
- return await extraDataTypeManager.getExtensionObjectConstructorFromDataType(dataTypeNodeId);
28
+ return await dataTypeManager.getExtensionObjectConstructorFromDataType(dataTypeNodeId);
28
29
  }