node-opcua-address-space 2.164.2 → 2.165.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.
@@ -2,63 +2,41 @@
2
2
  * @module node-opcua-address-space
3
3
  */
4
4
  import chalk from "chalk";
5
-
5
+ import type { BaseNode, ISessionContext, UADataType, UAObject, UAVariable } from "node-opcua-address-space-base";
6
6
  import { assert } from "node-opcua-assert";
7
- import { NodeClass, QualifiedNameLike } from "node-opcua-data-model";
8
- import { AttributeIds } from "node-opcua-data-model";
9
- import { DataValue, DataValueLike } from "node-opcua-data-value";
10
- import { ExpandedNodeId, NodeId } from "node-opcua-nodeid";
7
+ import { coerceInt64, coerceInt64toInt32, type Int64 } from "node-opcua-basic-types";
8
+ import { DataTypeIds } from "node-opcua-constants";
9
+ import { AttributeIds, type LocalizedText, NodeClass, type QualifiedNameLike } from "node-opcua-data-model";
10
+ import { DataValue, type DataValueLike } from "node-opcua-data-value";
11
+ import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
12
+ import { ExpandedNodeId, NodeId, resolveNodeId } from "node-opcua-nodeid";
11
13
  import { NumericRange } from "node-opcua-numeric-range";
12
14
  import { StatusCodes } from "node-opcua-status-code";
13
15
  import {
14
- DataTypeDefinition,
16
+ type DataTypeDefinition,
15
17
  EnumDefinition,
16
- EnumFieldOptions,
18
+ type EnumFieldOptions,
19
+ type EnumValueType,
17
20
  StructureDefinition,
18
- StructureFieldOptions,
21
+ type StructureFieldOptions,
19
22
  StructureType
20
23
  } from "node-opcua-types";
21
- import {
22
- DataType
23
- } from "node-opcua-variant";
24
- import {
25
- UAObject,
26
- ISessionContext,
27
- UADataType,
28
- UAVariable,
29
- BaseNode
30
- } from "node-opcua-address-space-base";
31
- import {
32
- DataTypeIds
33
- } from "node-opcua-constants";
34
- import {
35
- Int64,
36
- coerceInt64,
37
- coerceInt64toInt32
38
- } from "node-opcua-basic-types";
24
+ import { DataType } from "node-opcua-variant";
25
+ import type { ExtensionObjectConstructorFuncWithSchema } from "../source/interfaces/extension_object_constructor";
39
26
  import { SessionContext } from "../source/session_context";
40
- import { ExtensionObjectConstructorFuncWithSchema } from "../source/interfaces/extension_object_constructor";
41
- import { BaseNodeImpl, InternalBaseNodeOptions } from "./base_node_impl";
27
+ import { BaseNodeImpl, type InternalBaseNodeOptions } from "./base_node_impl";
42
28
  import {
29
+ BaseNode_getCache,
43
30
  BaseNode_References_toString,
44
31
  BaseNode_toString,
45
32
  ToStringBuilder,
46
- ToStringOption
33
+ type ToStringOption
47
34
  } from "./base_node_private";
48
- import { construct_isSubtypeOf } from "./tool_isSubtypeOf";
49
- import { get_subtypeOf } from "./tool_isSubtypeOf";
50
- import { get_subtypeOfObj } from "./tool_isSubtypeOf";
51
- import { BaseNode_getCache } from "./base_node_private";
52
- import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
53
-
35
+ import { construct_isSubtypeOf, get_subtypeOf, get_subtypeOfObj } from "./tool_isSubtypeOf";
54
36
 
55
37
  const debugLog = make_debugLog("DATA_TYPE");
56
38
  const doDebug = checkDebugFlag("DATA_TYPE");
57
39
 
58
- export interface UADataTypeImpl {
59
- _extensionObjectConstructor: ExtensionObjectConstructorFuncWithSchema;
60
- }
61
-
62
40
 
