node-opcua-address-space 2.187.8 → 2.187.11

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.
@@ -811,7 +811,7 @@ function _clone_collection_new(
811
811
 
812
812
  // also clone or instantiate interface members that may be required in the optionals
813
813
  extraInfo.level++;
814
- _cloneInterface(nodeToClone, newParent, node, optionalFilter, extraInfo, browseNameMap);
814
+ _cloneInterface(nodeToClone, clonedNode, node, optionalFilter, extraInfo, browseNameMap);
815
815
  extraInfo.level--;
816
816
  // === clone);
817
817
  // extraInfo.registerClonedObject(clone, node);
@@ -898,7 +898,7 @@ function _extractInterfaces2(typeDefinitionNode: UAObjectType | UAVariableType,
898
898
 
899
899
  function _cloneInterface(
900
900
  nodeToClone: UAObject | UAVariable | UAMethod | UAObjectType | UAVariableType,
901
- _newParent: BaseNode,
901
+ clonedNode: BaseNode,
902
902
  node: UAObject | UAVariable | UAMethod,
903
903
  optionalFilter: CloneFilter,
904
904
  extraInfo: CloneExtraInfo,
@@ -936,7 +936,10 @@ function _cloneInterface(
936
936
  const localFilter = optionalFilter.filterFor(node);
937
937
 
938
938
  for (const iface of interfaces) {
939
- const aggregates = iface.findReferencesEx("Aggregates", BrowseDirection.Forward);
939
+ // a member the clone already has (the type declares it itself) is not cloned a second time
940
+ const aggregates = iface
941
+ .findReferencesEx("Aggregates", BrowseDirection.Forward)
942
+ .filter((r) => !r.node || !clonedNode.getChildByName(r.node.browseName));
940
943
  if (aggregates.length === 0) continue;
941
944
  // c8 ignore next
942
945
  doTrace &&
@@ -946,7 +949,7 @@ function _cloneInterface(
946
949
  `\n${extraInfo?.pad()}`,
947
950
  aggregates.map((r) => r.toString({ addressSpace })).join(`\n${extraInfo?.pad()}`)
948
951
  );
949
- _clone_collection_new(nodeToClone, node, aggregates, false, localFilter, extraInfo, browseNameMap);
952
+ _clone_collection_new(nodeToClone, clonedNode, aggregates, false, localFilter, extraInfo, browseNameMap);
950
953
  }
951
954
  }
952
955
 
@@ -255,7 +255,12 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
255
255
  * it, as the Read service does for the others, evaluates the getter twice for nothing.
256
256
  */
257
257
  declare public _refreshFuncWrapsGetter?: boolean;
258
- declare public __waiting_callbacks?: CallbackT<DataValue>[];
258
+ // the read that started the refresh in progress, and the reads that came while it was (an array only then):
259
+ // a sampled variable is read once per sample, and one at a time
260
+ declare private __waiting_callback?: CallbackT<DataValue>;
261
+ declare private __more_waiting_callbacks?: CallbackT<DataValue>[];
262
+ // set by _readValueAsync for the next asyncRefresh when its clock is shared by a sampler (see asyncRefresh)
263
+ declare private __sharedReadClock?: boolean;
259
264
  // the variant types already found to fit the DataType below (bit k: DataType k), set on the first write
260
265
  declare private _validatedForDataType?: NodeId;
261
266
  declare private _validatedVariantTypes?: number;
@@ -553,40 +558,35 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
553
558
  this.verifyVariantCompatibility(this.$dataValue.value);
554
559
  }
555
560
 
556
- const lessOrEqual = (a: PreciseClock, b: PreciseClock) => {
557
- return (
558
- a.timestamp.getTime() < b.timestamp.getTime() ||
559
- (a.timestamp.getTime() === b.timestamp.getTime() && a.picoseconds <= b.picoseconds)
560
- );
561
- };
562
-
563
561
  if (!this.refreshFunc) {
564
562
  // no refresh func
565
563
  const dataValue = this.readValue();
566
564
  dataValue.serverTimestamp = oldestDate.timestamp;
567
565
  dataValue.serverPicoseconds = oldestDate.picoseconds;
568
- const serverClock = coerceClock(dataValue.serverTimestamp, dataValue.serverPicoseconds);
569
- if (lessOrEqual(oldestDate, serverClock)) {
570
- cb(null, dataValue);
571
- return;
572
- } else {
573
- // fake
574
- cb(null, dataValue);
575
- return;
576
- }
566
+ cb(null, dataValue);
567
+ return;
577
568
  }
578
569
 
579
- const c1 = this.$dataValue.serverTimestamp
580
- ? coerceClock(this.$dataValue.serverTimestamp, this.$dataValue.serverPicoseconds)
581
- : null;
582
-
583
- if (c1 && lessOrEqual(oldestDate, c1)) {
570
+ // the value held is recent enough for this read: it is answered without a refresh
571
+ const held = this.$dataValue;
572
+ if (held.serverTimestamp && clockNotAfter(oldestDate, held.serverTimestamp, held.serverPicoseconds)) {
584
573
  const dataValue = this.readValue().clone();
585
574
  dataValue.serverTimestamp = oldestDate.timestamp;
586
575
  dataValue.serverPicoseconds = oldestDate.picoseconds;
587
576
  cb(null, dataValue);
588
577
  return;
589
578
  }
579
+ // a refreshed value without a server timestamp is stamped with the time the server obtained it
580
+ // (OPC 10000-4 7.11), read from the clock once the refreshFunc has answered, so it is never earlier
581
+ // than a source timestamp the refreshFunc took. Left unset, it inherited the previous value's server
582
+ // timestamp, which never moved on. A sampler that reads many variables in one go shares one clock
583
+ // read between them: their values get that clock's Date (the sampler recognises it by identity and
584
+ // stamps them all with one clock read made once they have all answered).
585
+ const sharedClock = this.__sharedReadClock === true;
586
+ this.__sharedReadClock = false;
587
+ const requestedAt = oldestDate.timestamp;
588
+ const requestedAtPicoseconds = oldestDate.picoseconds;
589
+ let answeredAtOnce = true;
590
590
  try {
591
591
  this.refreshFunc.call(this, (err: Error | null, dataValue?: DataValueLike) => {
592
592
  // c8 ignore next
@@ -600,10 +600,22 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
600
600
  dataValue = { statusCode: StatusCodes.BadNoDataAvailable };
601
601
  }
602
602
  if (dataValue && dataValue !== this.$dataValue) {
603
- this._internal_set_dataValue(coerceDataValue(dataValue), null);
603
+ const refreshed = coerceDataValue(dataValue);
604
+ if (!refreshed.serverTimestamp) {
605
+ if (answeredAtOnce && sharedClock) {
606
+ refreshed.serverTimestamp = requestedAt;
607
+ refreshed.serverPicoseconds = requestedAtPicoseconds;
608
+ } else {
609
+ const now = getCurrentClock();
610
+ refreshed.serverTimestamp = now.timestamp;
611
+ refreshed.serverPicoseconds = now.picoseconds;
612
+ }
613
+ }
614
+ this._internal_set_dataValue(refreshed, null);
604
615
  }
605
616
  cb(err, this.$dataValue);
606
617
  });
618
+ answeredAtOnce = false;
607
619
  } catch (err) {
608
620
  errorLog("-------------- refresh call failed 2", this.browseName.toString(), this.nodeId.toString());
609
621
  errorLog(err);
@@ -1314,13 +1326,17 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1314
1326
  /**
1315
1327
 
1316
1328
  */
1317
- public readValueAsync(context: ISessionContext | null, callback: CallbackT<DataValue>): void;
1329
+ public readValueAsync(context: ISessionContext | null, callback: CallbackT<DataValue>, clock?: PreciseClock): void;
1318
1330
  public readValueAsync(context: ISessionContext | null): Promise<DataValue>;
1319
- public readValueAsync(context: ISessionContext | null, callback?: CallbackT<DataValue>): Promise<DataValue> | undefined {
1331
+ public readValueAsync(
1332
+ context: ISessionContext | null,
1333
+ callback?: CallbackT<DataValue>,
1334
+ clock?: PreciseClock
1335
+ ): Promise<DataValue> | undefined {
1320
1336
  // written out rather than wrapped with thenify-ex: every sample of a monitored item comes
1321
1337
  // through here with a callback, and the generic wrapper forwarded it with apply(arguments)
1322
1338
  if (callback) {
1323
- this._readValueAsync(context, callback);
1339
+ this._readValueAsync(context, callback, clock);
1324
1340
  return undefined;
1325
1341
  }
1326
1342
  return new Promise<DataValue>((resolve, reject) => {
@@ -1328,14 +1344,13 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1328
1344
  });
1329
1345
  }
1330
1346
 
1331
- private _readValueAsync(context: ISessionContext | null, callback: CallbackT<DataValue>): void {
1347
+ private _readValueAsync(context: ISessionContext | null, callback: CallbackT<DataValue>, readAt?: PreciseClock): void {
1332
1348
  context = context || SessionContext.defaultContext;
1333
1349
 
1334
- this.__waiting_callbacks = this.__waiting_callbacks || [];
1335
- this.__waiting_callbacks.push(callback);
1336
-
1337
- const _readValueAsync_in_progress = this.__waiting_callbacks.length >= 2;
1338
- if (_readValueAsync_in_progress) {
1350
+ if (this.__waiting_callback) {
1351
+ // a refresh is in progress: its value answers this read too
1352
+ this.__more_waiting_callbacks ??= [];
1353
+ this.__more_waiting_callbacks.push(callback);
1339
1354
  return;
1340
1355
  }
1341
1356
 
@@ -1343,6 +1358,7 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1343
1358
  callback?.(null, new DataValue({ statusCode: StatusCodes.BadNodeIdUnknown }));
1344
1359
  return;
1345
1360
  }
1361
+ this.__waiting_callback = callback;
1346
1362
 
1347
1363
  // runs once per sample of every monitored item, so nothing here allocates a closure or
1348
1364
  // binds a function: the denial (if any) is a StatusCode, and the callback that releases
@@ -1371,11 +1387,16 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1371
1387
  }
1372
1388
 
1373
1389
  this.__satisfy_waiting_callbacks ??= (err: Error | null, dataValue?: DataValue) => {
1374
- // now call all pending callbacks
1375
- const callbacks = this.__waiting_callbacks || [];
1376
- this.__waiting_callbacks = [];
1377
- for (const callback1 of callbacks) {
1378
- callback1.call(this, err, dataValue);
1390
+ // now call all pending callbacks, in the order of the reads
1391
+ const first = this.__waiting_callback;
1392
+ const more = this.__more_waiting_callbacks;
1393
+ this.__waiting_callback = undefined;
1394
+ this.__more_waiting_callbacks = undefined;
1395
+ first?.call(this, err, dataValue);
1396
+ if (more) {
1397
+ for (const callback1 of more) {
1398
+ callback1.call(this, err, dataValue);
1399
+ }
1379
1400
  }
1380
1401
  };
1381
1402
  const satisfy_callbacks = this.__satisfy_waiting_callbacks;
@@ -1386,13 +1407,16 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1386
1407
  // on a Bad result too (FEAT-34)
1387
1408
  satisfy_callbacks(null, makeNowDataValue({ statusCode: denied }));
1388
1409
  } else if (typeof this.refreshFunc === "function") {
1389
- const clock = getCurrentClock();
1410
+ const clock = readAt ?? getCurrentClock();
1411
+ this.__sharedReadClock = readAt !== undefined;
1390
1412
  if (this.asyncRefresh === wrappedAsyncRefresh) {
1391
1413
  // the thenified wrapper adds nothing when a callback is given
1392
1414
  asyncRefreshImpl.call(this, clock, satisfy_callbacks);
1393
1415
  } else {
1394
1416
  this.asyncRefresh(clock, satisfy_callbacks);
1395
1417
  }
1418
+ // consumed by asyncRefresh on entry; cleared here for an overridden one that never reads it
1419
+ this.__sharedReadClock = false;
1396
1420
  } else {
1397
1421
  assert(this.$dataValue instanceof DataValue);
1398
1422
  satisfy_callbacks(null, this.readValue(context));
@@ -2327,6 +2351,13 @@ function coerceDataValue(dataValue: DataValue | DataValueLike): DataValue {
2327
2351
  return new DataValue(dataValue);
2328
2352
  }
2329
2353
 
2354
+ /** `clock` is not later than the time given as a Date and picoseconds (compared as they are, with no clock object made) */
2355
+ function clockNotAfter(clock: PreciseClock, timestamp: Date, picoseconds: number): boolean {
2356
+ const a = clock.timestamp.getTime();
2357
+ const b = timestamp.getTime();
2358
+ return a < b || (a === b && clock.picoseconds <= (picoseconds || 0));
2359
+ }
2360
+
2330
2361
  // variation #3 :
2331
2362
  function _Variable_bind_with_async_refresh(
2332
2363
  this: UAVariableImpl,
@@ -2611,10 +2642,11 @@ export class UAVariableImplT<T, DT extends DataType> extends UAVariableImpl impl
2611
2642
  }
2612
2643
 
2613
2644
  public readValueAsync(context: ISessionContext | null): Promise<DataValueT<T, DT>>;
2614
- public readValueAsync(context: ISessionContext | null, callback: CallbackT<DataValueT<T, DT>>): void;
2645
+ public readValueAsync(context: ISessionContext | null, callback: CallbackT<DataValueT<T, DT>>, clock?: PreciseClock): void;
2615
2646
  public readValueAsync(
2616
2647
  context: ISessionContext | null,
2617
- callback?: CallbackT<DataValueT<T, DT>>
2648
+ callback?: CallbackT<DataValueT<T, DT>>,
2649
+ clock?: PreciseClock
2618
2650
  ): Promise<DataValueT<T, DT>> | undefined {
2619
2651
  // forward the arguments we were actually given: the base method picks the promise or the
2620
2652
  // callback form from the callback it receives (writeValue below, still thenified, picks
@@ -2622,7 +2654,7 @@ export class UAVariableImplT<T, DT extends DataType> extends UAVariableImpl impl
2622
2654
  if (callback === undefined) {
2623
2655
  return super.readValueAsync(context) as Promise<DataValueT<T, DT>>;
2624
2656
  }
2625
- super.readValueAsync(context, callback as CallbackT<DataValue>);
2657
+ super.readValueAsync(context, callback as CallbackT<DataValue>, clock);
2626
2658
  return undefined;
2627
2659
  }
2628
2660
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-opcua-address-space",
3
3
  "type": "module",
4
- "version": "2.187.8",
4
+ "version": "2.187.11",
5
5
  "description": "pure nodejs OPCUA SDK - module address-space",
6
6
  "main": "./dist/api/index.js",
7
7
  "types": "./dist/api/index.d.ts",
@@ -23,34 +23,34 @@
23
23
  "@types/semver": "7.8.0",
24
24
  "chalk": "6.0.1",
25
25
  "dequeue": "^1.0.5",
26
- "node-opcua-address-space-base": "2.187.8",
27
- "node-opcua-address-space-store": "2.187.8",
26
+ "node-opcua-address-space-base": "2.187.9",
27
+ "node-opcua-address-space-store": "2.187.9",
28
28
  "node-opcua-assert": "2.186.7",
29
29
  "node-opcua-basic-types": "2.187.0",
30
30
  "node-opcua-binary-stream": "2.187.0",
31
- "node-opcua-client-dynamic-extension-object": "2.187.8",
31
+ "node-opcua-client-dynamic-extension-object": "2.187.9",
32
32
  "node-opcua-constants": "2.185.0",
33
33
  "node-opcua-crypto": "6.2.3",
34
- "node-opcua-data-access": "2.187.0",
34
+ "node-opcua-data-access": "2.187.9",
35
35
  "node-opcua-data-model": "2.187.0",
36
- "node-opcua-data-value": "2.187.0",
36
+ "node-opcua-data-value": "2.187.9",
37
37
  "node-opcua-date-time": "2.187.0",
38
38
  "node-opcua-debug": "2.186.7",
39
39
  "node-opcua-enum": "2.187.0",
40
40
  "node-opcua-extension-object": "2.187.0",
41
41
  "node-opcua-factory": "2.187.0",
42
42
  "node-opcua-nodeid": "2.187.0",
43
- "node-opcua-nodeset-ua": "2.187.8",
43
+ "node-opcua-nodeset-ua": "2.187.9",
44
44
  "node-opcua-numeric-range": "2.187.0",
45
45
  "node-opcua-object-registry": "2.186.7",
46
- "node-opcua-pseudo-session": "2.187.8",
47
- "node-opcua-service-browse": "2.187.0",
48
- "node-opcua-service-call": "2.187.0",
49
- "node-opcua-service-history": "2.187.0",
50
- "node-opcua-service-translate-browse-path": "2.187.0",
51
- "node-opcua-service-write": "2.187.0",
46
+ "node-opcua-pseudo-session": "2.187.9",
47
+ "node-opcua-service-browse": "2.187.9",
48
+ "node-opcua-service-call": "2.187.9",
49
+ "node-opcua-service-history": "2.187.9",
50
+ "node-opcua-service-translate-browse-path": "2.187.9",
51
+ "node-opcua-service-write": "2.187.9",
52
52
  "node-opcua-status-code": "2.187.0",
53
- "node-opcua-types": "2.187.0",
53
+ "node-opcua-types": "2.187.9",
54
54
  "node-opcua-utils": "2.187.0",
55
55
  "node-opcua-variant": "2.187.0",
56
56
  "node-opcua-xml2json": "2.187.0",
@@ -65,7 +65,7 @@
65
65
  "node-opcua-nodesets": "2.187.4",
66
66
  "node-opcua-packet-analyzer": "2.187.0",
67
67
  "node-opcua-schemas": "2.187.0",
68
- "node-opcua-service-filter": "2.187.8",
68
+ "node-opcua-service-filter": "2.187.9",
69
69
  "node-opcua-test-fixtures": "2.184.0",
70
70
  "node-opcua-test-helpers": "2.184.0"
71
71
  },
@@ -84,7 +84,7 @@
84
84
  "internet of things"
85
85
  ],
86
86
  "homepage": "http://node-opcua.github.io/",
87
- "gitHead": "88fe3c715fa67b2d077410362503df22a2414e9f",
87
+ "gitHead": "da8e4d922c74b877e62f5abbd7530354b0e92ee2",
88
88
  "files": [
89
89
  "dist",
90
90
  "distHelpers",