• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

node-opcua / node-opcua / 25640124541

10 May 2026 09:22PM UTC coverage: 92.191% (-0.03%) from 92.22%
25640124541

push

github

erossignon
refactor(address-space): typed event emitters + biome conformance

Introduce a typed-event-emitter contract for the address-space class
hierarchy, and apply Biome 2.4.14 across the repo.

Typed events (address-space-base):
- Replace `declare class X extends EventEmitter` declarations with
  `interface X<T extends ListenerSignature<T>> extends ITypedEventEmitter<T>`
  for BaseNode, UAVariable, UAObject, UAMethod, UAObjectType,
  UAVariableType, UAReferenceType, UADataType, UAView.
- Add per-class event maps: BaseNodeEvents, UAVariableEvents,
  UAMethodEvents, etc., each extending the parent map.
- Introduce `ListenerSignature<L>` self-referential constraint and
  `ITypedEventEmitter<T>` so listeners are checked against the event
  map at compile time. `TypedEventEmitter` aliases Node's EventEmitter
  so listeners observe `this === <emitter>` (the previous
  composition-based wrapper broke that contract).
- Add ua_base_event_ex.ts (UABaseEventEx, UABaseEventEvents).

Tooling:
- Add biome.json (formatter + `useImportType` linter rule, lf line
  endings, 4-space indent, lineWidth 132, double quotes, no trailing
  commas) and @biomejs/biome 2.4.14 devDep.
- Set tsconfig.common.json `types: ["node"]`.

Mechanical biome cleanup across ~325 files in packages/* and tests:
- Convert type-only imports to `import type`.
- Use `node:` protocol for builtin imports (e.g. `node:events`).
- Reorganize/sort imports.
- Prefer `Number.isFinite` over global `isFinite`, `**` over
  `Math.pow`, prefix unused parameters with `_`.

18613 of 22121 branches covered (84.14%)

3162 of 3491 new or added lines in 174 files covered. (90.58%)

18 existing lines in 12 files now uncovered.

162931 of 176732 relevant lines covered (92.19%)

470530.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.06
/packages/node-opcua-address-space/src/extension_object_array_node.ts
1
/**
1✔
2
 * @module node-opcua-address-space.Private
1✔
3
 */
1✔
4

1✔
5
import type { UADataType, UADynamicVariableArray, UAObject, UAReferenceType, UAVariable } from "node-opcua-address-space-base";
1✔
6
import { assert } from "node-opcua-assert";
1✔
7
import { BrowseDirection, NodeClass } from "node-opcua-data-model";
1✔
8
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
1✔
9
import { ExtensionObject } from "node-opcua-extension-object";
1✔
10
import type { NodeId } from "node-opcua-nodeid";
1✔
11
import { DataType, Variant, VariantArrayType } from "node-opcua-variant";
1✔
12
import { UAVariableImpl } from "./ua_variable_impl";
1✔
13

1✔
14
const _doDebug = checkDebugFlag(__filename);
1✔
15
const _debugLog = make_debugLog(__filename);
1✔
16
const errorLog = make_errorLog(__filename);
1✔
17
const warningLog = make_warningLog(__filename);
1✔
18

1✔
19
/*
1✔
20
 * define a complex Variable containing a array of extension objects
1✔
21
 * each element of the array is also accessible as a component variable.
1✔
22
 *
1✔
23
 */
1✔
24

1✔
25
function getExtObjArrayNodeValue<T extends ExtensionObject>(this: UADynamicVariableArray<T>) {
2,045✔
26
    return new Variant({
2,045✔
27
        arrayType: VariantArrayType.Array,
2,045✔
28
        dataType: DataType.ExtensionObject,
2,045✔
29
        value: this.$$extensionObjectArray
2,045✔
30
    });
2,045✔
31
}
2,045✔
32