63
41
  export interface StructureFieldOptionsEx extends StructureFieldOptions {
64
42
  allowSubTypes: boolean;
@@ -80,6 +58,7 @@ export interface UADataTypeOptions extends InternalBaseNodeOptions {
80
58
  }
81
59
 
82
60
  export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
61
+ public _extensionObjectConstructor!: ExtensionObjectConstructorFuncWithSchema;
83
62
  public readonly nodeClass = NodeClass.DataType;
84
63
  public readonly definitionName: string = "";
85
64
  public readonly symbolicName: string;
@@ -100,7 +79,7 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
100
79
  }
101
80
 
102
81
  public get subtypeOfObj(): UADataType | null {
103
- return get_subtypeOfObj.call(this) as any as UADataType;
82
+ return get_subtypeOfObj.call(this) as unknown as UADataType;
104
83
  }
105
84
 
106
85
  /** @deprecated */
@@ -108,10 +87,8 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
108
87
  public isSubtypeOf = construct_isSubtypeOf<UADataType>(UADataTypeImpl);
109
88
 
110
89
  public readonly isAbstract: boolean;
111
-
112
- private $isUnion?: boolean;
113
- private enumStrings?: any;
114
- private enumValues?: any;
90
+ private enumStrings?: UAVariable;
91
+ private enumValues?: UAVariable;
115
92
  private $partialDefinition?: StructureFieldOptionsEx[] | EnumFieldOptions[];
116
93
  private $fullDefinition?: StructureDefinition | EnumDefinition;
117
94
 
@@ -119,10 +96,9 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
119
96
  super(options);
120
97
  if (options.partialDefinition) {
121
98
  this.$partialDefinition = options.partialDefinition;
122
- this.$isUnion = options.isUnion;
123
99
  }
124
100
  this.isAbstract = options.isAbstract === undefined || options.isAbstract === null ? false : options.isAbstract;
125
- this.symbolicName = options.symbolicName || this.browseName.name!;
101
+ this.symbolicName = options.symbolicName || this.browseName.name || "";
126
102
  }
127
103
 
