node-opcua-alias-name-server 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 +22 -0
- package/README.md +361 -0
- package/dist/add_alias.d.ts +73 -0
- package/dist/add_alias.js +262 -0
- package/dist/add_alias.js.map +1 -0
- package/dist/address_space_alias_store.d.ts +130 -0
- package/dist/address_space_alias_store.js +360 -0
- package/dist/address_space_alias_store.js.map +1 -0
- package/dist/alias_hierarchy.d.ts +45 -0
- package/dist/alias_hierarchy.js +135 -0
- package/dist/alias_hierarchy.js.map +1 -0
- package/dist/alias_index.d.ts +60 -0
- package/dist/alias_index.js +119 -0
- package/dist/alias_index.js.map +1 -0
- package/dist/alias_name_archive.d.ts +45 -0
- package/dist/alias_name_archive.js +75 -0
- package/dist/alias_name_archive.js.map +1 -0
- package/dist/bind_alias_category.d.ts +173 -0
- package/dist/bind_alias_category.js +417 -0
- package/dist/bind_alias_category.js.map +1 -0
- package/dist/bind_configuration_methods.d.ts +44 -0
- package/dist/bind_configuration_methods.js +175 -0
- package/dist/bind_configuration_methods.js.map +1 -0
- package/dist/bind_find_alias.d.ts +61 -0
- package/dist/bind_find_alias.js +227 -0
- package/dist/bind_find_alias.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/install_alias_names.d.ts +229 -0
- package/dist/install_alias_names.js +146 -0
- package/dist/install_alias_names.js.map +1 -0
- package/dist/last_change.d.ts +87 -0
- package/dist/last_change.js +174 -0
- package/dist/last_change.js.map +1 -0
- package/dist/well_known.d.ts +88 -0
- package/dist/well_known.js +93 -0
- package/dist/well_known.js.map +1 -0
- package/package.json +54 -0
- package/source/add_alias.ts +318 -0
- package/source/address_space_alias_store.ts +417 -0
- package/source/alias_hierarchy.ts +141 -0
- package/source/alias_index.ts +127 -0
- package/source/alias_name_archive.ts +84 -0
- package/source/bind_alias_category.ts +546 -0
- package/source/bind_configuration_methods.ts +219 -0
- package/source/bind_find_alias.ts +290 -0
- package/source/index.ts +74 -0
- package/source/install_alias_names.ts +342 -0
- package/source/last_change.ts +201 -0
- package/source/well_known.ts +101 -0
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-opcua-alias-name-server",
|
|
3
|
+
"version": "2.176.0",
|
|
4
|
+
"description": "pure nodejs OPCUA SDK - server-side alias names (OPC 10000-17)",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc -b",
|
|
7
|
+
"lint": "biome check .",
|
|
8
|
+
"format": "biome check --write .",
|
|
9
|
+
"clean": "npx rimraf -g node_modules dist *.tsbuildinfo",
|
|
10
|
+
"test": "mocha",
|
|
11
|
+
"test:check": "tsc --noEmit -p test/tsconfig.json"
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"devEngines": {
|
|
16
|
+
"node": ">=18.x",
|
|
17
|
+
"npm": ">=10.x"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"node-opcua-address-space-base": "2.176.0",
|
|
21
|
+
"node-opcua-alias-name-common": "2.176.0",
|
|
22
|
+
"node-opcua-constants": "2.176.0",
|
|
23
|
+
"node-opcua-data-model": "2.176.0",
|
|
24
|
+
"node-opcua-nodeid": "2.176.0",
|
|
25
|
+
"node-opcua-service-call": "2.176.0",
|
|
26
|
+
"node-opcua-status-code": "2.176.0",
|
|
27
|
+
"node-opcua-types": "2.176.0",
|
|
28
|
+
"node-opcua-variant": "2.176.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"node-opcua-address-space": "2.176.0",
|
|
32
|
+
"node-opcua-leak-detector": "2.175.6",
|
|
33
|
+
"node-opcua-nodesets": "2.175.3"
|
|
34
|
+
},
|
|
35
|
+
"author": "Etienne Rossignon",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git://github.com/node-opcua/node-opcua.git"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"OPCUA",
|
|
43
|
+
"opcua",
|
|
44
|
+
"m2m",
|
|
45
|
+
"iot",
|
|
46
|
+
"opc ua",
|
|
47
|
+
"internet of things"
|
|
48
|
+
],
|
|
49
|
+
"homepage": "http://node-opcua.github.io/",
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"source"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-alias-name-server
|
|
3
|
+
*
|
|
4
|
+
* Creating and removing `AliasNameType` instances from code, for Servers whose
|
|
5
|
+
* address space is built programmatically rather than loaded from a NodeSet2.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { BaseNode, IAddressSpace, UAObject, UAObjectType } from "node-opcua-address-space-base";
|
|
9
|
+
import { BrowseDirection, NodeClass } from "node-opcua-data-model";
|
|
10
|
+
import { type NodeId, NodeId as NodeIdClass } from "node-opcua-nodeid";
|
|
11
|
+
import { findAliasNameType } from "./alias_hierarchy.js";
|
|
12
|
+
import { lookupAlias, noteAliasAdded, noteAliasRemoved } from "./alias_index.js";
|
|
13
|
+
import { getInstalledAliasNames } from "./bind_alias_category.js";
|
|
14
|
+
import { ALIAS_FOR, ALIAS_NAME_CATEGORY_TYPE, PUBLISHED_DATA_SET_TYPE, WellKnownCategories } from "./well_known.js";
|
|
15
|
+
|
|
16
|
+
/** Raised when an alias would break a rule of OPC 10000-17. */
|
|
17
|
+
export class AliasNameError extends Error {
|
|
18
|
+
constructor(message: string) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "AliasNameError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AddAliasOptions {
|
|
25
|
+
/**
|
|
26
|
+
* ReferenceType linking the AliasName to its target. Defaults to `AliasFor`
|
|
27
|
+
* (clause 8.2); a subtype is allowed.
|
|
28
|
+
*/
|
|
29
|
+
referenceType?: NodeId | string;
|
|
30
|
+
/**
|
|
31
|
+
* Namespace index for the AliasName's BrowseName. Defaults to the Server's
|
|
32
|
+
* own namespace. Clause 6.2 allows namespace 1 or a vendor namespace, and
|
|
33
|
+
* requires Clients to ignore it when comparing.
|
|
34
|
+
*/
|
|
35
|
+
namespaceIndex?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Accept a target this Server cannot resolve.
|
|
38
|
+
*
|
|
39
|
+
* For a Node on **another** Server, which by definition is not in this
|
|
40
|
+
* address space: the existence check and the clause 9.3 / 9.4 category
|
|
41
|
+
* restrictions are both skipped, because neither can be evaluated without
|
|
42
|
+
* being a Client of that Server. Used by `AddAliasesToCategory` when
|
|
43
|
+
* `TargetServers` names a remote Server (clause 6.3.4).
|
|
44
|
+
*/
|
|
45
|
+
allowUnresolvedTarget?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Add an `AliasNameType` instance under `category`, naming `target`.
|
|
50
|
+
*
|
|
51
|
+
* Enforces clause 6.2:
|
|
52
|
+
*
|
|
53
|
+
* - the string part of the BrowseName equals the DisplayName, with an empty
|
|
54
|
+
* locale and no other locale;
|
|
55
|
+
* - the Object has at least one `AliasFor` Reference;
|
|
56
|
+
* - the BrowseName is immutable once created. There is deliberately no rename:
|
|
57
|
+
* "If an AliasName is to be changed, it shall be a deletion of the old
|
|
58
|
+
* AliasName and the addition of the new AliasName", which yields a new NodeId
|
|
59
|
+
* and is what lets an aggregating Server notice the change.
|
|
60
|
+
*
|
|
61
|
+
* and the category restrictions of clauses 9.3 and 9.4, where the target is
|
|
62
|
+
* local and therefore checkable.
|
|
63
|
+
*
|
|
64
|
+
* Adding the same (name, target) twice returns the existing node rather than
|
|
65
|
+
* creating a duplicate.
|
|
66
|
+
*/
|
|
67
|
+
export function addAlias(
|
|
68
|
+
addressSpace: IAddressSpace,
|
|
69
|
+
category: UAObject | NodeId,
|
|
70
|
+
aliasName: string,
|
|
71
|
+
target: BaseNode | NodeId,
|
|
72
|
+
options?: AddAliasOptions
|
|
73
|
+
): UAObject {
|
|
74
|
+
const categoryNode = coerceCategory(addressSpace, category);
|
|
75
|
+
const referenceTypeId = coerceReferenceType(addressSpace, options?.referenceType);
|
|
76
|
+
|
|
77
|
+
if (!aliasName) {
|
|
78
|
+
throw new AliasNameError("addAlias: the alias name must not be empty");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// A target on another Server is not in this address space, so it can be
|
|
82
|
+
// neither resolved nor checked against the category restrictions.
|
|
83
|
+
let targetNodeId: NodeId;
|
|
84
|
+
if (options?.allowUnresolvedTarget) {
|
|
85
|
+
targetNodeId = target instanceof NodeIdClass ? target : (target as BaseNode).nodeId;
|
|
86
|
+
} else {
|
|
87
|
+
const targetNode = coerceTarget(addressSpace, target);
|
|
88
|
+
targetNodeId = targetNode.nodeId;
|
|
89
|
+
assertTargetAllowedInCategory(addressSpace, categoryNode, targetNode);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const existing = findAlias(addressSpace, categoryNode, aliasName);
|
|
93
|
+
if (existing) {
|
|
94
|
+
// clause 6.3.1: multiple <Alias> instances may point at the same Node,
|
|
95
|
+
// and the same name may name several Nodes; adding a target that is
|
|
96
|
+
// already there is a no-op rather than an error
|
|
97
|
+
const alreadyLinked = existing
|
|
98
|
+
.findReferencesEx(ALIAS_FOR, BrowseDirection.Forward)
|
|
99
|
+
.some((reference) => reference.nodeId.toString() === targetNodeId.toString());
|
|
100
|
+
if (!alreadyLinked) {
|
|
101
|
+
existing.addReference({ referenceType: referenceTypeId, nodeId: targetNodeId });
|
|
102
|
+
// clause 6.3.1: "the referenced Nodes of an AliasName in the
|
|
103
|
+
// AliasNameCategory changed"
|
|
104
|
+
notifyAliasChanged(addressSpace, categoryNode.nodeId);
|
|
105
|
+
}
|
|
106
|
+
return existing;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const aliasNameType = findAliasNameType(addressSpace);
|
|
110
|
+
if (!aliasNameType) {
|
|
111
|
+
throw new AliasNameError("addAlias: AliasNameType (i=23455) is not in the address space");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const namespace = addressSpace.getNamespace(options?.namespaceIndex ?? addressSpace.getOwnNamespace().index);
|
|
115
|
+
const alias = (aliasNameType as UAObjectType).instantiate({
|
|
116
|
+
browseName: { name: aliasName, namespaceIndex: namespace.index },
|
|
117
|
+
// clause 6.2: the DisplayName is the string part of the BrowseName, with
|
|
118
|
+
// an empty locale and no other locale - hence a single LocalizedText
|
|
119
|
+
// carrying a null locale, not a list
|
|
120
|
+
displayName: { locale: null, text: aliasName },
|
|
121
|
+
organizedBy: categoryNode,
|
|
122
|
+
namespace
|
|
123
|
+
}) as UAObject;
|
|
124
|
+
|
|
125
|
+
alias.addReference({ referenceType: referenceTypeId, nodeId: targetNodeId });
|
|
126
|
+
noteAliasAdded(addressSpace, categoryNode, aliasName, alias.nodeId);
|
|
127
|
+
// clause 6.3.1: "an AliasName was added to [...] the AliasNameCategory"
|
|
128
|
+
notifyAliasChanged(addressSpace, categoryNode.nodeId);
|
|
129
|
+
return alias;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Remove one target from an alias, or the whole alias when no target is given.
|
|
134
|
+
*
|
|
135
|
+
* Removing the last target deletes the `AliasNameType` node: clause 7.2 says the
|
|
136
|
+
* ReferencedNodes array always has at least one entry, so an alias naming
|
|
137
|
+
* nothing cannot be represented.
|
|
138
|
+
*
|
|
139
|
+
* @returns true when something was removed.
|
|
140
|
+
*/
|
|
141
|
+
export function removeAlias(
|
|
142
|
+
addressSpace: IAddressSpace,
|
|
143
|
+
category: UAObject | NodeId,
|
|
144
|
+
aliasName: string,
|
|
145
|
+
target?: BaseNode | NodeId
|
|
146
|
+
): boolean {
|
|
147
|
+
const categoryNode = coerceCategory(addressSpace, category);
|
|
148
|
+
const alias = findAlias(addressSpace, categoryNode, aliasName);
|
|
149
|
+
if (!alias) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!target) {
|
|
154
|
+
addressSpace.deleteNode(alias.nodeId);
|
|
155
|
+
noteAliasRemoved(addressSpace, categoryNode, aliasName);
|
|
156
|
+
notifyAliasChanged(addressSpace, categoryNode.nodeId);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const targetNodeId = target instanceof NodeIdClass ? target : target.nodeId;
|
|
161
|
+
const references = alias.findReferencesEx(ALIAS_FOR, BrowseDirection.Forward);
|
|
162
|
+
const match = references.find((reference) => reference.nodeId.toString() === targetNodeId.toString());
|
|
163
|
+
if (!match) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (references.length === 1) {
|
|
168
|
+
// the last target: the alias itself goes (clause 7.2)
|
|
169
|
+
addressSpace.deleteNode(alias.nodeId);
|
|
170
|
+
noteAliasRemoved(addressSpace, categoryNode, aliasName);
|
|
171
|
+
notifyAliasChanged(addressSpace, categoryNode.nodeId);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
alias.removeReference({ referenceType: match.referenceType, nodeId: targetNodeId });
|
|
175
|
+
notifyAliasChanged(addressSpace, categoryNode.nodeId);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The `AliasNameType` instance with this name Organized by `category`, if any.
|
|
181
|
+
*
|
|
182
|
+
* Compares the string part only, ignoring the namespace, as clause 6.2 requires.
|
|
183
|
+
* Backed by a per-category index — see {@link lookupAlias} for why a plain scan
|
|
184
|
+
* was not good enough.
|
|
185
|
+
*/
|
|
186
|
+
export function findAlias(addressSpace: IAddressSpace, category: UAObject, aliasName: string): UAObject | null {
|
|
187
|
+
return lookupAlias(addressSpace, category, aliasName);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Enforce the target restrictions of clauses 9.3 and 9.4.
|
|
192
|
+
*
|
|
193
|
+
* `TagVariables` restricts targets to Variables; `Topics` restricts them to
|
|
194
|
+
* `PublishedDataSetType` instances or subtypes. Both are checked only where the
|
|
195
|
+
* target is local — a Node on another Server cannot be inspected, which is the
|
|
196
|
+
* same reason clause 6.3.4 has `Uncertain_ReferenceOutOfServer`.
|
|
197
|
+
*
|
|
198
|
+
* The restriction applies to the whole subtree: an alias nested three levels
|
|
199
|
+
* below `TagVariables` is still "in the TagVariables hierarchy" (clause 9.3).
|
|
200
|
+
*/
|
|
201
|
+
function assertTargetAllowedInCategory(addressSpace: IAddressSpace, category: UAObject, target: BaseNode): void {
|
|
202
|
+
if (isWithin(addressSpace, category, WellKnownCategories.TagVariables)) {
|
|
203
|
+
if (target.nodeClass !== NodeClass.Variable) {
|
|
204
|
+
throw new AliasNameError(
|
|
205
|
+
`addAlias: TagVariables restricts targets to Variables (OPC 10000-17 clause 9.3); ` +
|
|
206
|
+
`${target.browseName.toString()} is a ${NodeClass[target.nodeClass]}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (isWithin(addressSpace, category, WellKnownCategories.Topics)) {
|
|
211
|
+
const publishedDataSetType = addressSpace.findObjectType(PUBLISHED_DATA_SET_TYPE);
|
|
212
|
+
const typeDefinition = target.nodeClass === NodeClass.Object ? (target as UAObject).typeDefinitionObj : null;
|
|
213
|
+
const ok =
|
|
214
|
+
publishedDataSetType &&
|
|
215
|
+
typeDefinition &&
|
|
216
|
+
(typeDefinition.nodeId.value === publishedDataSetType.nodeId.value || typeDefinition.isSubtypeOf(publishedDataSetType));
|
|
217
|
+
if (!ok) {
|
|
218
|
+
throw new AliasNameError(
|
|
219
|
+
`addAlias: Topics restricts targets to PublishedDataSetType or a subtype ` +
|
|
220
|
+
`(OPC 10000-17 clause 9.4); ${target.browseName.toString()} is not one`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** True when `category` is `ancestorNodeId` or nested anywhere below it. */
|
|
227
|
+
function isWithin(addressSpace: IAddressSpace, category: UAObject, ancestorNodeId: NodeId): boolean {
|
|
228
|
+
const ancestor = addressSpace.findNode(ancestorNodeId);
|
|
229
|
+
if (!ancestor) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
const seen = new Set<string>();
|
|
233
|
+
const walkUp = (node: BaseNode): boolean => {
|
|
234
|
+
const key = node.nodeId.toString();
|
|
235
|
+
if (seen.has(key)) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
seen.add(key);
|
|
239
|
+
if (key === ancestor.nodeId.toString()) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
for (const parent of node.findReferencesExAsObject("HierarchicalReferences", BrowseDirection.Inverse)) {
|
|
243
|
+
if (walkUp(parent)) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
};
|
|
249
|
+
return walkUp(category);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Accept either a category node or its NodeId. */
|
|
253
|
+
function coerceCategory(addressSpace: IAddressSpace, category: UAObject | NodeId): UAObject {
|
|
254
|
+
if (!(category instanceof NodeIdClass)) {
|
|
255
|
+
return category;
|
|
256
|
+
}
|
|
257
|
+
const node = addressSpace.findNode(category);
|
|
258
|
+
if (!node || node.nodeClass !== NodeClass.Object) {
|
|
259
|
+
throw new AliasNameError(`addAlias: ${category.toString()} is not an Object in this address space`);
|
|
260
|
+
}
|
|
261
|
+
const typeDefinition = (node as UAObject).typeDefinitionObj;
|
|
262
|
+
const categoryType = addressSpace.findObjectType(ALIAS_NAME_CATEGORY_TYPE);
|
|
263
|
+
if (
|
|
264
|
+
categoryType &&
|
|
265
|
+
typeDefinition &&
|
|
266
|
+
typeDefinition.nodeId.value !== categoryType.nodeId.value &&
|
|
267
|
+
!typeDefinition.isSubtypeOf(categoryType)
|
|
268
|
+
) {
|
|
269
|
+
throw new AliasNameError(`addAlias: ${category.toString()} is not an AliasNameCategoryType instance`);
|
|
270
|
+
}
|
|
271
|
+
return node as UAObject;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Accept either a target node or its NodeId. */
|
|
275
|
+
function coerceTarget(addressSpace: IAddressSpace, target: BaseNode | NodeId): BaseNode {
|
|
276
|
+
if (!(target instanceof NodeIdClass)) {
|
|
277
|
+
return target;
|
|
278
|
+
}
|
|
279
|
+
const node = addressSpace.findNode(target);
|
|
280
|
+
if (!node) {
|
|
281
|
+
throw new AliasNameError(`addAlias: target ${target.toString()} does not exist in this address space`);
|
|
282
|
+
}
|
|
283
|
+
return node;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Resolve the ReferenceType, defaulting to `AliasFor` (clause 8.2). */
|
|
287
|
+
function coerceReferenceType(addressSpace: IAddressSpace, referenceType: NodeId | string | undefined): NodeId {
|
|
288
|
+
if (!referenceType) {
|
|
289
|
+
return ALIAS_FOR;
|
|
290
|
+
}
|
|
291
|
+
const resolved = typeof referenceType === "string" ? addressSpace.findReferenceType(referenceType)?.nodeId : referenceType;
|
|
292
|
+
if (!resolved) {
|
|
293
|
+
throw new AliasNameError(`addAlias: unknown ReferenceType ${String(referenceType)}`);
|
|
294
|
+
}
|
|
295
|
+
const aliasFor = addressSpace.findReferenceType(ALIAS_FOR);
|
|
296
|
+
const candidate = addressSpace.findReferenceType(resolved);
|
|
297
|
+
if (aliasFor && candidate && candidate.nodeId.value !== aliasFor.nodeId.value && !candidate.isSubtypeOf(aliasFor)) {
|
|
298
|
+
throw new AliasNameError(
|
|
299
|
+
`addAlias: ${candidate.browseName.toString()} is not AliasFor or a subtype of it (OPC 10000-17 clause 8.2)`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
return resolved;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Tell the installed `LastChange` tracker that a category changed
|
|
307
|
+
* (clause 6.3.1).
|
|
308
|
+
*
|
|
309
|
+
* Routed through whatever installation recorded on the address space rather
|
|
310
|
+
* than through a parameter, so `addAlias` keeps its simple signature and a
|
|
311
|
+
* Server that has not installed AliasNames is unaffected. Fire and forget: the
|
|
312
|
+
* Property is written synchronously, only the persistence write is async, and
|
|
313
|
+
* failing to persist must not fail the caller's add.
|
|
314
|
+
*/
|
|
315
|
+
function notifyAliasChanged(addressSpace: IAddressSpace, categoryNodeId: NodeId): void {
|
|
316
|
+
const installed = getInstalledAliasNames(addressSpace);
|
|
317
|
+
void installed?.lastChange?.touch(categoryNodeId);
|
|
318
|
+
}
|