node-opcua-alias-name-client 2.176.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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022-2024 Sterfive SAS - 833264583 RCS ORLEANS - France (https://www.sterfive.com)
4
+
5
+ Copyright (c) 2014-2022 Etienne Rossignon
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
8
+ this software and associated documentation files (the "Software"), to deal in
9
+ the Software without restriction, including without limitation the rights to
10
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11
+ the Software, and to permit persons to whom the Software is furnished to do so,
12
+ subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # node-opcua-alias-name-client
2
+
3
+ Client-side OPC UA **AliasNames** (OPC 10000-17).
4
+
5
+ These packages let a Server publish **its own** AliasNames and let a Client resolve
6
+ them. They do **not** aggregate AliasNames collected from other Servers: Annex B
7
+ (aggregating Server) and Annex C (GDS) of OPC 10000-17, and the Annex D PubSub change
8
+ notification, are out of scope. Anything that requires knowing about more than one
9
+ Server is not implemented here.
10
+
11
+ see http://node-opcua.github.io/
12
+
13
+ ## What it is for
14
+
15
+ Part 17 is, in effect, DNS for an address space. Instead of configuring a Client with a
16
+ raw NodeId that changes whenever the Server is re-engineered, ask for the plant tag:
17
+
18
+ ```ts
19
+ import { ClientAliasSet } from "node-opcua-alias-name-client";
20
+
21
+ const aliases = new ClientAliasSet(session);
22
+ const [entry] = await aliases.findAlias("TI101");
23
+ const nodeId = entry.referencedNodes[0];
24
+ ```
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install node-opcua-alias-name-client
30
+ ```
31
+
32
+ ## Works with any session
33
+
34
+ `ClientAliasSet` takes an `IBasicSessionAsync2`, so the same code drives a remote
35
+ `ClientSession` and an in-process `PseudoSession`:
36
+
37
+ ```ts
38
+ const aliases = new ClientAliasSet(new PseudoSession(addressSpace));
39
+ ```
40
+
41
+ That is what lets this package be tested without a transport, and what lets a
42
+ Server-side tool resolve its own aliases through the API a Client uses.
43
+
44
+ ## Searching
45
+
46
+ The argument is an OPC 10000-4 `Like` pattern — `%` is any run of characters, `_` exactly
47
+ one, `[abc]` and `[^abc]` are lists, `\` escapes. An exact name is a pattern with no
48
+ wildcards.
49
+
50
+ ```ts
51
+ await aliases.findAlias("TI101"); // exact
52
+ await aliases.findAlias("TI%"); // prefix
53
+ await aliases.findAlias("%", { categoryNodeId: TAG_VARIABLES }); // one branch
54
+ ```
55
+
56
+ The search is recursive from the category given, defaulting to the `Aliases` root, so one
57
+ call covers everything the Server publishes.
58
+
59
+ Results are **typed**, never raw Variants: `aliasName` is a string, `referencedNodes` is
60
+ `ExpandedNodeId[]`, ordered best match first (clause 6.3.2).
61
+
62
+ No match is an empty array, not an error — clause 6.3.2 Table 3 makes that a `Good`
63
+ response. A Server-side failure raises `AliasNameCallError`, which carries the StatusCode
64
+ so the cases of Table 4 stay distinguishable:
65
+
66
+ ```ts
67
+ try {
68
+ await aliases.findAlias(userSuppliedPattern);
69
+ } catch (err) {
70
+ if (err instanceof AliasNameCallError && err.statusCode.equals(StatusCodes.BadResponseTooLarge)) {
71
+ // ask the user to narrow the pattern
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## `FindAliasVerbose` is optional — handle its absence
77
+
78
+ Only `FindAlias` is MANDATORY (clause 6.3). A Server exposing nothing else is perfectly
79
+ conformant, so its absence is an outcome to handle rather than a fault:
80
+
81
+ ```ts
82
+ if (await aliases.supportsVerbose()) {
83
+ const entries = await aliases.findAliasVerbose("TI101");
84
+ entries[0].aliasNameCategoryId; // which category held it
85
+ entries[0].serverUris; // parallel to referencedNodes; null = this Server
86
+ }
87
+ ```
88
+
89
+ Calling it anyway raises `AliasNameMethodNotSupportedError`, naming the Method and the
90
+ category, **before any call is made** — the absence is discovered while resolving NodeIds,
91
+ so it never arrives as an unhandled `Bad_NotImplemented` from the wire.
92
+
93
+ `supportsConfiguration()` does the same for `AddAliasesToCategory` /
94
+ `DeleteAliasesFromCategory`.
95
+
96
+ ## Method NodeIds are resolved once
97
+
98
+ All four Methods of a category are resolved in a **single** `translateBrowsePath` round
99
+ trip and cached for the life of the instance — asking for four costs no more than asking
100
+ for one, so a later `findAliasVerbose` after a `findAlias` makes no further round trip.
101
+ Construct one `ClientAliasSet` per session; call `invalidate()` if the Server's address
102
+ space changes underneath it.
103
+
104
+ ## Nodes on another Server (Annex A)
105
+
106
+ A returned `ExpandedNodeId` may carry a non-zero `ServerIndex`, which says only "this Node
107
+ is somewhere else" — the index means nothing without the Server's `ServerArray`. That
108
+ lookup is the step every Client has to perform, so it is here rather than in each caller:
109
+
110
+ ```ts
111
+ const located = await aliases.serverIndexResolver.locate(entry.referencedNodes[0]);
112
+ if (!located.local) {
113
+ connectTo(located.serverUri);
114
+ }
115
+ ```
116
+
117
+ The `ServerArray` is read once and cached. Every Node these packages publish is local, so
118
+ `local` is always true against a Server built with `node-opcua-alias-name-server`; this
119
+ matters when talking to a Server that does aggregate.
120
+
121
+ ## Browsing the hierarchy
122
+
123
+ Resolving an alias needs no browsing — `findAlias` on the root searches recursively — but
124
+ a Client that wants to show the tree, or search one branch, can walk it:
125
+
126
+ ```ts
127
+ for (const category of await aliases.browseSubCategories()) {
128
+ console.log(category.browseName, category.nodeId.toString());
129
+ }
130
+ ```
131
+
132
+ Only `AliasNameCategoryType` instances are returned; the `AliasNameType` instances a
133
+ category also Organizes are not categories and are filtered out.
134
+
135
+ ## License
136
+
137
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @module node-opcua-alias-name-client
3
+ *
4
+ * Client-side API for OPC UA AliasNames (OPC 10000-17).
5
+ *
6
+ * All navigation goes through {@link IBasicSessionAsync2} — browse, read, call,
7
+ * translateBrowsePath — so the same code drives a remote `ClientSession` and an
8
+ * in-process `PseudoSession`. That is what lets these be tested without a
9
+ * transport, and what lets a Server-side tool resolve its own aliases through
10
+ * the same API a Client uses.
11
+ */
12
+ import { type ExpandedNodeId, type NodeId } from "node-opcua-nodeid";
13
+ import type { IBasicSessionAsync2 } from "node-opcua-pseudo-session";
14
+ import { ServerIndexResolver } from "./server_index_resolver.js";
15
+ /** `Aliases`, the root of the hierarchy (OPC 10000-17 clause 9.2). */
16
+ export declare const ALIASES_ROOT: NodeId;
17
+ /** `TagVariables` (clause 9.3). */
18
+ export declare const TAG_VARIABLES: NodeId;
19
+ /** `Topics` (clause 9.4). */
20
+ export declare const TOPICS: NodeId;
21
+ /** One resolved AliasName, as `FindAlias` reports it (clause 7.2). */
22
+ export interface ClientAliasEntry {
23
+ /** The string part of the AliasName. */
24
+ aliasName: string;
25
+ /**
26
+ * The namespace the AliasName was published in.
27
+ *
28
+ * Clause 6.2 requires a Client to **ignore this when comparing** AliasNames.
29
+ * It is reported for completeness, not for matching.
30
+ */
31
+ namespaceIndex: number;
32
+ /** The Nodes the alias names, best match first (clause 6.3.2). */
33
+ referencedNodes: ExpandedNodeId[];
34
+ }
35
+ /** One resolved AliasName, as `FindAliasVerbose` reports it (clause 7.3). */
36
+ export interface ClientAliasVerboseEntry extends ClientAliasEntry {
37
+ /**
38
+ * Parallel to {@link referencedNodes}: the ServerUri of each Node, `null`
39
+ * for one on the Server that answered.
40
+ */
41
+ serverUris: (string | null)[];
42
+ /**
43
+ * The category that actually held the alias, which for a recursive search is
44
+ * the nested one rather than the one that was called.
45
+ */
46
+ aliasNameCategoryId: NodeId;
47
+ }
48
+ export interface FindAliasOptions {
49
+ /**
50
+ * The category to search, recursively. Defaults to {@link ALIASES_ROOT},
51
+ * which covers everything the Server publishes.
52
+ */
53
+ categoryNodeId?: NodeId;
54
+ /**
55
+ * Restrict to this ReferenceType and its subtypes (clause 6.3.2 Table 3).
56
+ * Omit for any.
57
+ */
58
+ referenceTypeFilter?: NodeId;
59
+ }
60
+ /**
61
+ * Client-side entry point for a Server's AliasNames.
62
+ *
63
+ * ```ts
64
+ * const aliases = new ClientAliasSet(session);
65
+ * const [entry] = await aliases.findAlias("TI101");
66
+ * const nodeId = entry.referencedNodes[0];
67
+ * ```
68
+ *
69
+ * Construct one per session: Method NodeIds are resolved lazily, in a single
70
+ * `translateBrowsePath` round trip per category, and cached for the lifetime of
71
+ * the instance.
72
+ *
73
+ * This resolves the aliases **a Server publishes about itself**. Aggregating
74
+ * across Servers (OPC 10000-17 Annexes B and C) is out of scope; where a Server
75
+ * does aggregate, {@link serverIndexResolver} turns the `ServerIndex` of a
76
+ * returned `ExpandedNodeId` into a URI.
77
+ */
78
+ export declare class ClientAliasSet {
79
+ readonly session: IBasicSessionAsync2;
80
+ /** Resolves the `ServerIndex` of a returned Node (Annex A). */
81
+ readonly serverIndexResolver: ServerIndexResolver;
82
+ private readonly methodCache;
83
+ constructor(session: IBasicSessionAsync2);
84
+ /**
85
+ * Resolve an AliasName, or a `Like` pattern, to the Nodes it names.
86
+ *
87
+ * The pattern is an OPC 10000-4 `Like` pattern: `%` is any run of
88
+ * characters, `_` is exactly one, `[abc]` and `[^abc]` are lists, and `\`
89
+ * escapes. An exact name is simply a pattern with no wildcards.
90
+ *
91
+ * @throws {@link AliasNameCallError} when the Server answers a bad
92
+ * StatusCode — `Bad_InvalidArgument` for a malformed pattern,
93
+ * `Bad_ResponseTooLarge` when a narrower filter is needed,
94
+ * `Bad_UserAccessDenied` when the session may not read the category.
95
+ */
96
+ findAlias(pattern: string, options?: FindAliasOptions): Promise<ClientAliasEntry[]>;
97
+ /**
98
+ * Resolve an AliasName and learn which category held it and which Server
99
+ * each Node is on (clause 6.3.3).
100
+ *
101
+ * @throws {@link AliasNameMethodNotSupportedError} when the Server exposes
102
+ * only the mandatory `FindAlias`. That is a conformant Server, so this is
103
+ * an expected outcome to handle, not a fault — use {@link supportsVerbose}
104
+ * to check first.
105
+ */
106
+ findAliasVerbose(pattern: string, options?: FindAliasOptions): Promise<ClientAliasVerboseEntry[]>;
107
+ /**
108
+ * True when the Server exposes `FindAliasVerbose` on this category.
109
+ *
110
+ * Cheaper than catching {@link AliasNameMethodNotSupportedError}, and reads
111
+ * better when the Client has a fallback path.
112
+ */
113
+ supportsVerbose(categoryNodeId?: NodeId): Promise<boolean>;
114
+ /**
115
+ * True when the Server exposes the configuration Methods on this category
116
+ * (clauses 6.3.4 and 6.3.5). They are optional and off by default in most
117
+ * Servers.
118
+ */
119
+ supportsConfiguration(categoryNodeId?: NodeId): Promise<boolean>;
120
+ /**
121
+ * The `AliasNameCategoryType` instances Organized directly by a category.
122
+ *
123
+ * Browsing is not needed to *resolve* an alias — `FindAlias` on the root
124
+ * searches recursively — but a Client that wants to show the hierarchy, or
125
+ * to search one branch, needs it.
126
+ */
127
+ browseSubCategories(categoryNodeId?: NodeId): Promise<Array<{
128
+ nodeId: NodeId;
129
+ browseName: string;
130
+ }>>;
131
+ /** Forget every cached Method NodeId. */
132
+ invalidate(): void;
133
+ /**
134
+ * Resolve every Method of a category in **one** `translateBrowsePath` round
135
+ * trip, and cache the result.
136
+ *
137
+ * Asking for all four at once costs no more than asking for one, and means a
138
+ * Client that later calls `findAliasVerbose` after `findAlias` makes no
139
+ * further round trip.
140
+ */
141
+ private resolveMethods;
142
+ /** Call one of the two find Methods and return its ExtensionObject array. */
143
+ private callFind;
144
+ }
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ /**
3
+ * @module node-opcua-alias-name-client
4
+ *
5
+ * Client-side API for OPC UA AliasNames (OPC 10000-17).
6
+ *
7
+ * All navigation goes through {@link IBasicSessionAsync2} — browse, read, call,
8
+ * translateBrowsePath — so the same code drives a remote `ClientSession` and an
9
+ * in-process `PseudoSession`. That is what lets these be tested without a
10
+ * transport, and what lets a Server-side tool resolve its own aliases through
11
+ * the same API a Client uses.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.ClientAliasSet = exports.TOPICS = exports.TAG_VARIABLES = exports.ALIASES_ROOT = void 0;
15
+ const node_opcua_constants_1 = require("node-opcua-constants");
16
+ const node_opcua_data_model_1 = require("node-opcua-data-model");
17
+ const node_opcua_nodeid_1 = require("node-opcua-nodeid");
18
+ const node_opcua_service_translate_browse_path_1 = require("node-opcua-service-translate-browse-path");
19
+ const node_opcua_variant_1 = require("node-opcua-variant");
20
+ const errors_js_1 = require("./errors.js");
21
+ const server_index_resolver_js_1 = require("./server_index_resolver.js");
22
+ /** `Aliases`, the root of the hierarchy (OPC 10000-17 clause 9.2). */
23
+ exports.ALIASES_ROOT = (0, node_opcua_nodeid_1.resolveNodeId)(node_opcua_constants_1.ObjectIds.Aliases);
24
+ /** `TagVariables` (clause 9.3). */
25
+ exports.TAG_VARIABLES = (0, node_opcua_nodeid_1.resolveNodeId)(node_opcua_constants_1.ObjectIds.TagVariables);
26
+ /** `Topics` (clause 9.4). */
27
+ exports.TOPICS = (0, node_opcua_nodeid_1.resolveNodeId)(node_opcua_constants_1.ObjectIds.Topics);
28
+ /** Read a QualifiedName's parts defensively — a Server may send a bare string. */
29
+ function qualifiedName(value) {
30
+ return { name: value?.name ?? "", namespaceIndex: value?.namespaceIndex ?? 0 };
31
+ }
32
+ /**
33
+ * Client-side entry point for a Server's AliasNames.
34
+ *
35
+ * ```ts
36
+ * const aliases = new ClientAliasSet(session);
37
+ * const [entry] = await aliases.findAlias("TI101");
38
+ * const nodeId = entry.referencedNodes[0];
39
+ * ```
40
+ *
41
+ * Construct one per session: Method NodeIds are resolved lazily, in a single
42
+ * `translateBrowsePath` round trip per category, and cached for the lifetime of
43
+ * the instance.
44
+ *
45
+ * This resolves the aliases **a Server publishes about itself**. Aggregating
46
+ * across Servers (OPC 10000-17 Annexes B and C) is out of scope; where a Server
47
+ * does aggregate, {@link serverIndexResolver} turns the `ServerIndex` of a
48
+ * returned `ExpandedNodeId` into a URI.
49
+ */
50
+ class ClientAliasSet {
51
+ session;
52
+ /** Resolves the `ServerIndex` of a returned Node (Annex A). */
53
+ serverIndexResolver;
54
+ methodCache = new Map();
55
+ constructor(session) {
56
+ this.session = session;
57
+ this.serverIndexResolver = new server_index_resolver_js_1.ServerIndexResolver(session);
58
+ }
59
+ /**
60
+ * Resolve an AliasName, or a `Like` pattern, to the Nodes it names.
61
+ *
62
+ * The pattern is an OPC 10000-4 `Like` pattern: `%` is any run of
63
+ * characters, `_` is exactly one, `[abc]` and `[^abc]` are lists, and `\`
64
+ * escapes. An exact name is simply a pattern with no wildcards.
65
+ *
66
+ * @throws {@link AliasNameCallError} when the Server answers a bad
67
+ * StatusCode — `Bad_InvalidArgument` for a malformed pattern,
68
+ * `Bad_ResponseTooLarge` when a narrower filter is needed,
69
+ * `Bad_UserAccessDenied` when the session may not read the category.
70
+ */
71
+ async findAlias(pattern, options) {
72
+ const categoryNodeId = options?.categoryNodeId ?? exports.ALIASES_ROOT;
73
+ const methods = await this.resolveMethods(categoryNodeId);
74
+ if (!methods.findAlias) {
75
+ // FindAlias is MANDATORY, so this means the Node is not an
76
+ // AliasNameCategoryType instance, or the Server is non-conformant
77
+ throw new errors_js_1.AliasNameMethodNotSupportedError("FindAlias", categoryNodeId);
78
+ }
79
+ const extensionObjects = await this.callFind("FindAlias", categoryNodeId, methods.findAlias, pattern, options?.referenceTypeFilter);
80
+ return extensionObjects.map((raw) => {
81
+ const value = raw;
82
+ const { name, namespaceIndex } = qualifiedName(value.aliasName);
83
+ return {
84
+ aliasName: name,
85
+ namespaceIndex,
86
+ referencedNodes: value.referencedNodes ?? []
87
+ };
88
+ });
89
+ }
90
+ /**
91
+ * Resolve an AliasName and learn which category held it and which Server
92
+ * each Node is on (clause 6.3.3).
93
+ *
94
+ * @throws {@link AliasNameMethodNotSupportedError} when the Server exposes
95
+ * only the mandatory `FindAlias`. That is a conformant Server, so this is
96
+ * an expected outcome to handle, not a fault — use {@link supportsVerbose}
97
+ * to check first.
98
+ */
99
+ async findAliasVerbose(pattern, options) {
100
+ const categoryNodeId = options?.categoryNodeId ?? exports.ALIASES_ROOT;
101
+ const methods = await this.resolveMethods(categoryNodeId);
102
+ if (!methods.findAliasVerbose) {
103
+ throw new errors_js_1.AliasNameMethodNotSupportedError("FindAliasVerbose", categoryNodeId);
104
+ }
105
+ const extensionObjects = await this.callFind("FindAliasVerbose", categoryNodeId, methods.findAliasVerbose, pattern, options?.referenceTypeFilter);
106
+ return extensionObjects.map((raw) => {
107
+ const value = raw;
108
+ const { name, namespaceIndex } = qualifiedName(value.aliasName);
109
+ return {
110
+ aliasName: name,
111
+ namespaceIndex,
112
+ referencedNodes: value.referencedNodes ?? [],
113
+ serverUris: value.serverUris ?? [],
114
+ aliasNameCategoryId: value.aliasNameCategoryId
115
+ };
116
+ });
117
+ }
118
+ /**
119
+ * True when the Server exposes `FindAliasVerbose` on this category.
120
+ *
121
+ * Cheaper than catching {@link AliasNameMethodNotSupportedError}, and reads
122
+ * better when the Client has a fallback path.
123
+ */
124
+ async supportsVerbose(categoryNodeId = exports.ALIASES_ROOT) {
125
+ return (await this.resolveMethods(categoryNodeId)).findAliasVerbose !== null;
126
+ }
127
+ /**
128
+ * True when the Server exposes the configuration Methods on this category
129
+ * (clauses 6.3.4 and 6.3.5). They are optional and off by default in most
130
+ * Servers.
131
+ */
132
+ async supportsConfiguration(categoryNodeId = exports.ALIASES_ROOT) {
133
+ const methods = await this.resolveMethods(categoryNodeId);
134
+ return methods.addAliasesToCategory !== null && methods.deleteAliasesFromCategory !== null;
135
+ }
136
+ /**
137
+ * The `AliasNameCategoryType` instances Organized directly by a category.
138
+ *
139
+ * Browsing is not needed to *resolve* an alias — `FindAlias` on the root
140
+ * searches recursively — but a Client that wants to show the hierarchy, or
141
+ * to search one branch, needs it.
142
+ */
143
+ async browseSubCategories(categoryNodeId = exports.ALIASES_ROOT) {
144
+ const result = await this.session.browse({
145
+ nodeId: categoryNodeId,
146
+ browseDirection: node_opcua_data_model_1.BrowseDirection.Forward,
147
+ referenceTypeId: (0, node_opcua_nodeid_1.resolveNodeId)("Organizes"),
148
+ includeSubtypes: true,
149
+ nodeClassMask: node_opcua_data_model_1.NodeClass.Object,
150
+ resultMask: 0x3f
151
+ });
152
+ const references = result.references ?? [];
153
+ const out = [];
154
+ for (const reference of references) {
155
+ // an AliasNameCategoryType instance carries FindAlias; an
156
+ // AliasNameType instance does not, which is how they are told apart
157
+ // without reading TypeDefinition for each
158
+ const methods = await this.resolveMethods(reference.nodeId);
159
+ if (methods.findAlias) {
160
+ out.push({ nodeId: reference.nodeId, browseName: reference.browseName.name ?? "" });
161
+ }
162
+ }
163
+ return out;
164
+ }
165
+ /** Forget every cached Method NodeId. */
166
+ invalidate() {
167
+ this.methodCache.clear();
168
+ this.serverIndexResolver.invalidate();
169
+ }
170
+ /**
171
+ * Resolve every Method of a category in **one** `translateBrowsePath` round
172
+ * trip, and cache the result.
173
+ *
174
+ * Asking for all four at once costs no more than asking for one, and means a
175
+ * Client that later calls `findAliasVerbose` after `findAlias` makes no
176
+ * further round trip.
177
+ */
178
+ async resolveMethods(categoryNodeId) {
179
+ const key = categoryNodeId.toString();
180
+ const cached = this.methodCache.get(key);
181
+ if (cached) {
182
+ return cached;
183
+ }
184
+ const names = ["FindAlias", "FindAliasVerbose", "AddAliasesToCategory", "DeleteAliasesFromCategory"];
185
+ const results = await this.session.translateBrowsePath(names.map((name) => (0, node_opcua_service_translate_browse_path_1.makeBrowsePath)(categoryNodeId, `/${name}`)));
186
+ const pick = (index) => {
187
+ const result = results[index];
188
+ return result?.statusCode.isGood() && result.targets?.length ? result.targets[0].targetId : null;
189
+ };
190
+ const methods = {
191
+ findAlias: pick(0),
192
+ findAliasVerbose: pick(1),
193
+ addAliasesToCategory: pick(2),
194
+ deleteAliasesFromCategory: pick(3)
195
+ };
196
+ this.methodCache.set(key, methods);
197
+ return methods;
198
+ }
199
+ /** Call one of the two find Methods and return its ExtensionObject array. */
200
+ async callFind(methodName, categoryNodeId, methodId, pattern, referenceTypeFilter) {
201
+ const result = await this.session.call({
202
+ objectId: categoryNodeId,
203
+ methodId,
204
+ inputArguments: [
205
+ { dataType: node_opcua_variant_1.DataType.String, value: pattern },
206
+ // an omitted filter is the null NodeId, not a null value
207
+ { dataType: node_opcua_variant_1.DataType.NodeId, value: referenceTypeFilter ?? node_opcua_nodeid_1.NodeId.nullNodeId }
208
+ ]
209
+ });
210
+ if (!result.statusCode.isGood()) {
211
+ throw new errors_js_1.AliasNameCallError(methodName, categoryNodeId, result.statusCode);
212
+ }
213
+ const output = result.outputArguments?.[0];
214
+ const value = output?.value;
215
+ // no match is Good with an empty list (clause 6.3.2 Table 3)
216
+ if (value === null || value === undefined) {
217
+ return [];
218
+ }
219
+ return Array.isArray(value) ? value : [value];
220
+ }
221
+ }
222
+ exports.ClientAliasSet = ClientAliasSet;
223
+ //# sourceMappingURL=client_alias_set.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client_alias_set.js","sourceRoot":"","sources":["../source/client_alias_set.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @module node-opcua-alias-name-client
3
+ */
4
+ import type { NodeId } from "node-opcua-nodeid";
5
+ import type { StatusCode } from "node-opcua-status-code";
6
+ /**
7
+ * The Server does not expose the Method that was asked for.
8
+ *
9
+ * `FindAlias` is MANDATORY on every `AliasNameCategoryType` instance, but
10
+ * `FindAliasVerbose`, `AddAliasesToCategory` and `DeleteAliasesFromCategory` are
11
+ * all optional (OPC 10000-17 clause 6.3). A Server that implements only the
12
+ * mandatory Method is perfectly conformant, so a Client that wants the verbose
13
+ * form must be able to tell "this Server does not offer it" apart from "the call
14
+ * failed".
15
+ *
16
+ * Raised before any call is made — the Method's absence is discovered while
17
+ * resolving NodeIds — so it never surfaces as an unhandled `Bad_NotImplemented`
18
+ * from the wire.
19
+ */
20
+ export declare class AliasNameMethodNotSupportedError extends Error {
21
+ /** The Method that is missing, by BrowseName. */
22
+ readonly methodName: string;
23
+ /** The category it was looked for on. */
24
+ readonly categoryNodeId: NodeId;
25
+ constructor(methodName: string, categoryNodeId: NodeId);
26
+ }
27
+ /**
28
+ * The Server answered a `FindAlias` call with a bad StatusCode.
29
+ *
30
+ * Carries the code so a caller can distinguish the cases clause 6.3.2 Table 4
31
+ * defines — `Bad_InvalidArgument` for a malformed search pattern,
32
+ * `Bad_ResponseTooLarge` for a result set that needs a narrower filter,
33
+ * `Bad_UserAccessDenied` for a category the session may not read.
34
+ */
35
+ export declare class AliasNameCallError extends Error {
36
+ readonly statusCode: StatusCode;
37
+ readonly categoryNodeId: NodeId;
38
+ constructor(methodName: string, categoryNodeId: NodeId, statusCode: StatusCode);
39
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ /**
3
+ * @module node-opcua-alias-name-client
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AliasNameCallError = exports.AliasNameMethodNotSupportedError = void 0;
7
+ /**
8
+ * The Server does not expose the Method that was asked for.
9
+ *
10
+ * `FindAlias` is MANDATORY on every `AliasNameCategoryType` instance, but
11
+ * `FindAliasVerbose`, `AddAliasesToCategory` and `DeleteAliasesFromCategory` are
12
+ * all optional (OPC 10000-17 clause 6.3). A Server that implements only the
13
+ * mandatory Method is perfectly conformant, so a Client that wants the verbose
14
+ * form must be able to tell "this Server does not offer it" apart from "the call
15
+ * failed".
16
+ *
17
+ * Raised before any call is made — the Method's absence is discovered while
18
+ * resolving NodeIds — so it never surfaces as an unhandled `Bad_NotImplemented`
19
+ * from the wire.
20
+ */
21
+ class AliasNameMethodNotSupportedError extends Error {
22
+ /** The Method that is missing, by BrowseName. */
23
+ methodName;
24
+ /** The category it was looked for on. */
25
+ categoryNodeId;
26
+ constructor(methodName, categoryNodeId) {
27
+ super(`the Server does not expose ${methodName} on ${categoryNodeId.toString()}. ` +
28
+ "Only FindAlias is mandatory in OPC 10000-17 clause 6.3; the rest are optional.");
29
+ this.name = "AliasNameMethodNotSupportedError";
30
+ this.methodName = methodName;
31
+ this.categoryNodeId = categoryNodeId;
32
+ }
33
+ }
34
+ exports.AliasNameMethodNotSupportedError = AliasNameMethodNotSupportedError;
35
+ /**
36
+ * The Server answered a `FindAlias` call with a bad StatusCode.
37
+ *
38
+ * Carries the code so a caller can distinguish the cases clause 6.3.2 Table 4
39
+ * defines — `Bad_InvalidArgument` for a malformed search pattern,
40
+ * `Bad_ResponseTooLarge` for a result set that needs a narrower filter,
41
+ * `Bad_UserAccessDenied` for a category the session may not read.
42
+ */
43
+ class AliasNameCallError extends Error {
44
+ statusCode;
45
+ categoryNodeId;
46
+ constructor(methodName, categoryNodeId, statusCode) {
47
+ super(`${methodName} on ${categoryNodeId.toString()} failed with ${statusCode.toString()}`);
48
+ this.name = "AliasNameCallError";
49
+ this.statusCode = statusCode;
50
+ this.categoryNodeId = categoryNodeId;
51
+ }
52
+ }
53
+ exports.AliasNameCallError = AliasNameCallError;
54
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../source/errors.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAKH;;;;;;;;;;;;;GAaG;AACH,MAAa,gCAAiC,SAAQ,KAAK;IACvD,iDAAiD;IACjC,UAAU,CAAS;IACnC,yCAAyC;IACzB,cAAc,CAAS;IAEvC,YAAY,UAAkB,EAAE,cAAsB;QAClD,KAAK,CACD,8BAA8B,UAAU,OAAO,cAAc,CAAC,QAAQ,EAAE,IAAI;YACxE,gFAAgF,CACvF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,kCAAkC,CAAC;QAC/C,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACzC,CAAC;CACJ;AAfD,4EAeC;AAED;;;;;;;GAOG;AACH,MAAa,kBAAmB,SAAQ,KAAK;IACzB,UAAU,CAAa;IACvB,cAAc,CAAS;IAEvC,YAAY,UAAkB,EAAE,cAAsB,EAAE,UAAsB;QAC1E,KAAK,CAAC,GAAG,UAAU,OAAO,cAAc,CAAC,QAAQ,EAAE,gBAAgB,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC5F,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACzC,CAAC;CACJ;AAVD,gDAUC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @module node-opcua-alias-name-client
3
+ *
4
+ * Client-side OPC 10000-17 (AliasNames).
5
+ *
6
+ * Resolves the AliasNames **a Server publishes about itself**. Aggregating
7
+ * AliasNames collected from other Servers (Annexes B and C) and the Annex D
8
+ * PubSub change notification are out of scope; {@link ServerIndexResolver}
9
+ * covers the one cross-Server step a Client still has to make, turning the
10
+ * `ServerIndex` of a returned `ExpandedNodeId` into a URI (Annex A).
11
+ */
12
+ export { ALIASES_ROOT, type ClientAliasEntry, ClientAliasSet, type ClientAliasVerboseEntry, type FindAliasOptions, TAG_VARIABLES, TOPICS } from "./client_alias_set.js";
13
+ export { AliasNameCallError, AliasNameMethodNotSupportedError } from "./errors.js";
14
+ export { LOCAL_SERVER_INDEX, ServerIndexResolver } from "./server_index_resolver.js";