1✔
33
function removeElementByIndex<T extends ExtensionObject>(uaArrayVariableNode: UADynamicVariableArray<T>, elementIndex: number) {
2,817✔
34
    const _array = uaArrayVariableNode.$$extensionObjectArray;
2,817✔
35

2,817✔
36
    assert(typeof elementIndex === "number");
2,817✔
37

2,817✔
38
    const addressSpace = uaArrayVariableNode.addressSpace;
2,817✔
39
    const extObj = _array[elementIndex];
2,817✔
40
    const browseName = uaArrayVariableNode.$$getElementBrowseName(extObj, elementIndex);
2,817✔
41

2,817✔
42
    // remove element from global array (inefficient)
2,817✔
43
    uaArrayVariableNode.$$extensionObjectArray.splice(elementIndex, 1);
2,817✔
44
    if (uaArrayVariableNode.$$extensionObjectArray !== uaArrayVariableNode.$dataValue.value.value) {
2,817!
NEW
45
        //    throw new Error("internal error");
×
UNCOV
46
    }
×
47
    uaArrayVariableNode.touchValue();
2,817✔
48

2,817✔
49
    // remove matching component
2,817✔
50
    const node = uaArrayVariableNode.getComponentByName(browseName);
2,817✔
51
    if (!node) {
2,817!
52
        throw new Error(" cannot find component ");
×
53
    }
×
54

2,817✔
55
    const hasComponent = uaArrayVariableNode.addressSpace.findReferenceType("HasComponent")! as UAReferenceType;
2,817✔
56

2,817✔
57
    // remove the hasComponent reference toward node
2,817✔
58
    uaArrayVariableNode.removeReference({
2,817✔
59
        isForward: true,
2,817✔
60
        nodeId: node.nodeId,
2,817✔
61
        referenceType: hasComponent.nodeId
2,817✔
62
    });
2,817✔
63

2,817✔
64
    // now check if node has still some parent
2,817✔
65
    const parents = node.findReferencesEx("HasChild", BrowseDirection.Inverse);
2,817✔
66
    if (parents.length === 0) {
2,817✔
67
        addressSpace.deleteNode(node.nodeId);
2,816✔
68
    }
2,816✔
69
}
2,817✔
70

1✔
71
/**
1✔
72
 *
1✔
73
 * create a node Variable that contains a array of ExtensionObject of a given type
1✔
74
 */
1✔
75
export function createExtObjArrayNode<T extends ExtensionObject>(parentFolder: UAObject, options: any): UADynamicVariableArray<T> {
979✔
76
    assert(typeof options.variableType === "string");
979✔
77
    assert(typeof options.indexPropertyName === "string");
979✔
78

979✔
79
    const addressSpace = parentFolder.addressSpace;
979✔
80
    const namespace = parentFolder.namespace;
979✔
81

979✔
82
    const complexVariableType = addressSpace.findVariableType(options.complexVariableType);
979✔
83
    // c8 ignore next
979✔
84
    if (!complexVariableType) {
979!
85
        throw new Error("cannot find complex variable type");
×
86
    }
×
87
    assert(!complexVariableType.nodeId.isEmpty());
979✔
88

979✔
89
    const variableType = addressSpace.findVariableType(options.variableType);
979✔
90
    if (!variableType) {
979!
91
        throw new Error("cannot find variable Type");
×
92
    }
×
93
    assert(!variableType.nodeId.isEmpty());
979✔
94

979✔
95
    const structure = addressSpace.findDataType("Structure");
979✔
96
    assert(structure, "Structure Type not found: please check your nodeset file");
979✔
97

979✔
98
    const dataType = addressSpace.findDataType(variableType.dataType);
979✔
99

979✔
100
    // c8 ignore next
979✔
101
    if (!dataType) {
979!
102
        errorLog(variableType.toString());
×
103
        throw new Error("cannot find Data Type");
×
104
    }
×
105

979✔
106
    assert(dataType.isSubtypeOf(structure as any), "expecting a structure (= ExtensionObject) here ");
979✔
107

979✔
108
    const inner_options = {
979✔
109
        componentOf: parentFolder,
979✔
110

979✔
111
        browseName: options.browseName,
979✔
112
        dataType: dataType.nodeId,
979✔
113
        typeDefinition: complexVariableType.nodeId,
979✔
114
        value: { dataType: DataType.ExtensionObject, value: [], arrayType: VariantArrayType.Array },
979✔
115
        valueRank: 1
979✔
116
    };
979✔
117

979✔
118
    const uaArrayVariableNode = namespace.addVariable(inner_options) as UADynamicVariableArray<T>;
979✔
119

979✔
120
    bindExtObjArrayNode(uaArrayVariableNode, options.variableType, options.indexPropertyName);
979✔
121

979✔
122
    return uaArrayVariableNode;
979✔
123
}
979✔
124

1✔
125
function _getElementBrowseName<T extends ExtensionObject>(
5,643✔
126
    this: UADynamicVariableArray<T>,
5,643✔
127
    extObj: ExtensionObject,
5,643✔
128
    _index: number | number[]
5,643✔
129
) {
5,643✔
130
    const indexPropertyName1 = this.$$indexPropertyName;
5,643✔
131

5,643✔
132
    if (!Object.hasOwn(extObj, indexPropertyName1)) {
5,643!
133
        warningLog(" extension object does not have ", indexPropertyName1, extObj);
×
134
    }
×
135
    // assert(extObj.constructor === addressSpace.constructExtensionObject(dataType));
5,643✔
136
    assert(Object.hasOwn(extObj, indexPropertyName1));
5,643✔
137
    const browseName = (extObj as any)[indexPropertyName1].toString();
5,643✔
138
    return browseName;
5,643✔
139
}
5,643✔
140