128
104
  public get basicDataType(): DataType {
@@ -165,10 +141,10 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
165
141
  public getEncodingDefinition(encoding_name: string): string | null {
166
142
  const encodingNode = this.getEncodingNode(encoding_name);
167
143
  if (!encodingNode) {
168
- throw new Error("Cannot find Encoding for " + encoding_name);
144
+ throw new Error(`Cannot find Encoding for ${encoding_name}`);
169
145
  }
170
146
  const indexRange = new NumericRange();
171
- const descriptionNodeRef = encodingNode.findReferences("HasDescription")[0]!;
147
+ const descriptionNodeRef = encodingNode.findReferences("HasDescription")[0];
172
148
  const descriptionNode = this.addressSpace.findNode(descriptionNodeRef.nodeId) as UAVariable;
173
149
  if (!descriptionNode) {
174
150
  return null;
@@ -180,7 +156,7 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
180
156
  public getEncodingNode(encoding_name: string): UAObject | null {
181
157
  const _cache = BaseNode_getCache(this);
182
158
  _cache._encoding = _cache._encoding || new Map();
183
- const key = encoding_name + "Node";
159
+ const key = `${encoding_name}Node`;
184
160
  if (!_cache._encoding.has(key)) {
185
161
  assert(encoding_name === "Default Binary" || encoding_name === "Default XML" || encoding_name === "Default JSON");
186
162
  // could be binary or xml
@@ -188,11 +164,11 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
188
164
  const addressSpace = this.addressSpace;
189
165
  const encoding = refs
190
166
  .map((ref) => addressSpace.findNode(ref.nodeId))
191
- .filter((obj: any) => obj !== null)
192
- .filter((obj: any) => obj.browseName.toString() === encoding_name);
167
+ .filter((obj): obj is BaseNode => obj !== null)
168
+ .filter((obj) => obj.browseName.toString() === encoding_name);
193
169
  const node = encoding.length === 0 ? null : (encoding[0] as UAObject);
194
170
  _cache._encoding.set(key, node);
195
- return node
171
+ return node;
196
172
  }
197
173
  return _cache._encoding.get(key) || null;
198
174
  }
@@ -247,7 +223,7 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
247
223
  if (this.enumStrings) {
248
224
  const enumStrings = this.enumStrings.readValue().value.value;
249
225
  assert(Array.isArray(enumStrings));
250
- definition = enumStrings.map((e: any, index: number) => {
226
+ definition = enumStrings.map((e: LocalizedText, index: number) => {
251
227
  return {
252
228
  name: e.text,
253
229
  value: index
@@ -257,7 +233,7 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
257
233
  assert(this.enumValues, "must have a enumValues property");
258
234
  const enumValues = this.enumValues.readValue().value.value;
259
235
  assert(Array.isArray(enumValues));
260
- definition = enumValues.map((e: any) => {
236
+ definition = enumValues.map((e: EnumValueType) => {
261
237
  return {
262
238
  name: e.displayName.text,
263
239
  value: coerceInt64toInt32(e.value)
@@ -317,9 +293,8 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
317
293
 
318
294
  // https://reference.opcfoundation.org/v105/Core/docs/Part3/8.49/#Table34
319
295
  if (isStructure) {
320
-
321
296
  let dataTypeNode: UADataTypeImpl | null = this;
322
- const allPartialDefinitions: (StructureFieldOptionsEx[])[] = [];
297
+ const allPartialDefinitions: StructureFieldOptionsEx[][] = [];
323
298
  while (dataTypeNode && !isRootDataType(dataTypeNode)) {
324
299
  if (dataTypeNode.$partialDefinition) {
325
300
  allPartialDefinitions.push(dataTypeNode.$partialDefinition as StructureFieldOptionsEx[]);
@@ -337,14 +312,7 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
337
312
 
338
313
  const defaultEncodingId = this.binaryEncodingNodeId || this.xmlEncodingNodeId || new NodeId();
339
314
 
340
-
341
-
342
- this.$fullDefinition = makeStructureDefinition(
343
- basicDataType,
344
- defaultEncodingId,
345
- definitionFields,
346
- isUnion
347
- );
315
+ this.$fullDefinition = makeStructureDefinition(basicDataType, defaultEncodingId, definitionFields, isUnion);
348
316
  } else if (isEnumeration) {
349
317
  const allPartialDefinitions: StructureFieldOptions[][] = [];
350
318
  // eslint-disable-next-line @typescript-eslint/no-this-alias
@@ -363,7 +331,7 @@ export class UADataTypeImpl extends BaseNodeImpl implements UADataType {
363
331
  this.$fullDefinition = makeEnumDefinition(definitionFields);
364
332
  }
365
333
 
366
- return this.$fullDefinition!;
334
+ return this.$fullDefinition as DataTypeDefinition;
367
335
  }
368
336
 
369
337
  public getDefinition(): DataTypeDefinition {
@@ -395,14 +363,14 @@ function dataTypeDefinition_toString(this: UADataTypeImpl, options: ToStringOpti
395
363
  const output = definition.toString();
396
364
  options.add(options.padding + chalk.yellow(" Definition : "));
397
365
  for (const str of output.split("\n")) {
398
- options.add(options.padding + chalk.yellow(" : " + str));
366
+ options.add(options.padding + chalk.yellow(` : ${str}`));
399
367
  }
400
368
  }
401
369
 
402
370
  export function DataType_toString(this: UADataTypeImpl, options: ToStringOption): void {
403
371
  BaseNode_toString.call(this, options);
404
- options.add(options.padding + chalk.yellow(" isAbstract : " + this.isAbstract));
405
- options.add(options.padding + chalk.yellow(" definitionName : " + this.definitionName));
372
+ options.add(options.padding + chalk.yellow(` isAbstract : ${this.isAbstract}`));
373
+ options.add(options.padding + chalk.yellow(` definitionName : ${this.definitionName}`));
406
374
 
407
375
  options.add(
408
376
  options.padding +
@@ -450,7 +418,6 @@ function makeStructureDefinition(
450
418
  fields: StructureFieldOptionsEx[],
451
419
  isUnion: boolean
452
420
  ): StructureDefinition {
453
-
454
421
  const hasSubtypedFields = fields.filter((field) => field.allowSubTypes).length > 0;
455
422
 
456
423
  if (hasSubtypedFields && doDebug) {
@@ -463,18 +430,15 @@ function makeStructureDefinition(
463
430
  }
464
431
  const hasOptionalFields = fields.filter((field) => field.isOptional).length > 0;
465
432
 
466
-
467
-
468
433
  const structureType = isUnion
469
- ?
470
- (
471
- hasSubtypedFields ? StructureType.UnionWithSubtypedValues : StructureType.Union
472
- )
434
+ ? hasSubtypedFields
435
+ ? StructureType.UnionWithSubtypedValues
436
+ : StructureType.Union
473
437
  : hasOptionalFields
474
438
  ? StructureType.StructureWithOptionalFields
475
- : (
476
- hasSubtypedFields ? StructureType.StructureWithSubtypedValues : StructureType.Structure
477
- );
439
+ : hasSubtypedFields
440
+ ? StructureType.StructureWithSubtypedValues
441
+ : StructureType.Structure;
478
442
 
479
443
  // note: https://reference.opcfoundation.org/Core/Part3/v105/docs/8.51
480
444
  // field.isOptional has a special behavior depending on the structure type
@@ -490,8 +454,7 @@ function makeStructureDefinition(
490
454
  // indicate if the data type field allows subtyping.Subtyping is allowed when set to TRUE.
491
455
 
492
456
  const isUnionOrStructureWithSubtypedValues =
493
- structureType === StructureType.UnionWithSubtypedValues ||
494
- structureType === StructureType.StructureWithSubtypedValues;
457
+ structureType === StructureType.UnionWithSubtypedValues || structureType === StructureType.StructureWithSubtypedValues;
495
458
 
496
459
  if (isUnionOrStructureWithSubtypedValues) {
497
460
  for (const field of fields) {
@@ -503,10 +466,18 @@ function makeStructureDefinition(
503
466
  }
504
467
  }
505
468
 
469
+ // Normalize: default missing field dataType to BaseDataType (i=24)
470
+ // per OPC UA spec, a missing DataType should reference BaseDataType,
471
+ // not Null NodeId (i=0)
472
+ const baseDataTypeNodeId = resolveNodeId(DataTypeIds.BaseDataType);
473
+
506
474
  const sd = new StructureDefinition({
507
475
  baseDataType,
508
476
  defaultEncodingId,
509
- fields,
477
+ fields: fields.map((f) => ({
478
+ ...f,
479
+ dataType: f.dataType ?? baseDataTypeNodeId
480
+ })),
510
481
  structureType
511
482
  });
512
483
  return sd;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @module node-opcua-address-space
3
3
  */
4
- import { callbackify, types } from "util";
4
+ import { callbackify } from "util";
5
5
  import chalk from "chalk";
6
6
  import { assert } from "node-opcua-assert";
7
7
 
@@ -12,7 +12,7 @@ import { make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug"
12
12
  import { NodeId } from "node-opcua-nodeid";
13
13
  import { NumericRange } from "node-opcua-numeric-range";
14
14
  import { Argument } from "node-opcua-service-call";
15
- import { CallbackT, StatusCodes } from "node-opcua-status-code";
15
+ import { CallbackT, StatusCode, StatusCodes } from "node-opcua-status-code";
16
16
  import { CallMethodResultOptions, PermissionType } from "node-opcua-types";
17
17
  import { Variant } from "node-opcua-variant";
18
18
  import { DataType, VariantLike } from "node-opcua-variant";
@@ -209,47 +209,90 @@ export class UAMethodImpl extends BaseNodeImpl implements UAMethod {
209
209
 
210
210
  context.object = object;
211
211
 
212
- try {
213
- this._asyncExecutionFunction.call(
214
- this as UAMethodImpl,
215
- inputArguments as Variant[],
216
- context,
217
- (err: Error | null, callMethodResult?: CallMethodResultOptions) => {
218
- if (err) {
219
- debugLog(err.message);
220
- debugLog(err);
212
+ // ── method call interceptors ──
213
+ const addressSpace = this.addressSpace as AddressSpacePrivate;
214
+ const interceptors = addressSpace._methodCallInterceptors;
215
+ const self = this;
216
+ const coercedInputArguments = inputArguments as Variant[];
217
+ const callObject = object;
218
+
219
+ const runInterceptorsAndExecute = async () => {
220
+ // Run interceptors sequentially
221
+ for (const interceptor of interceptors) {
222
+ let status: StatusCode;
223
+ try {
224
+ status = await interceptor(context, callObject, self, coercedInputArguments);
225
+ } catch (err) {
226
+ if (err instanceof Error) {
227
+ warningLog(chalk.red("ERR in method interceptor"), err.message);
221
228
  }
222
- callMethodResult = callMethodResult || {};
223
-
224
- callMethodResult.statusCode = callMethodResult.statusCode || StatusCodes.Good;
225
- callMethodResult.outputArguments = callMethodResult.outputArguments || [];
226
-
227
- callMethodResult.inputArgumentResults =
228
- callMethodResult.inputArgumentResults?.length === inputArguments?.length
229
- ? callMethodResult.inputArgumentResults
230
- : inputArguments?.map(() => StatusCodes.Good);
231
- callMethodResult.inputArgumentDiagnosticInfos =
232
- callMethodResult.inputArgumentDiagnosticInfos || inputArgumentDiagnosticInfos;
233
-
234
- // verify that output arguments are correct according to schema
235
- // Todo : ...
236
- // const outputArgsDef = this.getOutputArguments();
237
-
238
- // xx assert(outputArgsDef.length === callMethodResponse.outputArguments.length,
239
- // xx "_asyncExecutionFunction did not provide the expected number of output arguments");
240
- // to be continued ...
229
+ const callMethodResponse = { statusCode: StatusCodes.BadInternalError };
230
+ callback!(err as Error, callMethodResponse);
231
+ return;
232
+ }
233
+ if (status !== StatusCodes.Good) {
234
+ const callMethodResult: CallMethodResultOptions = {
235
+ statusCode: status,
236
+ outputArguments: [],
237
+ inputArgumentResults: coercedInputArguments?.map(() => status) || [],
238
+ inputArgumentDiagnosticInfos
239
+ };
240
+ return callback!(null, callMethodResult);
241
+ }
242
+ }
241
243
 
242
- callback(err, callMethodResult);
244
+ // All interceptors passed — execute the method body
245
+ try {
246
+ self._asyncExecutionFunction!.call(
247
+ self as UAMethodImpl,
248
+ coercedInputArguments,
249
+ context,
250
+ (err: Error | null, callMethodResult?: CallMethodResultOptions) => {
251
+ if (err) {
252
+ debugLog(err.message);
253
+ debugLog(err);
254
+ }
255
+ callMethodResult = callMethodResult || {};
256
+
257
+ callMethodResult.statusCode = callMethodResult.statusCode || StatusCodes.Good;
258
+ callMethodResult.outputArguments = callMethodResult.outputArguments || [];
259
+
260
+ callMethodResult.inputArgumentResults =
261
+ callMethodResult.inputArgumentResults?.length === coercedInputArguments?.length
262
+ ? callMethodResult.inputArgumentResults
263
+ : coercedInputArguments?.map(() => StatusCodes.Good);
264
+ callMethodResult.inputArgumentDiagnosticInfos =
265
+ callMethodResult.inputArgumentDiagnosticInfos || inputArgumentDiagnosticInfos;
266
+
267
+ // ── afterCall event ──
268
+ try {
269
+ self.emit("afterCall", context, coercedInputArguments, callMethodResult);
270
+ } catch (afterCallErr) {
271
+ if (afterCallErr instanceof Error) {
272
+ warningLog(chalk.red("ERR in afterCall listener"), afterCallErr.message);
273
+ }
274
+ }
275
+
276
+ callback!(err, callMethodResult);
277
+ }
278
+ );
279
+ } catch (err) {
280
+ if (err instanceof Error) {
281
+ warningLog(chalk.red("ERR in method handler"), err.message);
282
+ warningLog(err.stack);
243
283
  }
244
- );
245
- } catch (err) {
246
- if (types.isNativeError(err)) {
247
- warningLog(chalk.red("ERR in method handler"), err.message);
248
- warningLog(err.stack);
284
+ const callMethodResponse = { statusCode: StatusCodes.BadInternalError };
285
+ callback!(err as Error, callMethodResponse);
286
+ }
287
+ };
288
+
289
+ runInterceptorsAndExecute().catch((err) => {
290
+ if (err instanceof Error) {
291
+ warningLog(chalk.red("ERR in method interceptor"), err.message);
249
292
  }
250
293
  const callMethodResponse = { statusCode: StatusCodes.BadInternalError };
251
- callback(err as Error, callMethodResponse);
252
- }
294
+ callback!(err as Error, callMethodResponse);
295
+ });
253
296
  }
254
297
 
255
298
  public clone(options: CloneOptions, optionalFilter?: CloneFilter, extraInfo?: CloneExtraInfo): UAMethod {
@@ -272,7 +315,7 @@ export class UAMethodImpl extends BaseNodeImpl implements UAMethod {
272
315
  UAMethodImpl,
273
316
  options,
274
317
  optionalFilter || defaultCloneFilter,
275
- extraInfo
318
+ extraInfo
276
319
  ) as UAMethodImpl;
277
320
 
278
321
  clonedMethod._asyncExecutionFunction = this._asyncExecutionFunction;
@@ -153,13 +153,57 @@ export function setExtensionObjectPartialValue(node: UAVariableImpl, partialObje
153
153
 
154
154
  const extensionObject = node.$extensionObject;
155
155
  if (!extensionObject) {
156
- throw new Error("setExtensionObjectValue node has no extension object " + node.browseName.toString());
156
+ throw new Error(`setExtensionObjectValue node has no extension object ${node.browseName.toString()}`);
157
+ }
158
+ /**
159
+ * Returns true if the value is a structure-like object that should
160
+ * be recursed into during a partial update.
161
+ *
162
+ * For the existing extension object (extObject[prop]):
163
+ * - Proxied sub-extension objects → recurse (they have $isProxy)
164
+ * For the incoming partial object (partialObject1[prop]):
165
+ * - Plain objects {} → recurse (from constructExtensionObject or literals)
166
+ *
167
+ * Everything else (Date, Buffer, NodeId, QualifiedName, LocalizedText,
168
+ * DiagnosticInfo, arrays, etc.) is a terminal value and should be
169
+ * assigned directly — even if it has a `schema` property.
170
+ */
171
+ function _shouldRecurseIntoExisting(value: any): boolean {
172
+ if (value === null || value === undefined) return false;
173
+ if (typeof value !== "object") return false;
174
+ if (Array.isArray(value)) return false;
175
+ // proxied sub-structures: installed by bindExtensionObject for nested structs
176
+ if (isProxy(value)) return true;
177
+ // plain objects (unlikely on the existing side but safe fallback)
178
+ const proto = Object.getPrototypeOf(value);
179
+ if (proto === Object.prototype || proto === null) return true;
180
+ return false;
181
+ }
182
+
183
+ function _shouldRecurseIntoNew(value: any): boolean {
184
+ if (value === null || value === undefined) return false;
185
+ if (typeof value !== "object") return false;
186
+ if (Array.isArray(value)) return false;
187
+ // plain objects: partial update literals like { field1: v1 }
188
+ const proto = Object.getPrototypeOf(value);
189
+ if (proto === Object.prototype || proto === null) return true;
190
+ // extension objects from constructExtensionObject have a schema
191
+ // but so do QualifiedName/LocalizedText — we disambiguate by
192
+ // checking the *existing* side with isProxy, not here.
193
+ // Instead, only recurse if it's a schema'd type AND has
194
+ // Extension-Object-like characteristics (constructor with schema.name)
195
+ if (value.schema !== undefined && value.constructor?.name !== "QualifiedName"
196
+ && value.constructor?.name !== "LocalizedText"
197
+ && value.constructor?.name !== "DiagnosticInfo") {
198
+ return true;
199
+ }
200
+ return false;
157
201
  }
158
202
 
159
203
  function _update_extension_object(extObject: any, partialObject1: any) {
160
204
  const keys = Object.keys(partialObject1);
161
205
  for (const prop of keys) {
162
- if (extObject[prop] instanceof Object) {
206
+ if (_shouldRecurseIntoExisting(extObject[prop]) && _shouldRecurseIntoNew(partialObject1[prop])) {
163
207
  _update_extension_object(extObject[prop], partialObject1[prop]);
164
208
  } else {
165
209
  if (isProxy(extObject)) {