node-opcua-alias-name-client 2.176.0 → 2.178.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.
- package/README.md +37 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/read_alias_reference_types.d.ts +80 -0
- package/dist/read_alias_reference_types.js +156 -0
- package/dist/read_alias_reference_types.js.map +1 -0
- package/package.json +11 -10
- package/source/index.ts +5 -0
- package/source/read_alias_reference_types.ts +257 -0
package/README.md
CHANGED
|
@@ -101,6 +101,43 @@ for one, so a later `findAliasVerbose` after a `findAlias` makes no further roun
|
|
|
101
101
|
Construct one `ClientAliasSet` per session; call `invalidate()` if the Server's address
|
|
102
102
|
space changes underneath it.
|
|
103
103
|
|
|
104
|
+
## Recovering the ReferenceType of each target
|
|
105
|
+
|
|
106
|
+
`AliasNameDataType` and `AliasNameVerboseDataType` (clauses 7.2 and 7.3) carry the
|
|
107
|
+
referenced Nodes but **not** the ReferenceType of each Reference: a target linked with a
|
|
108
|
+
vendor subtype of `AliasFor` (clause 8.2) comes back indistinguishable from one linked
|
|
109
|
+
with `AliasFor` itself. That is a limitation of the DataTypes, not of any Server.
|
|
110
|
+
|
|
111
|
+
Most Clients never care — they want the NodeId. An **aggregator** re-publishing pulled
|
|
112
|
+
aliases does: recorded as plain `AliasFor`, a downstream `FindAlias` whose
|
|
113
|
+
`ReferenceTypeFilter` names the subtype can no longer be answered faithfully across the
|
|
114
|
+
aggregation hop. `readAliasReferenceTypes` recovers the actual ReferenceType per
|
|
115
|
+
(alias, target) by browsing the `AliasNameType` instance Nodes:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { readAliasReferenceTypes } from "node-opcua-alias-name-client";
|
|
119
|
+
|
|
120
|
+
const entries = await aliases.findAliasVerbose("%", { categoryNodeId });
|
|
121
|
+
const referenceTypes = await readAliasReferenceTypes(session, entries);
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
for (const { targetNodeId, referenceTypeId } of referenceTypes.get(entry) ?? []) {
|
|
124
|
+
republish(entry.aliasName, targetNodeId, referenceTypeId);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The map is keyed by the very elements passed in; an entry the Server could not resolve
|
|
130
|
+
(deleted since the find, for instance) is absent rather than guessed at. Callers that
|
|
131
|
+
already know the `AliasNameType` instance NodeIds can pass those instead of verbose
|
|
132
|
+
entries and skip the lookup step.
|
|
133
|
+
|
|
134
|
+
**Cost.** One `TranslateBrowsePaths` request per 1000 aliases to locate the instance
|
|
135
|
+
Nodes (skipped when NodeIds are passed), one `Browse` request per 1000, plus a
|
|
136
|
+
`BrowseNext` round trip per continuation the Server imposes — batched precisely so a
|
|
137
|
+
large category is a handful of round trips, never one per alias. Lower `maxNodesPerCall`
|
|
138
|
+
when a Server advertises tighter `OperationLimits` (OPC 10000-5 clause 6.3.11). If you
|
|
139
|
+
only resolve names to NodeIds, skip all of it.
|
|
140
|
+
|
|
104
141
|
## Nodes on another Server (Annex A)
|
|
105
142
|
|
|
106
143
|
A returned `ExpandedNodeId` may carry a non-zero `ServerIndex`, which says only "this Node
|
package/dist/index.d.ts
CHANGED
|
@@ -11,4 +11,5 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export { ALIASES_ROOT, type ClientAliasEntry, ClientAliasSet, type ClientAliasVerboseEntry, type FindAliasOptions, TAG_VARIABLES, TOPICS } from "./client_alias_set.js";
|
|
13
13
|
export { AliasNameCallError, AliasNameMethodNotSupportedError } from "./errors.js";
|
|
14
|
+
export { type AliasReferenceTypeEntry, type ReadAliasReferenceTypesOptions, readAliasReferenceTypes } from "./read_alias_reference_types.js";
|
|
14
15
|
export { LOCAL_SERVER_INDEX, ServerIndexResolver } from "./server_index_resolver.js";
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* `ServerIndex` of a returned `ExpandedNodeId` into a URI (Annex A).
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.ServerIndexResolver = exports.LOCAL_SERVER_INDEX = exports.AliasNameMethodNotSupportedError = exports.AliasNameCallError = exports.TOPICS = exports.TAG_VARIABLES = exports.ClientAliasSet = exports.ALIASES_ROOT = void 0;
|
|
14
|
+
exports.ServerIndexResolver = exports.LOCAL_SERVER_INDEX = exports.readAliasReferenceTypes = exports.AliasNameMethodNotSupportedError = exports.AliasNameCallError = exports.TOPICS = exports.TAG_VARIABLES = exports.ClientAliasSet = exports.ALIASES_ROOT = void 0;
|
|
15
15
|
var client_alias_set_js_1 = require("./client_alias_set.js");
|
|
16
16
|
Object.defineProperty(exports, "ALIASES_ROOT", { enumerable: true, get: function () { return client_alias_set_js_1.ALIASES_ROOT; } });
|
|
17
17
|
Object.defineProperty(exports, "ClientAliasSet", { enumerable: true, get: function () { return client_alias_set_js_1.ClientAliasSet; } });
|
|
@@ -20,6 +20,8 @@ Object.defineProperty(exports, "TOPICS", { enumerable: true, get: function () {
|
|
|
20
20
|
var errors_js_1 = require("./errors.js");
|
|
21
21
|
Object.defineProperty(exports, "AliasNameCallError", { enumerable: true, get: function () { return errors_js_1.AliasNameCallError; } });
|
|
22
22
|
Object.defineProperty(exports, "AliasNameMethodNotSupportedError", { enumerable: true, get: function () { return errors_js_1.AliasNameMethodNotSupportedError; } });
|
|
23
|
+
var read_alias_reference_types_js_1 = require("./read_alias_reference_types.js");
|
|
24
|
+
Object.defineProperty(exports, "readAliasReferenceTypes", { enumerable: true, get: function () { return read_alias_reference_types_js_1.readAliasReferenceTypes; } });
|
|
23
25
|
var server_index_resolver_js_1 = require("./server_index_resolver.js");
|
|
24
26
|
Object.defineProperty(exports, "LOCAL_SERVER_INDEX", { enumerable: true, get: function () { return server_index_resolver_js_1.LOCAL_SERVER_INDEX; } });
|
|
25
27
|
Object.defineProperty(exports, "ServerIndexResolver", { enumerable: true, get: function () { return server_index_resolver_js_1.ServerIndexResolver; } });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AAEH,6DAQ+B;AAP3B,mHAAA,YAAY,OAAA;AAEZ,qHAAA,cAAc,OAAA;AAGd,oHAAA,aAAa,OAAA;AACb,6GAAA,MAAM,OAAA;AAEV,yCAAmF;AAA1E,+GAAA,kBAAkB,OAAA;AAAE,6HAAA,gCAAgC,OAAA;AAC7D,uEAAqF;AAA5E,8HAAA,kBAAkB,OAAA;AAAE,+HAAA,mBAAmB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AAEH,6DAQ+B;AAP3B,mHAAA,YAAY,OAAA;AAEZ,qHAAA,cAAc,OAAA;AAGd,oHAAA,aAAa,OAAA;AACb,6GAAA,MAAM,OAAA;AAEV,yCAAmF;AAA1E,+GAAA,kBAAkB,OAAA;AAAE,6HAAA,gCAAgC,OAAA;AAC7D,iFAIyC;AADrC,wIAAA,uBAAuB,OAAA;AAE3B,uEAAqF;AAA5E,8HAAA,kBAAkB,OAAA;AAAE,+HAAA,mBAAmB,OAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-alias-name-client
|
|
3
|
+
*
|
|
4
|
+
* Recover the ReferenceType linking each AliasName to each of its targets.
|
|
5
|
+
*
|
|
6
|
+
* `AliasNameDataType` and `AliasNameVerboseDataType` (OPC 10000-17 clauses 7.2
|
|
7
|
+
* and 7.3) carry the referenced Nodes, but **not** the ReferenceType of each
|
|
8
|
+
* Reference: a target linked with a vendor subtype of `AliasFor` (clause 8.2)
|
|
9
|
+
* comes back from `FindAlias` / `FindAliasVerbose` indistinguishable from one
|
|
10
|
+
* linked with `AliasFor` itself. That is a limitation of the DataTypes, not of
|
|
11
|
+
* any particular Server.
|
|
12
|
+
*
|
|
13
|
+
* Most Clients never notice — they want the NodeId and the subtype carries no
|
|
14
|
+
* extra meaning for them. An **aggregating** Server does: re-published as plain
|
|
15
|
+
* `AliasFor`, a downstream `FindAlias` whose `ReferenceTypeFilter` names the
|
|
16
|
+
* subtype (clause 6.3.2 Table 3) cannot be answered faithfully across the
|
|
17
|
+
* aggregation hop. {@link readAliasReferenceTypes} closes that gap by going
|
|
18
|
+
* back to the address space: the `AliasNameType` instances are Nodes, and
|
|
19
|
+
* Browse reports the ReferenceType of every Reference they hold.
|
|
20
|
+
*/
|
|
21
|
+
import { type ExpandedNodeId, type NodeId } from "node-opcua-nodeid";
|
|
22
|
+
import type { IBasicSessionAsync2 } from "node-opcua-pseudo-session";
|
|
23
|
+
import type { ClientAliasVerboseEntry } from "./client_alias_set.js";
|
|
24
|
+
/** One (alias, target) link, with the ReferenceType the find Methods cannot report. */
|
|
25
|
+
export interface AliasReferenceTypeEntry {
|
|
26
|
+
/** The Node the alias names — matches one element of `referencedNodes`. */
|
|
27
|
+
targetNodeId: ExpandedNodeId;
|
|
28
|
+
/** `AliasFor` (i=23469) or the subtype the publisher actually used. */
|
|
29
|
+
referenceTypeId: NodeId;
|
|
30
|
+
}
|
|
31
|
+
export interface ReadAliasReferenceTypesOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Upper bound on the operations packed into one `TranslateBrowsePaths` or
|
|
34
|
+
* `Browse` request. The default of 1000 fits most Servers; lower it when a
|
|
35
|
+
* Server advertises tighter `OperationLimits` (OPC 10000-5 clause 6.3.11) —
|
|
36
|
+
* `Bad_TooManyOperations` is the symptom of exceeding them.
|
|
37
|
+
*/
|
|
38
|
+
maxNodesPerCall?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read the ReferenceType of every `AliasFor` Reference (and subtype) each alias
|
|
42
|
+
* holds, which is the one thing `FindAliasVerbose` cannot report.
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* const entries = await aliases.findAliasVerbose("%", { categoryNodeId });
|
|
46
|
+
* const referenceTypes = await readAliasReferenceTypes(session, entries);
|
|
47
|
+
* for (const entry of entries) {
|
|
48
|
+
* for (const { targetNodeId, referenceTypeId } of referenceTypes.get(entry) ?? []) {
|
|
49
|
+
* republish(entry.aliasName, targetNodeId, referenceTypeId);
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* **When to use it.** Only when the ReferenceType itself matters — typically an
|
|
55
|
+
* aggregator that must re-publish pulled aliases with reference-type fidelity,
|
|
56
|
+
* so a downstream `ReferenceTypeFilter` naming an `AliasFor` subtype keeps
|
|
57
|
+
* working across the hop. A Client that only resolves names to NodeIds gets
|
|
58
|
+
* nothing from it and should not pay for it.
|
|
59
|
+
*
|
|
60
|
+
* **What it costs.** On top of the find call already made: one
|
|
61
|
+
* `TranslateBrowsePaths` request per {@link ReadAliasReferenceTypesOptions.maxNodesPerCall}
|
|
62
|
+
* aliases to locate the `AliasNameType` instance Nodes (skipped when NodeIds
|
|
63
|
+
* are passed directly), then one `Browse` request per batch, plus one
|
|
64
|
+
* `BrowseNext` round trip per continuation the Server imposes. The requests are
|
|
65
|
+
* batched precisely so a large category does **not** become one round trip per
|
|
66
|
+
* alias: 1000 aliases resolve in two or three round trips, not 1000.
|
|
67
|
+
*
|
|
68
|
+
* The result is keyed by the **very elements passed in** — look entries up with
|
|
69
|
+
* the objects from the input array, not with reconstructed equals. An input the
|
|
70
|
+
* Server could not resolve (the alias was deleted since the find, a NodeId that
|
|
71
|
+
* no longer browses) is absent from the map rather than present with a guess.
|
|
72
|
+
*
|
|
73
|
+
* @param session any `IBasicSessionAsync2` — a remote `ClientSession` or an
|
|
74
|
+
* in-process `PseudoSession`, as everywhere in this package
|
|
75
|
+
* @param aliases either the entries of a `findAliasVerbose` call, or the
|
|
76
|
+
* NodeIds of `AliasNameType` instance Nodes when the caller already knows
|
|
77
|
+
* them (from browsing a category, or from its own bookkeeping)
|
|
78
|
+
*/
|
|
79
|
+
export declare function readAliasReferenceTypes(session: IBasicSessionAsync2, aliases: NodeId[], options?: ReadAliasReferenceTypesOptions): Promise<Map<NodeId, AliasReferenceTypeEntry[]>>;
|
|
80
|
+
export declare function readAliasReferenceTypes(session: IBasicSessionAsync2, aliases: ClientAliasVerboseEntry[], options?: ReadAliasReferenceTypesOptions): Promise<Map<ClientAliasVerboseEntry, AliasReferenceTypeEntry[]>>;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module node-opcua-alias-name-client
|
|
4
|
+
*
|
|
5
|
+
* Recover the ReferenceType linking each AliasName to each of its targets.
|
|
6
|
+
*
|
|
7
|
+
* `AliasNameDataType` and `AliasNameVerboseDataType` (OPC 10000-17 clauses 7.2
|
|
8
|
+
* and 7.3) carry the referenced Nodes, but **not** the ReferenceType of each
|
|
9
|
+
* Reference: a target linked with a vendor subtype of `AliasFor` (clause 8.2)
|
|
10
|
+
* comes back from `FindAlias` / `FindAliasVerbose` indistinguishable from one
|
|
11
|
+
* linked with `AliasFor` itself. That is a limitation of the DataTypes, not of
|
|
12
|
+
* any particular Server.
|
|
13
|
+
*
|
|
14
|
+
* Most Clients never notice — they want the NodeId and the subtype carries no
|
|
15
|
+
* extra meaning for them. An **aggregating** Server does: re-published as plain
|
|
16
|
+
* `AliasFor`, a downstream `FindAlias` whose `ReferenceTypeFilter` names the
|
|
17
|
+
* subtype (clause 6.3.2 Table 3) cannot be answered faithfully across the
|
|
18
|
+
* aggregation hop. {@link readAliasReferenceTypes} closes that gap by going
|
|
19
|
+
* back to the address space: the `AliasNameType` instances are Nodes, and
|
|
20
|
+
* Browse reports the ReferenceType of every Reference they hold.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.readAliasReferenceTypes = readAliasReferenceTypes;
|
|
24
|
+
const node_opcua_constants_1 = require("node-opcua-constants");
|
|
25
|
+
const node_opcua_data_model_1 = require("node-opcua-data-model");
|
|
26
|
+
const node_opcua_nodeid_1 = require("node-opcua-nodeid");
|
|
27
|
+
const node_opcua_service_translate_browse_path_1 = require("node-opcua-service-translate-browse-path");
|
|
28
|
+
/** `AliasFor` (i=23469), the base ReferenceType of every alias link (clause 8.2). */
|
|
29
|
+
const ALIAS_FOR = (0, node_opcua_nodeid_1.resolveNodeId)(node_opcua_constants_1.ReferenceTypeIds.AliasFor);
|
|
30
|
+
const HIERARCHICAL_REFERENCES = (0, node_opcua_nodeid_1.resolveNodeId)(node_opcua_constants_1.ReferenceTypeIds.HierarchicalReferences);
|
|
31
|
+
const REFERENCE_TYPE_RESULT_MASK = (0, node_opcua_data_model_1.makeResultMask)("ReferenceType");
|
|
32
|
+
const DEFAULT_MAX_NODES_PER_CALL = 1000;
|
|
33
|
+
async function readAliasReferenceTypes(session, aliases, options) {
|
|
34
|
+
const maxNodesPerCall = Math.max(1, options?.maxNodesPerCall ?? DEFAULT_MAX_NODES_PER_CALL);
|
|
35
|
+
const result = new Map();
|
|
36
|
+
if (aliases.length === 0) {
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
const aliasNodeIds = aliases[0] instanceof node_opcua_nodeid_1.NodeId
|
|
40
|
+
? aliases
|
|
41
|
+
: await resolveAliasNodeIds(session, aliases, maxNodesPerCall);
|
|
42
|
+
const targets = await browseAliasTargets(session, aliasNodeIds, maxNodesPerCall);
|
|
43
|
+
aliases.forEach((key, index) => {
|
|
44
|
+
const entries = targets[index];
|
|
45
|
+
if (entries) {
|
|
46
|
+
result.set(key, entries);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Locate the `AliasNameType` instance Node behind each verbose entry.
|
|
53
|
+
*
|
|
54
|
+
* `FindAliasVerbose` names the category that held the alias
|
|
55
|
+
* (`aliasNameCategoryId`, clause 7.3) but not the alias Node itself, so the
|
|
56
|
+
* Node is found by translating one browse path below the category. The
|
|
57
|
+
* `RelativePath` is built element by element rather than parsed from a string,
|
|
58
|
+
* so an alias name containing `/`, `&` or the other OPC 10000-4 Annex A
|
|
59
|
+
* reserved characters needs no escaping. The QualifiedName match is exact —
|
|
60
|
+
* the entry's `namespaceIndex` is the one the Server reported, so this is the
|
|
61
|
+
* one place the namespace of an AliasName is **not** ignored.
|
|
62
|
+
*
|
|
63
|
+
* Unresolvable entries yield `null`, keeping positions aligned with the input.
|
|
64
|
+
*/
|
|
65
|
+
async function resolveAliasNodeIds(session, entries, maxNodesPerCall) {
|
|
66
|
+
const aliasNodeIds = new Array(entries.length).fill(null);
|
|
67
|
+
for (let offset = 0; offset < entries.length; offset += maxNodesPerCall) {
|
|
68
|
+
const chunk = entries.slice(offset, offset + maxNodesPerCall);
|
|
69
|
+
const results = await session.translateBrowsePath(chunk.map((entry) => new node_opcua_service_translate_browse_path_1.BrowsePath({
|
|
70
|
+
startingNode: entry.aliasNameCategoryId,
|
|
71
|
+
relativePath: {
|
|
72
|
+
elements: [
|
|
73
|
+
{
|
|
74
|
+
referenceTypeId: HIERARCHICAL_REFERENCES,
|
|
75
|
+
isInverse: false,
|
|
76
|
+
includeSubtypes: true,
|
|
77
|
+
targetName: { namespaceIndex: entry.namespaceIndex, name: entry.aliasName }
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
})));
|
|
82
|
+
results.forEach((browsePathResult, index) => {
|
|
83
|
+
if (browsePathResult.statusCode.isGood() && browsePathResult.targets?.length) {
|
|
84
|
+
// the targetId of a local Node is an ExpandedNodeId with
|
|
85
|
+
// serverIndex 0, usable as a NodeId as-is
|
|
86
|
+
aliasNodeIds[offset + index] = browsePathResult.targets[0].targetId;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return aliasNodeIds;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Browse the forward `AliasFor` References (subtypes included) of many alias
|
|
94
|
+
* Nodes in as few requests as possible, following every continuation point.
|
|
95
|
+
*
|
|
96
|
+
* Positions align with the input; `null` marks a Node that could not be
|
|
97
|
+
* resolved upstream or whose Browse did not complete — for the fidelity use
|
|
98
|
+
* case, an absent answer beats a truncated one presented as complete.
|
|
99
|
+
*/
|
|
100
|
+
async function browseAliasTargets(session, aliasNodeIds, maxNodesPerCall) {
|
|
101
|
+
const targets = new Array(aliasNodeIds.length).fill(null);
|
|
102
|
+
const toBrowse = [];
|
|
103
|
+
aliasNodeIds.forEach((nodeId, index) => {
|
|
104
|
+
if (nodeId) {
|
|
105
|
+
toBrowse.push({ index, nodeId });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const toEntry = (reference) => ({
|
|
109
|
+
targetNodeId: reference.nodeId,
|
|
110
|
+
referenceTypeId: reference.referenceTypeId
|
|
111
|
+
});
|
|
112
|
+
for (let offset = 0; offset < toBrowse.length; offset += maxNodesPerCall) {
|
|
113
|
+
const chunk = toBrowse.slice(offset, offset + maxNodesPerCall);
|
|
114
|
+
const browseResults = await session.browse(chunk.map(({ nodeId }) => ({
|
|
115
|
+
nodeId,
|
|
116
|
+
browseDirection: node_opcua_data_model_1.BrowseDirection.Forward,
|
|
117
|
+
referenceTypeId: ALIAS_FOR,
|
|
118
|
+
includeSubtypes: true,
|
|
119
|
+
nodeClassMask: 0,
|
|
120
|
+
resultMask: REFERENCE_TYPE_RESULT_MASK
|
|
121
|
+
})));
|
|
122
|
+
// continuations, batched too: one BrowseNext round trip serves every
|
|
123
|
+
// Node of the chunk that was truncated, however many there are
|
|
124
|
+
let pending = [];
|
|
125
|
+
browseResults.forEach((browseResult, chunkIndex) => {
|
|
126
|
+
const { index } = chunk[chunkIndex];
|
|
127
|
+
if (!browseResult.statusCode.isGood()) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
targets[index] = (browseResult.references ?? []).map(toEntry);
|
|
131
|
+
if (browseResult.continuationPoint?.length) {
|
|
132
|
+
pending.push({ index, continuationPoint: browseResult.continuationPoint });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
while (pending.length > 0) {
|
|
136
|
+
const nextResults = await session.browseNext(pending.map(({ continuationPoint }) => continuationPoint), false);
|
|
137
|
+
const stillPending = [];
|
|
138
|
+
nextResults.forEach((browseResult, pendingIndex) => {
|
|
139
|
+
const { index } = pending[pendingIndex];
|
|
140
|
+
if (!browseResult.statusCode.isGood()) {
|
|
141
|
+
// incomplete: withdraw the partial answer rather than let it
|
|
142
|
+
// pass for the whole truth
|
|
143
|
+
targets[index] = null;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
targets[index]?.push(...(browseResult.references ?? []).map(toEntry));
|
|
147
|
+
if (browseResult.continuationPoint?.length) {
|
|
148
|
+
stillPending.push({ index, continuationPoint: browseResult.continuationPoint });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
pending = stillPending;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return targets;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=read_alias_reference_types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read_alias_reference_types.js","sourceRoot":"","sources":["../source/read_alias_reference_types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-opcua-alias-name-client",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.178.0",
|
|
4
4
|
"description": "pure nodejs OPCUA SDK - client-side alias names (OPC 10000-17)",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsc -b",
|
|
@@ -18,17 +18,17 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"node-opcua-constants": "2.176.0",
|
|
21
|
-
"node-opcua-data-model": "2.
|
|
22
|
-
"node-opcua-nodeid": "2.
|
|
23
|
-
"node-opcua-pseudo-session": "2.
|
|
24
|
-
"node-opcua-service-translate-browse-path": "2.
|
|
21
|
+
"node-opcua-data-model": "2.177.0",
|
|
22
|
+
"node-opcua-nodeid": "2.177.0",
|
|
23
|
+
"node-opcua-pseudo-session": "2.178.0",
|
|
24
|
+
"node-opcua-service-translate-browse-path": "2.177.0",
|
|
25
25
|
"node-opcua-status-code": "2.176.0",
|
|
26
|
-
"node-opcua-types": "2.
|
|
27
|
-
"node-opcua-variant": "2.
|
|
26
|
+
"node-opcua-types": "2.177.0",
|
|
27
|
+
"node-opcua-variant": "2.177.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"node-opcua-address-space": "2.
|
|
31
|
-
"node-opcua-alias-name-server": "2.
|
|
30
|
+
"node-opcua-address-space": "2.178.0",
|
|
31
|
+
"node-opcua-alias-name-server": "2.178.0",
|
|
32
32
|
"node-opcua-leak-detector": "2.175.6",
|
|
33
33
|
"node-opcua-nodesets": "2.175.3"
|
|
34
34
|
},
|
|
@@ -50,5 +50,6 @@
|
|
|
50
50
|
"files": [
|
|
51
51
|
"dist",
|
|
52
52
|
"source"
|
|
53
|
-
]
|
|
53
|
+
],
|
|
54
|
+
"gitHead": "d0e850e5f7df80e14ceb6d8e092e793472965301"
|
|
54
55
|
}
|
package/source/index.ts
CHANGED
|
@@ -20,4 +20,9 @@ export {
|
|
|
20
20
|
TOPICS
|
|
21
21
|
} from "./client_alias_set.js";
|
|
22
22
|
export { AliasNameCallError, AliasNameMethodNotSupportedError } from "./errors.js";
|
|
23
|
+
export {
|
|
24
|
+
type AliasReferenceTypeEntry,
|
|
25
|
+
type ReadAliasReferenceTypesOptions,
|
|
26
|
+
readAliasReferenceTypes
|
|
27
|
+
} from "./read_alias_reference_types.js";
|
|
23
28
|
export { LOCAL_SERVER_INDEX, ServerIndexResolver } from "./server_index_resolver.js";
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-alias-name-client
|
|
3
|
+
*
|
|
4
|
+
* Recover the ReferenceType linking each AliasName to each of its targets.
|
|
5
|
+
*
|
|
6
|
+
* `AliasNameDataType` and `AliasNameVerboseDataType` (OPC 10000-17 clauses 7.2
|
|
7
|
+
* and 7.3) carry the referenced Nodes, but **not** the ReferenceType of each
|
|
8
|
+
* Reference: a target linked with a vendor subtype of `AliasFor` (clause 8.2)
|
|
9
|
+
* comes back from `FindAlias` / `FindAliasVerbose` indistinguishable from one
|
|
10
|
+
* linked with `AliasFor` itself. That is a limitation of the DataTypes, not of
|
|
11
|
+
* any particular Server.
|
|
12
|
+
*
|
|
13
|
+
* Most Clients never notice — they want the NodeId and the subtype carries no
|
|
14
|
+
* extra meaning for them. An **aggregating** Server does: re-published as plain
|
|
15
|
+
* `AliasFor`, a downstream `FindAlias` whose `ReferenceTypeFilter` names the
|
|
16
|
+
* subtype (clause 6.3.2 Table 3) cannot be answered faithfully across the
|
|
17
|
+
* aggregation hop. {@link readAliasReferenceTypes} closes that gap by going
|
|
18
|
+
* back to the address space: the `AliasNameType` instances are Nodes, and
|
|
19
|
+
* Browse reports the ReferenceType of every Reference they hold.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { ReferenceTypeIds } from "node-opcua-constants";
|
|
23
|
+
import { BrowseDirection, makeResultMask } from "node-opcua-data-model";
|
|
24
|
+
import { type ExpandedNodeId, type NodeId, NodeId as NodeIdClass, resolveNodeId } from "node-opcua-nodeid";
|
|
25
|
+
import type { IBasicSessionAsync2 } from "node-opcua-pseudo-session";
|
|
26
|
+
import { BrowsePath } from "node-opcua-service-translate-browse-path";
|
|
27
|
+
import type { ReferenceDescription } from "node-opcua-types";
|
|
28
|
+
import type { ClientAliasVerboseEntry } from "./client_alias_set.js";
|
|
29
|
+
|
|
30
|
+
/** `AliasFor` (i=23469), the base ReferenceType of every alias link (clause 8.2). */
|
|
31
|
+
const ALIAS_FOR: NodeId = resolveNodeId(ReferenceTypeIds.AliasFor);
|
|
32
|
+
const HIERARCHICAL_REFERENCES: NodeId = resolveNodeId(ReferenceTypeIds.HierarchicalReferences);
|
|
33
|
+
const REFERENCE_TYPE_RESULT_MASK = makeResultMask("ReferenceType");
|
|
34
|
+
const DEFAULT_MAX_NODES_PER_CALL = 1000;
|
|
35
|
+
|
|
36
|
+
/** One (alias, target) link, with the ReferenceType the find Methods cannot report. */
|
|
37
|
+
export interface AliasReferenceTypeEntry {
|
|
38
|
+
/** The Node the alias names — matches one element of `referencedNodes`. */
|
|
39
|
+
targetNodeId: ExpandedNodeId;
|
|
40
|
+
/** `AliasFor` (i=23469) or the subtype the publisher actually used. */
|
|
41
|
+
referenceTypeId: NodeId;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ReadAliasReferenceTypesOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Upper bound on the operations packed into one `TranslateBrowsePaths` or
|
|
47
|
+
* `Browse` request. The default of 1000 fits most Servers; lower it when a
|
|
48
|
+
* Server advertises tighter `OperationLimits` (OPC 10000-5 clause 6.3.11) —
|
|
49
|
+
* `Bad_TooManyOperations` is the symptom of exceeding them.
|
|
50
|
+
*/
|
|
51
|
+
maxNodesPerCall?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read the ReferenceType of every `AliasFor` Reference (and subtype) each alias
|
|
56
|
+
* holds, which is the one thing `FindAliasVerbose` cannot report.
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* const entries = await aliases.findAliasVerbose("%", { categoryNodeId });
|
|
60
|
+
* const referenceTypes = await readAliasReferenceTypes(session, entries);
|
|
61
|
+
* for (const entry of entries) {
|
|
62
|
+
* for (const { targetNodeId, referenceTypeId } of referenceTypes.get(entry) ?? []) {
|
|
63
|
+
* republish(entry.aliasName, targetNodeId, referenceTypeId);
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* **When to use it.** Only when the ReferenceType itself matters — typically an
|
|
69
|
+
* aggregator that must re-publish pulled aliases with reference-type fidelity,
|
|
70
|
+
* so a downstream `ReferenceTypeFilter` naming an `AliasFor` subtype keeps
|
|
71
|
+
* working across the hop. A Client that only resolves names to NodeIds gets
|
|
72
|
+
* nothing from it and should not pay for it.
|
|
73
|
+
*
|
|
74
|
+
* **What it costs.** On top of the find call already made: one
|
|
75
|
+
* `TranslateBrowsePaths` request per {@link ReadAliasReferenceTypesOptions.maxNodesPerCall}
|
|
76
|
+
* aliases to locate the `AliasNameType` instance Nodes (skipped when NodeIds
|
|
77
|
+
* are passed directly), then one `Browse` request per batch, plus one
|
|
78
|
+
* `BrowseNext` round trip per continuation the Server imposes. The requests are
|
|
79
|
+
* batched precisely so a large category does **not** become one round trip per
|
|
80
|
+
* alias: 1000 aliases resolve in two or three round trips, not 1000.
|
|
81
|
+
*
|
|
82
|
+
* The result is keyed by the **very elements passed in** — look entries up with
|
|
83
|
+
* the objects from the input array, not with reconstructed equals. An input the
|
|
84
|
+
* Server could not resolve (the alias was deleted since the find, a NodeId that
|
|
85
|
+
* no longer browses) is absent from the map rather than present with a guess.
|
|
86
|
+
*
|
|
87
|
+
* @param session any `IBasicSessionAsync2` — a remote `ClientSession` or an
|
|
88
|
+
* in-process `PseudoSession`, as everywhere in this package
|
|
89
|
+
* @param aliases either the entries of a `findAliasVerbose` call, or the
|
|
90
|
+
* NodeIds of `AliasNameType` instance Nodes when the caller already knows
|
|
91
|
+
* them (from browsing a category, or from its own bookkeeping)
|
|
92
|
+
*/
|
|
93
|
+
export async function readAliasReferenceTypes(
|
|
94
|
+
session: IBasicSessionAsync2,
|
|
95
|
+
aliases: NodeId[],
|
|
96
|
+
options?: ReadAliasReferenceTypesOptions
|
|
97
|
+
): Promise<Map<NodeId, AliasReferenceTypeEntry[]>>;
|
|
98
|
+
export async function readAliasReferenceTypes(
|
|
99
|
+
session: IBasicSessionAsync2,
|
|
100
|
+
aliases: ClientAliasVerboseEntry[],
|
|
101
|
+
options?: ReadAliasReferenceTypesOptions
|
|
102
|
+
): Promise<Map<ClientAliasVerboseEntry, AliasReferenceTypeEntry[]>>;
|
|
103
|
+
export async function readAliasReferenceTypes(
|
|
104
|
+
session: IBasicSessionAsync2,
|
|
105
|
+
aliases: NodeId[] | ClientAliasVerboseEntry[],
|
|
106
|
+
options?: ReadAliasReferenceTypesOptions
|
|
107
|
+
): Promise<Map<NodeId | ClientAliasVerboseEntry, AliasReferenceTypeEntry[]>> {
|
|
108
|
+
const maxNodesPerCall = Math.max(1, options?.maxNodesPerCall ?? DEFAULT_MAX_NODES_PER_CALL);
|
|
109
|
+
const result = new Map<NodeId | ClientAliasVerboseEntry, AliasReferenceTypeEntry[]>();
|
|
110
|
+
if (aliases.length === 0) {
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const aliasNodeIds: (NodeId | null)[] =
|
|
115
|
+
aliases[0] instanceof NodeIdClass
|
|
116
|
+
? (aliases as NodeId[])
|
|
117
|
+
: await resolveAliasNodeIds(session, aliases as ClientAliasVerboseEntry[], maxNodesPerCall);
|
|
118
|
+
|
|
119
|
+
const targets = await browseAliasTargets(session, aliasNodeIds, maxNodesPerCall);
|
|
120
|
+
aliases.forEach((key: NodeId | ClientAliasVerboseEntry, index: number) => {
|
|
121
|
+
const entries = targets[index];
|
|
122
|
+
if (entries) {
|
|
123
|
+
result.set(key, entries);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Locate the `AliasNameType` instance Node behind each verbose entry.
|
|
131
|
+
*
|
|
132
|
+
* `FindAliasVerbose` names the category that held the alias
|
|
133
|
+
* (`aliasNameCategoryId`, clause 7.3) but not the alias Node itself, so the
|
|
134
|
+
* Node is found by translating one browse path below the category. The
|
|
135
|
+
* `RelativePath` is built element by element rather than parsed from a string,
|
|
136
|
+
* so an alias name containing `/`, `&` or the other OPC 10000-4 Annex A
|
|
137
|
+
* reserved characters needs no escaping. The QualifiedName match is exact —
|
|
138
|
+
* the entry's `namespaceIndex` is the one the Server reported, so this is the
|
|
139
|
+
* one place the namespace of an AliasName is **not** ignored.
|
|
140
|
+
*
|
|
141
|
+
* Unresolvable entries yield `null`, keeping positions aligned with the input.
|
|
142
|
+
*/
|
|
143
|
+
async function resolveAliasNodeIds(
|
|
144
|
+
session: IBasicSessionAsync2,
|
|
145
|
+
entries: ClientAliasVerboseEntry[],
|
|
146
|
+
maxNodesPerCall: number
|
|
147
|
+
): Promise<(NodeId | null)[]> {
|
|
148
|
+
const aliasNodeIds: (NodeId | null)[] = new Array(entries.length).fill(null);
|
|
149
|
+
for (let offset = 0; offset < entries.length; offset += maxNodesPerCall) {
|
|
150
|
+
const chunk = entries.slice(offset, offset + maxNodesPerCall);
|
|
151
|
+
const results = await session.translateBrowsePath(
|
|
152
|
+
chunk.map(
|
|
153
|
+
(entry) =>
|
|
154
|
+
new BrowsePath({
|
|
155
|
+
startingNode: entry.aliasNameCategoryId,
|
|
156
|
+
relativePath: {
|
|
157
|
+
elements: [
|
|
158
|
+
{
|
|
159
|
+
referenceTypeId: HIERARCHICAL_REFERENCES,
|
|
160
|
+
isInverse: false,
|
|
161
|
+
includeSubtypes: true,
|
|
162
|
+
targetName: { namespaceIndex: entry.namespaceIndex, name: entry.aliasName }
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
results.forEach((browsePathResult, index) => {
|
|
170
|
+
if (browsePathResult.statusCode.isGood() && browsePathResult.targets?.length) {
|
|
171
|
+
// the targetId of a local Node is an ExpandedNodeId with
|
|
172
|
+
// serverIndex 0, usable as a NodeId as-is
|
|
173
|
+
aliasNodeIds[offset + index] = browsePathResult.targets[0].targetId;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return aliasNodeIds;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Browse the forward `AliasFor` References (subtypes included) of many alias
|
|
182
|
+
* Nodes in as few requests as possible, following every continuation point.
|
|
183
|
+
*
|
|
184
|
+
* Positions align with the input; `null` marks a Node that could not be
|
|
185
|
+
* resolved upstream or whose Browse did not complete — for the fidelity use
|
|
186
|
+
* case, an absent answer beats a truncated one presented as complete.
|
|
187
|
+
*/
|
|
188
|
+
async function browseAliasTargets(
|
|
189
|
+
session: IBasicSessionAsync2,
|
|
190
|
+
aliasNodeIds: (NodeId | null)[],
|
|
191
|
+
maxNodesPerCall: number
|
|
192
|
+
): Promise<(AliasReferenceTypeEntry[] | null)[]> {
|
|
193
|
+
const targets: (AliasReferenceTypeEntry[] | null)[] = new Array(aliasNodeIds.length).fill(null);
|
|
194
|
+
|
|
195
|
+
const toBrowse: { index: number; nodeId: NodeId }[] = [];
|
|
196
|
+
aliasNodeIds.forEach((nodeId, index) => {
|
|
197
|
+
if (nodeId) {
|
|
198
|
+
toBrowse.push({ index, nodeId });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const toEntry = (reference: ReferenceDescription): AliasReferenceTypeEntry => ({
|
|
203
|
+
targetNodeId: reference.nodeId,
|
|
204
|
+
referenceTypeId: reference.referenceTypeId
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
for (let offset = 0; offset < toBrowse.length; offset += maxNodesPerCall) {
|
|
208
|
+
const chunk = toBrowse.slice(offset, offset + maxNodesPerCall);
|
|
209
|
+
const browseResults = await session.browse(
|
|
210
|
+
chunk.map(({ nodeId }) => ({
|
|
211
|
+
nodeId,
|
|
212
|
+
browseDirection: BrowseDirection.Forward,
|
|
213
|
+
referenceTypeId: ALIAS_FOR,
|
|
214
|
+
includeSubtypes: true,
|
|
215
|
+
nodeClassMask: 0,
|
|
216
|
+
resultMask: REFERENCE_TYPE_RESULT_MASK
|
|
217
|
+
}))
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// continuations, batched too: one BrowseNext round trip serves every
|
|
221
|
+
// Node of the chunk that was truncated, however many there are
|
|
222
|
+
let pending: { index: number; continuationPoint: Buffer }[] = [];
|
|
223
|
+
browseResults.forEach((browseResult, chunkIndex) => {
|
|
224
|
+
const { index } = chunk[chunkIndex];
|
|
225
|
+
if (!browseResult.statusCode.isGood()) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
targets[index] = (browseResult.references ?? []).map(toEntry);
|
|
229
|
+
if (browseResult.continuationPoint?.length) {
|
|
230
|
+
pending.push({ index, continuationPoint: browseResult.continuationPoint });
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
while (pending.length > 0) {
|
|
235
|
+
const nextResults = await session.browseNext(
|
|
236
|
+
pending.map(({ continuationPoint }) => continuationPoint),
|
|
237
|
+
false
|
|
238
|
+
);
|
|
239
|
+
const stillPending: typeof pending = [];
|
|
240
|
+
nextResults.forEach((browseResult, pendingIndex) => {
|
|
241
|
+
const { index } = pending[pendingIndex];
|
|
242
|
+
if (!browseResult.statusCode.isGood()) {
|
|
243
|
+
// incomplete: withdraw the partial answer rather than let it
|
|
244
|
+
// pass for the whole truth
|
|
245
|
+
targets[index] = null;
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
targets[index]?.push(...(browseResult.references ?? []).map(toEntry));
|
|
249
|
+
if (browseResult.continuationPoint?.length) {
|
|
250
|
+
stillPending.push({ index, continuationPoint: browseResult.continuationPoint });
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
pending = stillPending;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return targets;
|
|
257
|
+
}
|