1✔
141
export function bindExtObjArrayNode<T extends ExtensionObject>(
1,933✔
142
    uaArrayVariableNode: UADynamicVariableArray<T>,
1,933✔
143
    variableTypeNodeId: string | NodeId,
1,933✔
144
    indexPropertyName: string
1,933✔
145
): UAVariable {
1,933✔
146
    assert(uaArrayVariableNode.valueRank === 1, "expecting a one dimension array");
1,933✔
147

1,933✔
148
    const addressSpace = uaArrayVariableNode.addressSpace;
1,933✔
149

1,933✔
150
    const variableType = addressSpace.findVariableType(variableTypeNodeId);
1,933✔
151
    // c8 ignore next
1,933✔
152
    if (!variableType || variableType.nodeId.isEmpty()) {
1,933!
NEW
153
        throw new Error(`Cannot find VariableType ${variableTypeNodeId.toString()}`);
×
154
    }
×
155

1,933✔
156
    const structure = addressSpace.findDataType("Structure");
1,933✔
157
    // c8 ignore next
1,933✔
158
    if (!structure) {
1,933!
159
        throw new Error("Structure Type not found: please check your nodeset file");
×
160
    }
×
161

1,933✔
162
    let dataType = addressSpace.findDataType(variableType.dataType);
1,933✔
163
    // c8 ignore next
1,933✔
164
    if (!dataType) {
1,933!
NEW
165
        throw new Error(`Cannot find DataType ${variableType.dataType.toString()}`);
×
166
    }
×
167

1,933✔
168
    assert(dataType.isSubtypeOf(structure), "expecting a structure (= ExtensionObject) here ");
1,933✔
169

1,933✔
170
    assert(!uaArrayVariableNode.$$variableType, "uaArrayVariableNode has already been bound !");
1,933✔
171

1,933✔
172
    uaArrayVariableNode.$$variableType = variableType;
1,933✔
173

1,933✔
174
    // verify that an object with same doesn't already exist
1,933✔
175
    dataType = addressSpace.findDataType(variableType.dataType)! as UADataType;
1,933✔
176
    assert(dataType?.isSubtypeOf(structure), "expecting a structure (= ExtensionObject) here ");
1,933✔
177
    assert(!uaArrayVariableNode.$$extensionObjectArray, "UAVariable ExtensionObject array already bounded");
1,933✔
178
    uaArrayVariableNode.$$dataType = dataType;
1,933✔
179
    uaArrayVariableNode.$$extensionObjectArray = [];
1,933✔
180
    uaArrayVariableNode.$$indexPropertyName = indexPropertyName;
1,933✔
181
    uaArrayVariableNode.$$getElementBrowseName = _getElementBrowseName;
1,933✔
182
    uaArrayVariableNode.$dataValue.value.value = uaArrayVariableNode.$$extensionObjectArray;
1,933✔
183
    uaArrayVariableNode.$dataValue.value.arrayType = VariantArrayType.Array;
1,933✔
184

1,933✔
185
    const bindOptions: any = {
1,933✔
186
        get: getExtObjArrayNodeValue,
1,933✔
187
        set: undefined // readonly
1,933✔
188
    };
1,933✔
189
    // bind the readonly
1,933✔
190
    uaArrayVariableNode.bindVariable(bindOptions, true);
1,933✔
191
    return uaArrayVariableNode;
1,933✔
192
}
1,933✔
193

1✔
194
/**
1✔
195

1✔
196
 * add a new element in a ExtensionObject Array variable
1✔
197
 * @param options {Object}   data used to construct the underlying ExtensionObject
1✔
198
 * @param uaArrayVariableNode {UAVariable}
1✔
199
 * @return {UAVariable}
1✔
200
 *
1✔
201
 */
