node-opcua-address-space 2.186.17 → 2.187.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 (84) hide show
  1. package/api/address_space_public.ts +16 -0
  2. package/api/helpers/resolve_opaque_on_address_space.ts +13 -1
  3. package/api/index.ts +14 -3
  4. package/api/loader/store_record_applier.ts +267 -0
  5. package/api/session_context.ts +116 -27
  6. package/dist/api/address_space_public.d.ts +14 -0
  7. package/dist/api/address_space_public.d.ts.map +1 -1
  8. package/dist/api/address_space_public.js +3 -0
  9. package/dist/api/address_space_public.js.map +1 -1
  10. package/dist/api/helpers/resolve_opaque_on_address_space.d.ts +6 -0
  11. package/dist/api/helpers/resolve_opaque_on_address_space.d.ts.map +1 -1
  12. package/dist/api/helpers/resolve_opaque_on_address_space.js +12 -1
  13. package/dist/api/helpers/resolve_opaque_on_address_space.js.map +1 -1
  14. package/dist/api/index.d.ts +4 -3
  15. package/dist/api/index.d.ts.map +1 -1
  16. package/dist/api/index.js +4 -3
  17. package/dist/api/index.js.map +1 -1
  18. package/dist/api/loader/store_record_applier.d.ts +49 -0
  19. package/dist/api/loader/store_record_applier.d.ts.map +1 -0
  20. package/dist/api/loader/store_record_applier.js +235 -0
  21. package/dist/api/loader/store_record_applier.js.map +1 -0
  22. package/dist/api/session_context.d.ts +10 -0
  23. package/dist/api/session_context.d.ts.map +1 -1
  24. package/dist/api/session_context.js +100 -27
  25. package/dist/api/session_context.js.map +1 -1
  26. package/dist/impl/address_space.d.ts +2 -0
  27. package/dist/impl/address_space.d.ts.map +1 -1
  28. package/dist/impl/address_space.js +12 -2
  29. package/dist/impl/address_space.js.map +1 -1
  30. package/dist/impl/base_node_impl.d.ts +12 -0
  31. package/dist/impl/base_node_impl.d.ts.map +1 -1
  32. package/dist/impl/base_node_impl.js +35 -29
  33. package/dist/impl/base_node_impl.js.map +1 -1
  34. package/dist/impl/base_node_private.d.ts +5 -5
  35. package/dist/impl/base_node_private.d.ts.map +1 -1
  36. package/dist/impl/base_node_private.js +11 -5
  37. package/dist/impl/base_node_private.js.map +1 -1
  38. package/dist/impl/extension_object_array_node.d.ts +4 -2
  39. package/dist/impl/extension_object_array_node.d.ts.map +1 -1
  40. package/dist/impl/extension_object_array_node.js +5 -2
  41. package/dist/impl/extension_object_array_node.js.map +1 -1
  42. package/dist/impl/historical_access/address_space_historical_data_node.d.ts +26 -2
  43. package/dist/impl/historical_access/address_space_historical_data_node.d.ts.map +1 -1
  44. package/dist/impl/historical_access/address_space_historical_data_node.js +39 -0
  45. package/dist/impl/historical_access/address_space_historical_data_node.js.map +1 -1
  46. package/dist/impl/namespace_impl.d.ts.map +1 -1
  47. package/dist/impl/namespace_impl.js +3 -1
  48. package/dist/impl/namespace_impl.js.map +1 -1
  49. package/dist/impl/nodeid_manager.d.ts.map +1 -1
  50. package/dist/impl/nodeid_manager.js +10 -7
  51. package/dist/impl/nodeid_manager.js.map +1 -1
  52. package/dist/impl/nodeset_tools/construct_namespace_dependency.d.ts.map +1 -1
  53. package/dist/impl/nodeset_tools/construct_namespace_dependency.js +28 -0
  54. package/dist/impl/nodeset_tools/construct_namespace_dependency.js.map +1 -1
  55. package/dist/impl/reference_impl.d.ts +2 -1
  56. package/dist/impl/reference_impl.d.ts.map +1 -1
  57. package/dist/impl/reference_impl.js +6 -5
  58. package/dist/impl/reference_impl.js.map +1 -1
  59. package/dist/impl/reference_index.d.ts +22 -0
  60. package/dist/impl/reference_index.d.ts.map +1 -0
  61. package/dist/impl/reference_index.js +104 -0
  62. package/dist/impl/reference_index.js.map +1 -0
  63. package/dist/impl/ua_variable_impl.d.ts +10 -0
  64. package/dist/impl/ua_variable_impl.d.ts.map +1 -1
  65. package/dist/impl/ua_variable_impl.js +152 -106
  66. package/dist/impl/ua_variable_impl.js.map +1 -1
  67. package/dist/tsconfig_base.tsbuildinfo +1 -1
  68. package/distNodeJS/generate_address_space.d.ts +6 -1
  69. package/distNodeJS/generate_address_space.d.ts.map +1 -1
  70. package/distNodeJS/generate_address_space.js +33 -1
  71. package/distNodeJS/generate_address_space.js.map +1 -1
  72. package/impl/address_space.ts +16 -2
  73. package/impl/base_node_impl.ts +52 -30
  74. package/impl/base_node_private.ts +16 -10
  75. package/impl/extension_object_array_node.ts +12 -3
  76. package/impl/historical_access/address_space_historical_data_node.ts +64 -1
  77. package/impl/namespace_impl.ts +3 -1
  78. package/impl/nodeid_manager.ts +10 -7
  79. package/impl/nodeset_tools/construct_namespace_dependency.ts +23 -1
  80. package/impl/reference_impl.ts +6 -5
  81. package/impl/reference_index.ts +116 -0
  82. package/impl/ua_variable_impl.ts +203 -115
  83. package/package.json +34 -33
  84. package/source_nodejs/generate_address_space.ts +46 -1
@@ -135,10 +135,6 @@ function adjust_samplingInterval(minimumSamplingInterval: number): number {
135
135
  return minimumSamplingInterval;
136
136
  }
137
137
 
138
- function is_Variant(v: unknown): boolean {
139
- return v instanceof Variant;
140
- }
141
-
142
138
  function is_StatusCode(v: unknown): boolean {
143
139
  return !!(
144
140
  v?.constructor &&
@@ -149,11 +145,8 @@ function is_StatusCode(v: unknown): boolean {
149
145
  );
150
146
  }
151
147
 
152
- function is_Variant_or_StatusCode(v: unknown): boolean {
153
- if (is_Variant(v)) {
154
- // /@@assert(v.isValid());
155
- }
156
- return is_Variant(v) || is_StatusCode(v);
148
+ function isThenable<T>(v: T | PromiseLike<T>): v is PromiseLike<T> {
149
+ return typeof (v as PromiseLike<T> | null | undefined)?.then === "function";
157
150
  }
158
151
 
159
152
  function default_func(this: UAVariable, dataValue1: DataValue, callback1: CallbackT<StatusCode>) {
@@ -213,46 +206,60 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
213
206
  implements UAVariable<T>, UAVariableImplExtArray
214
207
  {
215
208
  // -------------- UAvaraibleImplExArray
216
- $$variableType?: UAVariableType;
217
- $$dataType?: UADataType = undefined;
218
- $$getElementBrowseName?: (extObject: ExtensionObject, index: number | number[]) => QualifiedName;
219
- $$extensionObjectArray?: ExtensionObject[];
220
- $$indexPropertyName?: string;
209
+ declare $$variableType?: UAVariableType;
210
+ declare $$dataType?: UADataType;
211
+ declare $$getElementBrowseName?: (extObject: ExtensionObject, index: number | number[]) => QualifiedName;
212
+ declare $$extensionObjectArray?: ExtensionObject[];
213
+ declare $$indexPropertyName?: string;
221
214
  // --------------
222
215
 
223
216
  public readonly nodeClass = NodeClass.Variable;
224
217
 
225
218
  public dataType: NodeId;
226
- private _basicDataType?: DataType;
219
+ declare private _basicDataType?: DataType;
227
220
 
228
221
  // narrowing this to ExtensionObject breaks declaration-merging in ua_two_state_variable.ts and elsewhere
229
222
  // (concrete *T variants bind $extensionObject to a different, more specific shape) — see the matching
230
223
  // comment on UAVariable.$extensionObject in node-opcua-address-space-base for the documented precedent.
231
224
  // biome-ignore lint/suspicious/noExplicitAny: see comment above
232
- public $extensionObject?: any;
233
- public $set_ExtensionObject?: (newValue: ExtensionObject, sourceTimestamp: PreciseClock, cache: Set<UAVariableImpl>) => void;
225
+ declare public $extensionObject?: any;
226
+ declare public $set_ExtensionObject?: (
227
+ newValue: ExtensionObject,
228
+ sourceTimestamp: PreciseClock,
229
+ cache: Set<UAVariableImpl>
230
+ ) => void;
234
231
 
235
- public $historicalDataConfiguration?: UAHistoricalDataConfiguration;
236
- public varHistorian?: IVariableHistorian;
232
+ declare public $historicalDataConfiguration?: UAHistoricalDataConfiguration;
233
+ declare public varHistorian?: IVariableHistorian;
237
234
 
238
235
  /**
239
236
  * @internal @private
240
237
  */
241
238
  public $dataValue: DataValue;
242
239
  public accessLevel: number;
243
- public userAccessLevel?: number;
240
+ declare public userAccessLevel?: number;
244
241
  public valueRank: number;
245
242
  public minimumSamplingInterval: number;
246
243
  public historizing: boolean;
247
244
  public semantic_version: number;
248
245
  public arrayDimensions: null | number[];
249
246
 
250
- public _timestamped_get_func?: TimestampGetFunc | null;
251
- public _timestamped_set_func?: VariableDataValueSetterWithCallback | null;
252
- public _get_func?: GetFunc | null;
253
- public _set_func?: ((value: Variant, callback: (err: Error | null, statusCode?: StatusCode) => void) => void) | null;
254
- public refreshFunc?: (callback: CallbackT<DataValue>) => void;
255
- public __waiting_callbacks?: CallbackT<DataValue>[];
247
+ declare public _timestamped_get_func?: TimestampGetFunc | null;
248
+ declare public _timestamped_set_func?: VariableDataValueSetterWithCallback | null;
249
+ declare public _get_func?: GetFunc | null;
250
+ declare public _set_func?: ((value: Variant, callback: (err: Error | null, statusCode?: StatusCode) => void) => void) | null;
251
+ declare public refreshFunc?: (callback: CallbackT<DataValue>) => void;
252
+ /**
253
+ * true when refreshFunc only wraps the synchronous getter of a Variable bound with a plain
254
+ * `get`. readValue evaluates that getter itself: refreshing such a Variable before reading
255
+ * it, as the Read service does for the others, evaluates the getter twice for nothing.
256
+ */
257
+ declare public _refreshFuncWrapsGetter?: boolean;
258
+ declare public __waiting_callbacks?: CallbackT<DataValue>[];
259
+ // the variant types already found to fit the DataType below (bit k: DataType k), set on the first write
260
+ declare private _validatedForDataType?: NodeId;
261
+ declare private _validatedVariantTypes?: number;
262
+ declare private __satisfy_waiting_callbacks?: (err: Error | null, dataValue?: DataValue) => void;
256
263
 
257
264
  get typeDefinitionObj(): UAVariableType {
258
265
  /* c8 ignore next */
@@ -318,7 +325,6 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
318
325
  }
319
326
  private checkPermissionPrivate(context: ISessionContext, permission: PermissionType): boolean {
320
327
  if (!context) return true;
321
- assert(context instanceof SessionContext);
322
328
  if (context.checkPermission) {
323
329
  if (!(context.checkPermission instanceof Function)) {
324
330
  errorLog("context checkPermission is not a function");
@@ -437,7 +443,14 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
437
443
  if (!this.checkPermissionPrivate(context, PermissionType.Read)) {
438
444
  return makeNowDataValue({ statusCode: StatusCodes.BadUserAccessDenied });
439
445
  }
440
- if (!this.isUserReadable(context)) {
446
+ // isUserReadable() would evaluate isReadable() and the Read permission a second time, and the
447
+ // permission lookup walks the namespace metadata. Both just passed above, so only the access
448
+ // level is left, unless a node replaced isUserReadable (see makeNotReadableIfEnabledFlagIsFalse).
449
+ const userReadable =
450
+ this.isUserReadable === UAVariableImpl.prototype.isUserReadable
451
+ ? this.checkAccessLevelPrivate(context, AccessLevelFlag.CurrentRead)
452
+ : this.isUserReadable(context);
453
+ if (!userReadable) {
441
454
  return makeNowDataValue({ statusCode: StatusCodes.BadNotReadable });
442
455
  }
443
456
  if (!isValidDataEncoding(dataEncoding)) {
@@ -1273,6 +1286,7 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1273
1286
  this._get_func = null;
1274
1287
  this._set_func = null;
1275
1288
  this.refreshFunc = undefined;
1289
+ this._refreshFuncWrapsGetter = false;
1276
1290
  this._historyRead = UAVariableImpl.prototype._historyRead;
1277
1291
  }
1278
1292
 
@@ -1303,12 +1317,22 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1303
1317
  public readValueAsync(context: ISessionContext | null, callback: CallbackT<DataValue>): void;
1304
1318
  public readValueAsync(context: ISessionContext | null): Promise<DataValue>;
1305
1319
  public readValueAsync(context: ISessionContext | null, callback?: CallbackT<DataValue>): Promise<DataValue> | undefined {
1306
- assert(typeof callback === "function");
1320
+ // written out rather than wrapped with thenify-ex: every sample of a monitored item comes
1321
+ // through here with a callback, and the generic wrapper forwarded it with apply(arguments)
1322
+ if (callback) {
1323
+ this._readValueAsync(context, callback);
1324
+ return undefined;
1325
+ }
1326
+ return new Promise<DataValue>((resolve, reject) => {
1327
+ this._readValueAsync(context, (err, dataValue) => (err ? reject(err) : resolve(dataValue as DataValue)));
1328
+ });
1329
+ }
1307
1330
 
1331
+ private _readValueAsync(context: ISessionContext | null, callback: CallbackT<DataValue>): void {
1308
1332
  context = context || SessionContext.defaultContext;
1309
1333
 
1310
1334
  this.__waiting_callbacks = this.__waiting_callbacks || [];
1311
- this.__waiting_callbacks.push(callback as CallbackT<DataValue>);
1335
+ this.__waiting_callbacks.push(callback);
1312
1336
 
1313
1337
  const _readValueAsync_in_progress = this.__waiting_callbacks.length >= 2;
1314
1338
  if (_readValueAsync_in_progress) {
@@ -1320,19 +1344,10 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1320
1344
  return;
1321
1345
  }
1322
1346
 
1323
- const readImmediate = (innerCallback: (err: Error | null, dataValue: DataValue) => void) => {
1324
- assert(this.$dataValue instanceof DataValue);
1325
- const dataValue = this.readValue(context);
1326
- innerCallback(null, dataValue);
1327
- };
1328
-
1329
- let func: (innerCallback: (err: Error | null, dataValue: DataValue) => void) => void;
1330
-
1331
- // stamped like the same gates of readValue(): the requested timestamps are expected
1332
- // on a Bad result too (FEAT-34)
1333
- const answerStatusOnly = (statusCode: StatusCode) => (innerCallback: (err: Error | null, dataValue: DataValue) => void) =>
1334
- innerCallback(null, makeNowDataValue({ statusCode }));
1335
-
1347
+ // runs once per sample of every monitored item, so nothing here allocates a closure or
1348
+ // binds a function: the denial (if any) is a StatusCode, and the callback that releases
1349
+ // the waiting readers is made once per variable
1350
+ let denied: StatusCode | null = null;
1336
1351
  if (context.isAccessRestricted(this)) {
1337
1352
  // same gate as readValue(): a variable bound with a simple get()
1338
1353
  // also gets a refreshFunc, sending every read down the asyncRefresh
@@ -1340,30 +1355,48 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1340
1355
  // through readValue(). Without this check here, a node hardened
1341
1356
  // with AccessRestrictions(EncryptionRequired) answered its value
1342
1357
  // over an unencrypted channel.
1343
- func = answerStatusOnly(StatusCodes.BadSecurityModeInsufficient);
1358
+ denied = StatusCodes.BadSecurityModeInsufficient;
1344
1359
  } else if (!this.isReadable(context)) {
1345
- func = answerStatusOnly(StatusCodes.BadNotReadable);
1360
+ denied = StatusCodes.BadNotReadable;
1346
1361
  } else if (!this.checkPermissionPrivate(context, PermissionType.Read)) {
1347
- func = answerStatusOnly(StatusCodes.BadUserAccessDenied);
1348
- } else if (!this.isUserReadable(context)) {
1349
- func = answerStatusOnly(StatusCodes.BadNotReadable);
1350
- } else {
1351
- const clock = getCurrentClock();
1352
- func = typeof this.refreshFunc === "function" ? this.asyncRefresh.bind(this, clock) : readImmediate;
1362
+ denied = StatusCodes.BadUserAccessDenied;
1363
+ } else if (
1364
+ // as in readValue(): the Read permission has just passed, only the access level is
1365
+ // left, unless a node replaced isUserReadable (see makeNotReadableIfEnabledFlagIsFalse)
1366
+ this.isUserReadable === UAVariableImpl.prototype.isUserReadable
1367
+ ? !this.checkAccessLevelPrivate(context, AccessLevelFlag.CurrentRead)
1368
+ : !this.isUserReadable(context)
1369
+ ) {
1370
+ denied = StatusCodes.BadNotReadable;
1353
1371
  }
1354
1372
 
1355
- const satisfy_callbacks = (err: Error | null, dataValue?: DataValue) => {
1373
+ this.__satisfy_waiting_callbacks ??= (err: Error | null, dataValue?: DataValue) => {
1356
1374
  // now call all pending callbacks
1357
1375
  const callbacks = this.__waiting_callbacks || [];
1358
1376
  this.__waiting_callbacks = [];
1359
- const _n = callbacks.length;
1360
1377
  for (const callback1 of callbacks) {
1361
1378
  callback1.call(this, err, dataValue);
1362
1379
  }
1363
1380
  };
1381
+ const satisfy_callbacks = this.__satisfy_waiting_callbacks;
1364
1382
 
1365
1383
  try {
1366
- func.call(this, satisfy_callbacks);
1384
+ if (denied) {
1385
+ // stamped like the same gates of readValue(): the requested timestamps are expected
1386
+ // on a Bad result too (FEAT-34)
1387
+ satisfy_callbacks(null, makeNowDataValue({ statusCode: denied }));
1388
+ } else if (typeof this.refreshFunc === "function") {
1389
+ const clock = getCurrentClock();
1390
+ if (this.asyncRefresh === wrappedAsyncRefresh) {
1391
+ // the thenified wrapper adds nothing when a callback is given
1392
+ asyncRefreshImpl.call(this, clock, satisfy_callbacks);
1393
+ } else {
1394
+ this.asyncRefresh(clock, satisfy_callbacks);
1395
+ }
1396
+ } else {
1397
+ assert(this.$dataValue instanceof DataValue);
1398
+ satisfy_callbacks(null, this.readValue(context));
1399
+ }
1367
1400
  } catch (err) {
1368
1401
  // c8 ignore next
1369
1402
  if (doDebug) {
@@ -1374,7 +1407,6 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1374
1407
  }
1375
1408
  satisfy_callbacks(err as Error);
1376
1409
  }
1377
- return undefined;
1378
1410
  }
1379
1411
 
1380
1412
  public getWriteMask(): number {
@@ -1879,7 +1911,27 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1879
1911
  }
1880
1912
 
1881
1913
  public _validate_DataType(variantDataType: DataType): boolean {
1882
- return validateDataTypeCorrectness(this.addressSpace, this.dataType, variantDataType, /* allow Nulls */ false, this.nodeId);
1914
+ // every write of a Variable checks the same variant type against the same DataType: a check that
1915
+ // passed is remembered while the DataType stays the same; a refusal is always checked again
1916
+ const bit = variantDataType >= 0 && variantDataType < 31 ? 1 << variantDataType : 0;
1917
+ if (bit && this._validatedForDataType === this.dataType && ((this._validatedVariantTypes ?? 0) & bit) !== 0) {
1918
+ return true;
1919
+ }
1920
+ const valid = validateDataTypeCorrectness(
1921
+ this.addressSpace,
1922
+ this.dataType,
1923
+ variantDataType,
1924
+ /* allow Nulls */ false,
1925
+ this.nodeId
1926
+ );
1927
+ if (valid && bit) {
1928
+ if (this._validatedForDataType !== this.dataType) {
1929
+ this._validatedForDataType = this.dataType;
1930
+ this._validatedVariantTypes = 0;
1931
+ }
1932
+ this._validatedVariantTypes = (this._validatedVariantTypes ?? 0) | bit;
1933
+ }
1934
+ return valid;
1883
1935
  }
1884
1936
 
1885
1937
  public _internal_set_value(value: Variant): void {
@@ -1936,9 +1988,19 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1936
1988
  */
1937
1989
  public _inner_replace_dataValue(dataValue: DataValue, indexRange?: NumericRange | null) {
1938
1990
  assert(this.$dataValue.value instanceof Variant);
1939
- const old_dataValue = this.$dataValue.clone();
1940
1991
 
1941
- if (this.$$extensionObjectArray && dataValue.value.arrayType !== VariantArrayType.Scalar) {
1992
+ const replacesArrayInPlace = !!this.$$extensionObjectArray && dataValue.value.arrayType !== VariantArrayType.Scalar;
1993
+ const replacesExtensionObjectInPlace =
1994
+ this._basicDataType === DataType.ExtensionObject &&
1995
+ this.valueRank === -1 &&
1996
+ !!this.$set_ExtensionObject &&
1997
+ dataValue.value.arrayType === VariantArrayType.Scalar;
1998
+ // the previous value is needed intact for the comparison below. Only the two branches that
1999
+ // write into $dataValue in place need a copy of it: the common case swaps in the new object
2000
+ // and leaves the old one untouched
2001
+ const old_dataValue = replacesArrayInPlace || replacesExtensionObjectInPlace ? this.$dataValue.clone() : this.$dataValue;
2002
+
2003
+ if (replacesArrayInPlace) {
1942
2004
  // we have a bounded array or matrix
1943
2005
  assert(Array.isArray(dataValue.value.value));
1944
2006
  if (this.$$extensionObjectArray !== this.$dataValue.value.value) {
@@ -1952,12 +2014,7 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1952
2014
  this.$dataValue.serverPicoseconds = dataValue.serverPicoseconds;
1953
2015
  this.$dataValue.sourceTimestamp = dataValue.sourceTimestamp;
1954
2016
  this.$dataValue.sourcePicoseconds = dataValue.sourcePicoseconds;
1955
- } else if (
1956
- this._basicDataType === DataType.ExtensionObject &&
1957
- this.valueRank === -1 &&
1958
- this.$set_ExtensionObject &&
1959
- dataValue.value.arrayType === VariantArrayType.Scalar
1960
- ) {
2017
+ } else if (replacesExtensionObjectInPlace && this.$set_ExtensionObject) {
1961
2018
  // the entire extension object is changed.
1962
2019
  this.$dataValue.statusCode = this.$dataValue.statusCode || StatusCodes.Good;
1963
2020
  const preciseClock = coerceClock(this.$dataValue.sourceTimestamp, this.$dataValue.sourcePicoseconds);
@@ -1966,14 +2023,16 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1966
2023
  this.$dataValue = dataValue;
1967
2024
  this.$dataValue.statusCode = this.$dataValue.statusCode || StatusCodes.Good;
1968
2025
  }
1969
- // repair missing timestamps
1970
- const now = new Date();
2026
+ // repair missing timestamps (the clock is only read when one is missing: this runs on
2027
+ // every stored value, and most arrive with both timestamps)
2028
+ let now: Date | undefined;
1971
2029
  if (!dataValue.serverTimestamp) {
2030
+ now = new Date();
1972
2031
  this.$dataValue.serverTimestamp = old_dataValue.serverTimestamp || now;
1973
2032
  this.$dataValue.serverPicoseconds = old_dataValue.serverPicoseconds || 0;
1974
2033
  }
1975
2034
  if (!dataValue.sourceTimestamp) {
1976
- this.$dataValue.sourceTimestamp = old_dataValue.sourceTimestamp || now;
2035
+ this.$dataValue.sourceTimestamp = old_dataValue.sourceTimestamp || (now ?? new Date());
1977
2036
  this.$dataValue.sourcePicoseconds = old_dataValue.sourcePicoseconds || 0;
1978
2037
  }
1979
2038
 
@@ -1997,7 +2056,7 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
1997
2056
  this.touchValue(preciseClock);
1998
2057
  propagateTouchValueDownward(this, preciseClock, cache);
1999
2058
  }
2000
- } else {
2059
+ } else if ((this as UAVariable).listenerCount("value_changed") > 0) {
2001
2060
  (this as UAVariable).emit("value_changed", this.$dataValue.clone(), indexRange);
2002
2061
  }
2003
2062
  }
@@ -2105,11 +2164,15 @@ export class UAVariableImpl<T extends UAVariableEvents & ListenerSignature<T> =
2105
2164
  import type { IStructuredTypeSchema } from "node-opcua-factory";
2106
2165
  import { withCallback } from "thenify-ex";
2107
2166
 
2108
- UAVariableImpl.prototype.asyncRefresh = withCallback(UAVariableImpl.prototype.asyncRefresh);
2167
+ // readValueAsync calls the unwrapped asyncRefresh directly: binding the thenified wrapper on
2168
+ // every sample cost more than the refresh itself
2169
+ const asyncRefreshImpl: (this: UAVariableImpl, oldestDate: PreciseClock, callback: CallbackT<DataValue>) => void =
2170
+ UAVariableImpl.prototype.asyncRefresh;
2171
+ UAVariableImpl.prototype.asyncRefresh = withCallback(asyncRefreshImpl);
2172
+ const wrappedAsyncRefresh = UAVariableImpl.prototype.asyncRefresh;
2109
2173
  UAVariableImpl.prototype.writeValue = withCallback(UAVariableImpl.prototype.writeValue);
2110
2174
  UAVariableImpl.prototype.writeAttribute = withCallback(UAVariableImpl.prototype.writeAttribute);
2111
2175
  UAVariableImpl.prototype.historyRead = withCallback(UAVariableImpl.prototype.historyRead);
2112
- UAVariableImpl.prototype.readValueAsync = withCallback(UAVariableImpl.prototype.readValueAsync);
2113
2176
 
2114
2177
  export interface UAVariableImplExtArray {
2115
2178
  $$variableType?: UAVariableType;
@@ -2230,26 +2293,24 @@ type f3<T, D, R> = (this: T, data: D, callback: (err: Error | null, r?: R) => vo
2230
2293
  function turn_sync_to_async<T, D, R>(f: f1<T, D, R> | f2<T, D, R> | f3<T, D, R>, numberOfArgs: number): f3<T, D, R> {
2231
2294
  if (f.length <= numberOfArgs) {
2232
2295
  return function (this: T, data: D, callback: (err: Error | null, r?: R) => void) {
2233
- new Promise<R>((resolve, reject) => {
2234
- try {
2235
- // const ff1 = f as f1<T, D, R>;
2236
- const ff2 = f as f2<T, D, R>;
2237
- const r: R | Promise<R> = ff2.call(this, data);
2238
- if (r instanceof Promise) {
2239
- r.then(resolve, reject);
2240
- } else {
2241
- resolve(r);
2242
- }
2243
- } catch (err) {
2244
- reject(err);
2245
- }
2246
- })
2247
- .then((r) => {
2248
- callback(null, r);
2249
- })
2250
- .catch((err) => {
2251
- callback(err as Error);
2252
- });
2296
+ const ff2 = f as f2<T, D, R>;
2297
+ let r: R | Promise<R>;
2298
+ try {
2299
+ r = ff2.call(this, data);
2300
+ } catch (err) {
2301
+ callback(err as Error);
2302
+ return;
2303
+ }
2304
+ if (r instanceof Promise) {
2305
+ r.then(
2306
+ (value) => callback(null, value),
2307
+ (err) => callback(err as Error)
2308
+ );
2309
+ return;
2310
+ }
2311
+ // a synchronous function answers at once: a Write of such Variables completes without waiting
2312
+ // a turn per value, under one permission cache for the whole request
2313
+ callback(null, r);
2253
2314
  };
2254
2315
  } else {
2255
2316
  assert(f.length === numberOfArgs + 1);
@@ -2307,12 +2368,26 @@ function _Variable_bind_with_timestamped_get(
2307
2368
  assert(!this._timestamped_get_func);
2308
2369
 
2309
2370
  const async_refresh_func = (callback: (err: Error | null, dataValue?: DataValue) => void) => {
2310
- Promise.resolve((this._timestamped_get_func as VariableDataValueGetterSync).call(this))
2311
- .then((dataValue) => callback(null, dataValue))
2312
- .catch((err) => {
2313
- errorLog("asyncRefresh error: Variable is ", this.nodeId.toString(), this.browseName.toString());
2314
- callback(err as Error);
2315
- });
2371
+ const reportError = (err: unknown) => {
2372
+ errorLog("asyncRefresh error: Variable is ", this.nodeId.toString(), this.browseName.toString());
2373
+ callback(err as Error);
2374
+ };
2375
+ // a getter that throws is reported by asyncRefresh, which calls this function
2376
+ const dataValueOrPromise = (this._timestamped_get_func as () => DataValue | Promise<DataValue>).call(this);
2377
+ if (isThenable(dataValueOrPromise)) {
2378
+ Promise.resolve(dataValueOrPromise)
2379
+ .then((dataValue) => callback(null, dataValue))
2380
+ .catch(reportError);
2381
+ return;
2382
+ }
2383
+ // the getter is synchronous (it always is for a Variable bound with a plain `get`):
2384
+ // answer at once. A promise here costs every Read of the Variable an allocation and
2385
+ // a turn of the microtask queue, for a value that is already known.
2386
+ try {
2387
+ callback(null, dataValueOrPromise);
2388
+ } catch (err) {
2389
+ reportError(err);
2390
+ }
2316
2391
  };
2317
2392
  const pThis = this as UAVariable;
2318
2393
  if (options.timestamped_get.length === 0) {
@@ -2356,21 +2431,10 @@ function _Variable_bind_with_simple_get(this: UAVariableImpl, options: GetterOpt
2356
2431
  const timestamped_get_func_from__Variable_bind_with_simple_get = () => {
2357
2432
  const value: Variant | StatusCode = (this._get_func as GetFunc)();
2358
2433
 
2359
- /* c8 ignore next */
2360
- if (!is_Variant_or_StatusCode(value)) {
2361
- errorLog(
2362
- chalk.red(" Bind variable error: "),
2363
- " : the getter must return a Variant or a StatusCode" + "\nvalue_check.constructor.name ",
2364
- value ? value.constructor.name : "null"
2365
- );
2366
- throw new Error(
2367
- " bindVariable : the value getter function returns a invalid result ( expecting a Variant or a StatusCode !!!"
2368
- );
2369
- }
2370
- if (is_StatusCode(value)) {
2371
- return new DataValue({ statusCode: value as StatusCode });
2372
- } else {
2373
- if (!this.$dataValue?.statusCode.isGoodish() || !sameVariant(this.$dataValue.value, value as Variant)) {
2434
+ // a getter returns a Variant on every read but a failing one: test for it first, as
2435
+ // is_StatusCode ends in comparisons of constructor names
2436
+ if (value instanceof Variant) {
2437
+ if (!this.$dataValue?.statusCode.isGoodish() || !sameVariant(this.$dataValue.value, value)) {
2374
2438
  // rebuilding artificially timestamps with current clock as they are not provided
2375
2439
  // by the underlying getter function
2376
2440
  const { timestamp: sourceTimestamp, picoseconds: sourcePicoseconds } = getCurrentClock();
@@ -2382,12 +2446,26 @@ function _Variable_bind_with_simple_get(this: UAVariableImpl, options: GetterOpt
2382
2446
  }
2383
2447
  return this.$dataValue;
2384
2448
  }
2449
+
2450
+ /* c8 ignore next */
2451
+ if (!is_StatusCode(value)) {
2452
+ errorLog(
2453
+ chalk.red(" Bind variable error: "),
2454
+ " : the getter must return a Variant or a StatusCode" + "\nvalue_check.constructor.name ",
2455
+ value ? value.constructor.name : "null"
2456
+ );
2457
+ throw new Error(
2458
+ " bindVariable : the value getter function returns a invalid result ( expecting a Variant or a StatusCode !!!"
2459
+ );
2460
+ }
2461
+ return new DataValue({ statusCode: value as StatusCode });
2385
2462
  };
2386
2463
 
2387
2464
  _Variable_bind_with_timestamped_get.call(this, {
2388
2465
  get: undefined,
2389
2466
  timestamped_get: timestamped_get_func_from__Variable_bind_with_simple_get
2390
2467
  });
2468
+ this._refreshFuncWrapsGetter = true;
2391
2469
  }
2392
2470
 
2393
2471
  type SimpleSetOptions = { set?: SetFunc; timestamped_set: undefined };
@@ -2440,8 +2518,18 @@ function _Variable_bind_with_timestamped_set(
2440
2518
  "timestamped_set must have 2 parameters timestamped_set: function(dataValue,callback){} or one paramater timestamped_set: function(dataValue): Promise<StatusCode>{}"
2441
2519
  );
2442
2520
  assert(!options.set, "should not specify set when timestamped_set_func exists ");
2443
- this._timestamped_set_func = convertToCallbackFunction1<StatusCode, DataValue, UAVariable>(options.timestamped_set);
2521
+ // the callback form of a setter is a stateless wrapper around it (it takes the variable as
2522
+ // `this`): one per setter function, not one per variable. Every writable variable without a
2523
+ // setter of its own binds the same default, and a closure per variable was 110 bytes each
2524
+ let setter = callbackSetters.get(options.timestamped_set);
2525
+ if (!setter) {
2526
+ setter = convertToCallbackFunction1<StatusCode, DataValue, UAVariable>(options.timestamped_set);
2527
+ callbackSetters.set(options.timestamped_set, setter);
2528
+ }
2529
+ this._timestamped_set_func = setter;
2444
2530
  }
2531
+ /** the callback form of each setter function met so far, see _Variable_bind_with_timestamped_set */
2532
+ const callbackSetters = new WeakMap<TimestampSetFunc, VariableDataValueSetterWithCallback>();
2445
2533
 
2446
2534
  interface SetterOptions {
2447
2535
  set?: SetFunc;
@@ -2528,9 +2616,9 @@ export class UAVariableImplT<T, DT extends DataType> extends UAVariableImpl impl
2528
2616
  context: ISessionContext | null,
2529
2617
  callback?: CallbackT<DataValueT<T, DT>>
2530
2618
  ): Promise<DataValueT<T, DT>> | undefined {
2531
- // forward the arguments we were actually given. The base method is thenified, and
2532
- // thenify decides between promise and callback form by argument count, so passing an
2533
- // explicit undefined callback makes it hand that undefined straight through.
2619
+ // forward the arguments we were actually given: the base method picks the promise or the
2620
+ // callback form from the callback it receives (writeValue below, still thenified, picks
2621
+ // it from the argument count, where an explicit undefined callback would be passed on)
2534
2622
  if (callback === undefined) {
2535
2623
  return super.readValueAsync(context) as Promise<DataValueT<T, DT>>;
2536
2624
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "node-opcua-address-space",
3
3
  "type": "module",
4
- "version": "2.186.17",
4
+ "version": "2.187.1",
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,48 +23,49 @@
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.186.17",
26
+ "node-opcua-address-space-base": "2.187.0",
27
+ "node-opcua-address-space-store": "2.187.1",
27
28
  "node-opcua-assert": "2.186.7",
28
- "node-opcua-basic-types": "2.186.7",
29
- "node-opcua-binary-stream": "2.186.7",
30
- "node-opcua-client-dynamic-extension-object": "2.186.17",
29
+ "node-opcua-basic-types": "2.187.0",
30
+ "node-opcua-binary-stream": "2.187.0",
31
+ "node-opcua-client-dynamic-extension-object": "2.187.0",
31
32
  "node-opcua-constants": "2.185.0",
32
- "node-opcua-crypto": "6.1.4",
33
- "node-opcua-data-access": "2.186.10",
34
- "node-opcua-data-model": "2.186.10",
35
- "node-opcua-data-value": "2.186.10",
36
- "node-opcua-date-time": "2.186.7",
33
+ "node-opcua-crypto": "6.2.0",
34
+ "node-opcua-data-access": "2.187.0",
35
+ "node-opcua-data-model": "2.187.0",
36
+ "node-opcua-data-value": "2.187.0",
37
+ "node-opcua-date-time": "2.187.0",
37
38
  "node-opcua-debug": "2.186.7",
38
- "node-opcua-enum": "2.186.7",
39
- "node-opcua-extension-object": "2.186.7",
40
- "node-opcua-factory": "2.186.7",
41
- "node-opcua-nodeid": "2.186.7",
42
- "node-opcua-nodeset-ua": "2.186.17",
43
- "node-opcua-numeric-range": "2.186.7",
39
+ "node-opcua-enum": "2.187.0",
40
+ "node-opcua-extension-object": "2.187.0",
41
+ "node-opcua-factory": "2.187.0",
42
+ "node-opcua-nodeid": "2.187.0",
43
+ "node-opcua-nodeset-ua": "2.187.0",
44
+ "node-opcua-numeric-range": "2.187.0",
44
45
  "node-opcua-object-registry": "2.186.7",
45
- "node-opcua-pseudo-session": "2.186.17",
46
- "node-opcua-service-browse": "2.186.10",
47
- "node-opcua-service-call": "2.186.10",
48
- "node-opcua-service-history": "2.186.10",
49
- "node-opcua-service-translate-browse-path": "2.186.16",
50
- "node-opcua-service-write": "2.186.10",
51
- "node-opcua-status-code": "2.186.7",
52
- "node-opcua-types": "2.186.10",
53
- "node-opcua-utils": "2.186.7",
54
- "node-opcua-variant": "2.186.10",
55
- "node-opcua-xml2json": "2.186.7",
46
+ "node-opcua-pseudo-session": "2.187.0",
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",
52
+ "node-opcua-status-code": "2.187.0",
53
+ "node-opcua-types": "2.187.0",
54
+ "node-opcua-utils": "2.187.0",
55
+ "node-opcua-variant": "2.187.0",
56
+ "node-opcua-xml2json": "2.187.0",
56
57
  "semver": "^7.8.5",
57
58
  "thenify-ex": "4.4.0",
58
59
  "xml-writer": "^1.7.0"
59
60
  },
60
61
  "devDependencies": {
61
62
  "humanize": "0.0.9",
62
- "node-opcua-benchmarker": "2.186.7",
63
+ "node-opcua-benchmarker": "2.187.0",
63
64
  "node-opcua-leak-detector": "2.186.7",
64
- "node-opcua-nodesets": "2.185.0",
65
- "node-opcua-packet-analyzer": "2.186.7",
66
- "node-opcua-schemas": "2.186.10",
67
- "node-opcua-service-filter": "2.186.17",
65
+ "node-opcua-nodesets": "2.187.0",
66
+ "node-opcua-packet-analyzer": "2.187.0",
67
+ "node-opcua-schemas": "2.187.0",
68
+ "node-opcua-service-filter": "2.187.0",
68
69
  "node-opcua-test-fixtures": "2.184.0",
69
70
  "node-opcua-test-helpers": "2.184.0"
70
71
  },
@@ -83,7 +84,7 @@
83
84
  "internet of things"
84
85
  ],
85
86
  "homepage": "http://node-opcua.github.io/",
86
- "gitHead": "bdb3848c06d19056a99dfc06ef73771a0666472f",
87
+ "gitHead": "706db482c6f77cd5c7c1f98f9f7cf1c5841a0161",
87
88
  "files": [
88
89
  "dist",
89
90
  "distHelpers",