1✔
202
export function addElement<T extends ExtensionObject>(
2,824✔
203
    options: UAVariableImpl | ExtensionObject | Record<string, unknown>,
2,824✔
204
    uaArrayVariableNode: UADynamicVariableArray<T>
2,824✔
205
): UAVariable {
2,824✔
206
    assert(uaArrayVariableNode, " must provide an UAVariable containing the array");
2,824✔
207
    // verify that arr has been created correctly
2,824✔
208
    assert(
2,824✔
209
        !!uaArrayVariableNode.$$variableType && !!uaArrayVariableNode.$$dataType,
2,824✔
210
        "did you create the array Node with createExtObjArrayNode ?"
2,824✔
211
    );
2,824✔
212
    assert(uaArrayVariableNode.$$dataType.nodeClass === NodeClass.DataType);
2,824✔
213

2,824✔
214
    const addressSpace = uaArrayVariableNode.addressSpace;
2,824✔
215
    const Constructor = addressSpace.getExtensionObjectConstructor(uaArrayVariableNode.$$dataType);
2,824✔
216
    assert(Constructor instanceof Function);
2,824✔
217

2,824✔
218
    let extensionObject: T;
2,824✔
219
    let elVar = null;
2,824✔
220
    let browseName;
2,824✔
221

2,824✔
222
    if (options instanceof UAVariableImpl) {
2,824✔
223
        elVar = options;
2✔
224
        extensionObject = elVar.$extensionObject; // get shared extension object
2✔
225

2✔
226
        assert(
2✔
227
            extensionObject instanceof Constructor,
2✔
228
            "the provided variable must expose a Extension Object of the expected type "
2✔
229
        );
2✔
230
        // add a reference
2✔
231
        uaArrayVariableNode.addReference({
2✔
232
            isForward: true,
2✔
233
            nodeId: elVar.nodeId,
2✔
234
            referenceType: "HasComponent"
2✔
235
        });
2✔
236
        // xx elVar.bindExtensionObject();
2✔
237
    } else {
2,824✔
238
        if (options instanceof ExtensionObject) {
2,822✔
239
            // extension object has already been created
2,814✔
240
            extensionObject = options as T;
2,814✔
241
        } else {
2,822✔
242
            extensionObject = addressSpace.constructExtensionObject(uaArrayVariableNode.$$dataType, options) as T;
8✔
243
        }
8✔
244
        const index = uaArrayVariableNode.$$extensionObjectArray?.length || 0;
2,822✔
245
        browseName = uaArrayVariableNode.$$getElementBrowseName(extensionObject, index);
2,822✔
246
        elVar = uaArrayVariableNode.$$variableType.instantiate({
2,822✔
247
            browseName,
2,822✔
248
            componentOf: uaArrayVariableNode.nodeId,
2,822✔
249
            value: { dataType: DataType.ExtensionObject, value: extensionObject }
2,822✔
250
        }) as UAVariableImpl;
2,822✔
251
        elVar.bindExtensionObject(extensionObject, { force: true });
2,822✔
252
    }
2,822✔
253

2,824✔
254
    if (uaArrayVariableNode.$$extensionObjectArray !== uaArrayVariableNode.$dataValue.value.value) {
2,824!
NEW
255
        //    throw new Error("internal error");
×
UNCOV
256
    }
×
257
    // also add the value inside
2,824✔
258
    uaArrayVariableNode.$$extensionObjectArray.push(elVar.$extensionObject);
2,824✔
259
    uaArrayVariableNode.touchValue();
2,824✔
260

2,824✔
261
    return elVar;
2,824✔
262
}
2,824✔
263

1✔
264
/**
1✔
265
 *
1✔
266
 */
1✔
267
export function removeElement<T extends ExtensionObject>(
2,817✔
268
    uaArrayVariableNode: UADynamicVariableArray<T>,
2,817✔
269
    element: number | UAVariable | ((a: T) => boolean)
2,817✔
270
): void {
2,817✔
271
    assert(element, "removeElement: element must exist");
2,817✔
272
    const _array = uaArrayVariableNode.$$extensionObjectArray;
2,817✔
273

2,817✔
274
    // c8 ignore next
2,817✔
275
    if (_array.length === 0) {
2,817!
276
        throw new Error(" cannot remove an element from an empty array ");
×
277
    }
×
278
    let elementIndex = -1;
2,817✔
279
    if (typeof element === "number") {
2,817✔
280
        // find element by index
1✔
281
        elementIndex = element;
1✔
282
        assert(elementIndex >= 0 && elementIndex < _array.length);
1✔
283
    } else if (typeof element === "function") {
2,817✔
284
        // find element by functor
2,813✔
285
        elementIndex = _array.findIndex(element);
2,813✔
286
    } else if (element?.nodeClass) {
2,816✔
287
        // find element by name
3✔
288
        const browseNameToFind = element.browseName.name?.toString();
3✔
289
        elementIndex = _array.findIndex((obj: any, _i: number) => {
3✔
290
            const browseName = uaArrayVariableNode.$$getElementBrowseName(obj, elementIndex).toString();
3✔
291
            return browseName === browseNameToFind;
3✔
292
        });
3✔
293
    } else {
3!
294
        throw new Error("Unsupported anymore!!! please use a functor instead");
×
295
    }
×
296

2,817✔
297
    // c8 ignore next
2,817✔
298
    if (elementIndex < 0) {
2,817!
NEW
299
        throw new Error(`removeElement: cannot find element matching ${element.toString()}`);
×
300
    }
×
301
    return removeElementByIndex(uaArrayVariableNode, elementIndex);
2,817✔
302
}
2,817✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc