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.
Files changed (51) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +361 -0
  3. package/dist/add_alias.d.ts +73 -0
  4. package/dist/add_alias.js +262 -0
  5. package/dist/add_alias.js.map +1 -0
  6. package/dist/address_space_alias_store.d.ts +130 -0
  7. package/dist/address_space_alias_store.js +360 -0
  8. package/dist/address_space_alias_store.js.map +1 -0
  9. package/dist/alias_hierarchy.d.ts +45 -0
  10. package/dist/alias_hierarchy.js +135 -0
  11. package/dist/alias_hierarchy.js.map +1 -0
  12. package/dist/alias_index.d.ts +60 -0
  13. package/dist/alias_index.js +119 -0
  14. package/dist/alias_index.js.map +1 -0
  15. package/dist/alias_name_archive.d.ts +45 -0
  16. package/dist/alias_name_archive.js +75 -0
  17. package/dist/alias_name_archive.js.map +1 -0
  18. package/dist/bind_alias_category.d.ts +173 -0
  19. package/dist/bind_alias_category.js +417 -0
  20. package/dist/bind_alias_category.js.map +1 -0
  21. package/dist/bind_configuration_methods.d.ts +44 -0
  22. package/dist/bind_configuration_methods.js +175 -0
  23. package/dist/bind_configuration_methods.js.map +1 -0
  24. package/dist/bind_find_alias.d.ts +61 -0
  25. package/dist/bind_find_alias.js +227 -0
  26. package/dist/bind_find_alias.js.map +1 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.js +64 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/install_alias_names.d.ts +229 -0
  31. package/dist/install_alias_names.js +146 -0
  32. package/dist/install_alias_names.js.map +1 -0
  33. package/dist/last_change.d.ts +87 -0
  34. package/dist/last_change.js +174 -0
  35. package/dist/last_change.js.map +1 -0
  36. package/dist/well_known.d.ts +88 -0
  37. package/dist/well_known.js +93 -0
  38. package/dist/well_known.js.map +1 -0
  39. package/package.json +54 -0
  40. package/source/add_alias.ts +318 -0
  41. package/source/address_space_alias_store.ts +417 -0
  42. package/source/alias_hierarchy.ts +141 -0
  43. package/source/alias_index.ts +127 -0
  44. package/source/alias_name_archive.ts +84 -0
  45. package/source/bind_alias_category.ts +546 -0
  46. package/source/bind_configuration_methods.ts +219 -0
  47. package/source/bind_find_alias.ts +290 -0
  48. package/source/index.ts +74 -0
  49. package/source/install_alias_names.ts +342 -0
  50. package/source/last_change.ts +201 -0
  51. package/source/well_known.ts +101 -0
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ /**
3
+ * @module node-opcua-alias-name-server
4
+ *
5
+ * A per-category index from AliasName to the `AliasNameType` Object that carries
6
+ * it.
7
+ *
8
+ * ## Why this exists
9
+ *
10
+ * Looking an alias up by name means finding a child of the category with a given
11
+ * BrowseName. The address space has an O(1) index for exactly that — but
12
+ * `getChildByName` only consults it for `HasChild` subtypes, and an alias is an
13
+ * `Organizes` child of its category (clause 6.3 Table 2). `getFolderElementByName`
14
+ * does cover `Organizes`, but scans. So neither route is both correct and fast.
15
+ *
16
+ * That matters because `addAlias` has to look for an existing alias of the same
17
+ * name before creating one, so a Server building N aliases performed N linear
18
+ * scans of a growing category — quadratic. Measured on a category holding 1500
19
+ * aliases, 200 lookups cost 97 ms by scanning and 0 ms through this index, and
20
+ * building 1500 aliases went from 2545 ms to 1686 ms.
21
+ *
22
+ * That is not the whole story: the larger remaining cost is inside
23
+ * `UAObjectType.instantiate`, which is itself superlinear in the number of
24
+ * children the parent already has. That is an address-space concern rather than
25
+ * an AliasName one, and is tracked separately.
26
+ *
27
+ * ## How it stays correct
28
+ *
29
+ * The index is built lazily, from one full scan, so aliases modelled in a
30
+ * NodeSet2.xml are picked up. After that it is maintained incrementally by
31
+ * {@link noteAliasAdded} and {@link noteAliasRemoved}, which `addAlias` and
32
+ * `removeAlias` call.
33
+ *
34
+ * A hit is verified against the address space before being returned, so an alias
35
+ * deleted by other means degrades to a miss rather than a dangling Object. A
36
+ * miss is trusted: an alias created behind this package's back after the index
37
+ * was built would not be found, and `addAlias` would then fail loudly on the
38
+ * duplicate BrowseName rather than corrupting anything. Call
39
+ * {@link invalidateAliasIndex} if a Server mutates a category by other means.
40
+ *
41
+ * Keyed by the node itself in a `WeakMap`, so a disposed address space takes its
42
+ * indexes with it.
43
+ */
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.lookupAlias = lookupAlias;
46
+ exports.noteAliasAdded = noteAliasAdded;
47
+ exports.noteAliasRemoved = noteAliasRemoved;
48
+ exports.invalidateAliasIndex = invalidateAliasIndex;
49
+ const node_opcua_data_model_1 = require("node-opcua-data-model");
50
+ const alias_hierarchy_js_1 = require("./alias_hierarchy.js");
51
+ const indexes = new WeakMap();
52
+ /** Build the index for a category by scanning it once. */
53
+ function buildIndex(addressSpace, category) {
54
+ const index = new Map();
55
+ const aliasNameType = (0, alias_hierarchy_js_1.findAliasNameType)(addressSpace);
56
+ if (!aliasNameType) {
57
+ return index;
58
+ }
59
+ for (const child of category.findReferencesExAsObject("HierarchicalReferences", node_opcua_data_model_1.BrowseDirection.Forward)) {
60
+ if (child.nodeClass !== node_opcua_data_model_1.NodeClass.Object) {
61
+ continue;
62
+ }
63
+ const name = child.browseName.name;
64
+ if (!name || index.has(name)) {
65
+ continue;
66
+ }
67
+ const typeDefinition = child.typeDefinitionObj;
68
+ if (typeDefinition &&
69
+ (typeDefinition.nodeId.value === aliasNameType.nodeId.value || typeDefinition.isSubtypeOf(aliasNameType))) {
70
+ index.set(name, child.nodeId);
71
+ }
72
+ }
73
+ return index;
74
+ }
75
+ /** The index for a category, built on first use. */
76
+ function indexOf(addressSpace, category) {
77
+ let index = indexes.get(category);
78
+ if (!index) {
79
+ index = buildIndex(addressSpace, category);
80
+ indexes.set(category, index);
81
+ }
82
+ return index;
83
+ }
84
+ /**
85
+ * The `AliasNameType` instance with this name in `category`, or null.
86
+ *
87
+ * O(1) after the first call on a given category.
88
+ */
89
+ function lookupAlias(addressSpace, category, aliasName) {
90
+ const index = indexOf(addressSpace, category);
91
+ const nodeId = index.get(aliasName);
92
+ if (!nodeId) {
93
+ return null;
94
+ }
95
+ const node = addressSpace.findNode(nodeId);
96
+ if (!node || node.nodeClass !== node_opcua_data_model_1.NodeClass.Object) {
97
+ // deleted behind our back; forget it rather than hand back a ghost
98
+ index.delete(aliasName);
99
+ return null;
100
+ }
101
+ return node;
102
+ }
103
+ /** Record a newly created alias. */
104
+ function noteAliasAdded(addressSpace, category, aliasName, nodeId) {
105
+ indexOf(addressSpace, category).set(aliasName, nodeId);
106
+ }
107
+ /** Record a removed alias. */
108
+ function noteAliasRemoved(addressSpace, category, aliasName) {
109
+ indexOf(addressSpace, category).delete(aliasName);
110
+ }
111
+ /**
112
+ * Forget a category's index, so it is rebuilt from the address space on next
113
+ * use. Needed only if a Server adds or removes aliases without going through
114
+ * {@link addAlias} / {@link removeAlias}.
115
+ */
116
+ function invalidateAliasIndex(category) {
117
+ indexes.delete(category);
118
+ }
119
+ //# sourceMappingURL=alias_index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alias_index.js","sourceRoot":"","sources":["../source/alias_index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @module node-opcua-alias-name-server
3
+ *
4
+ * Persistence for `LastChange` (OPC 10000-17 clause 6.3.1).
5
+ *
6
+ * Clause 6.3.1 is blunt about why this exists: *"The LastChange shall be
7
+ * persisted. A Client that detects a LastChange that is older than what it has
8
+ * cached, shall clear all cached AliasNameCategories and related AliasNames."*
9
+ *
10
+ * So a restart that reset `LastChange` to zero would not merely lose
11
+ * information — it would order every connected Client to throw away a cache
12
+ * that is still perfectly valid, silently and on every restart. That is a
13
+ * Server-side bug whose only symptom is remote.
14
+ *
15
+ * The archive is plain JSON: a version and a map of category NodeId to
16
+ * VersionTime. There is nothing secret in it, so unlike the RoleSet archive it
17
+ * is not encrypted — it is a handful of integers describing when things last
18
+ * changed. Writes are atomic (temp file + rename) so a crash cannot leave a
19
+ * half-written archive, which would be worse than no archive at all.
20
+ */
21
+ /** Bumped when the on-disk shape changes incompatibly. */
22
+ export declare const ALIAS_NAME_ARCHIVE_VERSION = 1;
23
+ /** The persisted form of a Server's `LastChange` state. */
24
+ export interface AliasNameArchive {
25
+ version: number;
26
+ /** Category NodeId (as a string) to VersionTime (UInt32 seconds since 2000-01-01Z). */
27
+ lastChange: Record<string, number>;
28
+ }
29
+ /**
30
+ * Read an archive, or return `null` when there is none.
31
+ *
32
+ * A missing file is normal — the first start. A corrupt or
33
+ * future-versioned file is **not** silently ignored: continuing with a zeroed
34
+ * `LastChange` is exactly the cache-clearing bug persistence exists to prevent,
35
+ * so the caller is told rather than left to discover it from a Client.
36
+ */
37
+ export declare function readAliasNameArchive(path: string): Promise<AliasNameArchive | null>;
38
+ /**
39
+ * Write an archive atomically.
40
+ *
41
+ * Temp file plus rename, so a crash mid-write leaves either the previous
42
+ * archive or the new one, never a truncated file that would fail to parse on
43
+ * the next start.
44
+ */
45
+ export declare function writeAliasNameArchive(path: string, archive: AliasNameArchive): Promise<void>;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * @module node-opcua-alias-name-server
4
+ *
5
+ * Persistence for `LastChange` (OPC 10000-17 clause 6.3.1).
6
+ *
7
+ * Clause 6.3.1 is blunt about why this exists: *"The LastChange shall be
8
+ * persisted. A Client that detects a LastChange that is older than what it has
9
+ * cached, shall clear all cached AliasNameCategories and related AliasNames."*
10
+ *
11
+ * So a restart that reset `LastChange` to zero would not merely lose
12
+ * information — it would order every connected Client to throw away a cache
13
+ * that is still perfectly valid, silently and on every restart. That is a
14
+ * Server-side bug whose only symptom is remote.
15
+ *
16
+ * The archive is plain JSON: a version and a map of category NodeId to
17
+ * VersionTime. There is nothing secret in it, so unlike the RoleSet archive it
18
+ * is not encrypted — it is a handful of integers describing when things last
19
+ * changed. Writes are atomic (temp file + rename) so a crash cannot leave a
20
+ * half-written archive, which would be worse than no archive at all.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.ALIAS_NAME_ARCHIVE_VERSION = void 0;
24
+ exports.readAliasNameArchive = readAliasNameArchive;
25
+ exports.writeAliasNameArchive = writeAliasNameArchive;
26
+ const node_fs_1 = require("node:fs");
27
+ const node_path_1 = require("node:path");
28
+ /** Bumped when the on-disk shape changes incompatibly. */
29
+ exports.ALIAS_NAME_ARCHIVE_VERSION = 1;
30
+ /**
31
+ * Read an archive, or return `null` when there is none.
32
+ *
33
+ * A missing file is normal — the first start. A corrupt or
34
+ * future-versioned file is **not** silently ignored: continuing with a zeroed
35
+ * `LastChange` is exactly the cache-clearing bug persistence exists to prevent,
36
+ * so the caller is told rather than left to discover it from a Client.
37
+ */
38
+ async function readAliasNameArchive(path) {
39
+ let raw;
40
+ try {
41
+ raw = await node_fs_1.promises.readFile(path, "utf-8");
42
+ }
43
+ catch (err) {
44
+ if (err.code === "ENOENT") {
45
+ return null;
46
+ }
47
+ throw err;
48
+ }
49
+ let parsed;
50
+ try {
51
+ parsed = JSON.parse(raw);
52
+ }
53
+ catch {
54
+ throw new Error(`readAliasNameArchive: ${path} is not valid JSON. Delete it to start fresh, but note that a Client that has cached AliasNames will be told to clear its cache.`);
55
+ }
56
+ const archive = parsed;
57
+ if (archive.version !== exports.ALIAS_NAME_ARCHIVE_VERSION) {
58
+ throw new Error(`readAliasNameArchive: ${path} has version ${String(archive.version)}, expected ${exports.ALIAS_NAME_ARCHIVE_VERSION}`);
59
+ }
60
+ return { version: archive.version, lastChange: archive.lastChange ?? {} };
61
+ }
62
+ /**
63
+ * Write an archive atomically.
64
+ *
65
+ * Temp file plus rename, so a crash mid-write leaves either the previous
66
+ * archive or the new one, never a truncated file that would fail to parse on
67
+ * the next start.
68
+ */
69
+ async function writeAliasNameArchive(path, archive) {
70
+ await node_fs_1.promises.mkdir((0, node_path_1.dirname)(path), { recursive: true });
71
+ const temporaryPath = `${path}.tmp`;
72
+ await node_fs_1.promises.writeFile(temporaryPath, JSON.stringify(archive, null, 2), "utf-8");
73
+ await node_fs_1.promises.rename(temporaryPath, path);
74
+ }
75
+ //# sourceMappingURL=alias_name_archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alias_name_archive.js","sourceRoot":"","sources":["../source/alias_name_archive.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAuBH,oDA2BC;AASD,sDAKC;AA9DD,qCAAyC;AACzC,yCAAoC;AAEpC,0DAA0D;AAC7C,QAAA,0BAA0B,GAAG,CAAC,CAAC;AAS5C;;;;;;;GAOG;AACI,KAAK,UAAU,oBAAoB,CAAC,IAAY;IACnD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACD,GAAG,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACnD,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,GAAG,CAAC;IACd,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACD,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACL,MAAM,IAAI,KAAK,CACX,yBAAyB,IAAI,kIAAkI,CAClK,CAAC;IACN,CAAC;IAED,MAAM,OAAO,GAAG,MAAmC,CAAC;IACpD,IAAI,OAAO,CAAC,OAAO,KAAK,kCAA0B,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CACX,yBAAyB,IAAI,gBAAgB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,kCAA0B,EAAE,CACjH,CAAC;IACN,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,qBAAqB,CAAC,IAAY,EAAE,OAAyB;IAC/E,MAAM,kBAAE,CAAC,KAAK,CAAC,IAAA,mBAAO,EAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,aAAa,GAAG,GAAG,IAAI,MAAM,CAAC;IACpC,MAAM,kBAAE,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC7E,MAAM,kBAAE,CAAC,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @module node-opcua-alias-name-server
3
+ *
4
+ * Binding the Methods of a single `AliasNameCategoryType` instance, and creating
5
+ * new categories at runtime.
6
+ *
7
+ * This is the one binding path. `installAliasNamesOnAddressSpace` calls
8
+ * {@link bindAliasCategory} in its loop rather than doing the work itself, so a
9
+ * category created after installation cannot end up bound differently from one
10
+ * that was there at install time — or, worse, not bound at all. An unbound
11
+ * MANDATORY `FindAlias` is the exact defect this package exists to remove, and
12
+ * it should not be able to reappear at runtime.
13
+ */
14
+ import type { IAddressSpace, ISessionContext, UAMethod, UAObject, UAObjectType, UAVariable } from "node-opcua-address-space-base";
15
+ import type { IAliasStore } from "node-opcua-alias-name-common";
16
+ import { type NodeId } from "node-opcua-nodeid";
17
+ import type { RolePermissionTypeOptions } from "node-opcua-types";
18
+ import { type AliasComparator } from "./bind_find_alias.js";
19
+ import { type LastChangeTracker } from "./last_change.js";
20
+ /** Everything a category needs in order to answer `FindAlias`. */
21
+ export interface BindAliasCategoryOptions {
22
+ /** Where aliases come from. */
23
+ store: IAliasStore;
24
+ /** Result cap per call (clause 6.3.2 Table 4). */
25
+ maxResults: number;
26
+ /** Also bind `FindAliasVerbose`, adding the Method if the instance lacks it. */
27
+ verbose?: boolean;
28
+ /** Result ordering (clause 6.3.2, "best match first"). */
29
+ comparator?: AliasComparator;
30
+ /** Read gate; see {@link FindAliasBindingOptions.isReadAllowed}. */
31
+ isReadAllowed?: (context: ISessionContext, categoryNodeId: NodeId) => boolean | Promise<boolean>;
32
+ /**
33
+ * Write gate for the configuration Methods, mirroring `isReadAllowed`.
34
+ * Defaults to denying everyone.
35
+ */
36
+ isWriteAllowed?: (context: ISessionContext, categoryNodeId: NodeId) => boolean | Promise<boolean>;
37
+ /** Also add and bind `AddAliasesToCategory` / `DeleteAliasesFromCategory`. */
38
+ configurationMethods?: boolean;
39
+ /** Ensure the category carries a `LastChange` Property (clause 6.3.1). */
40
+ lastChangeProperty?: boolean;
41
+ /** Called after a configuration Method changed the category. */
42
+ onChanged?: (categoryNodeId: NodeId) => void | Promise<void>;
43
+ }
44
+ /**
45
+ * Bind `FindAlias` — and, when `verbose`, `FindAliasVerbose` — on one
46
+ * `AliasNameCategoryType` instance.
47
+ *
48
+ * Safe to call on a category that is already bound: `bindMethod` replaces the
49
+ * handler, and the optional Method is only added when it is missing.
50
+ *
51
+ * Use this for a category created after `installAliasNames` has run. The options
52
+ * that installation used are on {@link InstallAliasNamesResult.bindingOptions},
53
+ * so a caller does not have to reassemble them and risk binding a late category
54
+ * with a different store or a different result cap.
55
+ */
56
+ export declare function bindAliasCategory(addressSpace: IAddressSpace, category: UAObject, options: BindAliasCategoryOptions): void;
57
+ /**
58
+ * Ensure a category has a `LastChange` Property.
59
+ *
60
+ * `LastChange` is Optional on `AliasNameCategoryType` and the shipped nodeset
61
+ * instantiates it only on the `Aliases` root, which clause 9.2 makes mandatory.
62
+ * Adding it to every category is conformant — Optional means may, not must not —
63
+ * and it is what makes the clause 6.3.1 rollup observable: without it, a Client
64
+ * watching one branch has nothing to watch.
65
+ */
66
+ export declare function ensureLastChangeProperty(addressSpace: IAddressSpace, category: UAObject): UAVariable | null;
67
+ export interface AddAliasCategoryOptions extends Partial<BindAliasCategoryOptions> {
68
+ /**
69
+ * Namespace for the new category's BrowseName. Defaults to the Server's own.
70
+ */
71
+ namespaceIndex?: number;
72
+ /**
73
+ * NodeId for the new category. Defaults to a server-assigned one, which is
74
+ * correct for any category the specification does not name.
75
+ */
76
+ nodeId?: NodeId;
77
+ /**
78
+ * ObjectType to instantiate. Defaults to `AliasNameCategoryType`; a subtype
79
+ * is accepted, since discovery and binding both already handle subtypes.
80
+ */
81
+ categoryType?: UAObjectType | NodeId;
82
+ /**
83
+ * RolePermissions for the new category.
84
+ *
85
+ * Worth setting deliberately. Namespace 0 declares no `RolePermissions` on
86
+ * any Part 17 node, so a category created without them inherits the
87
+ * namespace default silently — which is a decision either way, just an
88
+ * invisible one.
89
+ */
90
+ rolePermissions?: RolePermissionTypeOptions[];
91
+ }
92
+ /**
93
+ * Create a vendor `AliasNameCategoryType` instance under `parent` **and bind it**.
94
+ *
95
+ * Creating one by hand means instantiating the type, wiring the `Organizes`
96
+ * reference and then remembering to bind — and a category whose `FindAlias` is
97
+ * unbound fails conformance silently. This does all three.
98
+ *
99
+ * When `installAliasNames` has already run on this address space, the binding
100
+ * options it used are reused unless overridden, so a category added at runtime
101
+ * behaves exactly like one that was present at install time. Pass a `store`
102
+ * explicitly if installation has not run yet.
103
+ */
104
+ export declare function addAliasCategory(addressSpace: IAddressSpace, parent: UAObject | NodeId, browseName: string, options?: AddAliasCategoryOptions): UAObject;
105
+ /**
106
+ * Remove a category, and decide what happens to what it Organizes.
107
+ *
108
+ * The specification does not say, so the rule is stated here rather than left to
109
+ * whatever `deleteNode` happens to do:
110
+ *
111
+ * - **`reparent`** (the default) moves the category's aliases and subcategories
112
+ * to its parent before deleting it. Nothing disappears, so a Client that had
113
+ * resolved an alias keeps resolving it — the alias Object keeps its NodeId,
114
+ * and clause 6.2 makes a NodeId change mean "this is a different alias".
115
+ * - **`cascade`** deletes them with it. Correct when the category *is* the
116
+ * thing being retired, such as a tenant being removed.
117
+ *
118
+ * Refuses to remove one of the three well-known categories, which clause 9
119
+ * requires a Server to have.
120
+ *
121
+ * @returns the aliases and subcategories that were re-parented, or deleted.
122
+ */
123
+ export declare function removeAliasCategory(addressSpace: IAddressSpace, category: UAObject | NodeId, options?: {
124
+ orphans?: "reparent" | "cascade";
125
+ }): {
126
+ moved: NodeId[];
127
+ deleted: NodeId[];
128
+ };
129
+ /**
130
+ * Find a Method on a category by its MethodDeclarationId, falling back to the
131
+ * BrowseName.
132
+ *
133
+ * The declaration id is the reliable key: a Server may publish the Method under
134
+ * a localised DisplayName, and the BrowseName is only unique within the
135
+ * namespace. The fallback covers instances built in code, which do not always
136
+ * carry a `methodDeclarationId`.
137
+ */
138
+ export declare function findMethodByDeclaration(category: UAObject, declarationId: NodeId, browseName: string): UAMethod | null;
139
+ /**
140
+ * Ensure an optional Method exists on a category, adding it when the nodeset
141
+ * only declared it on the type.
142
+ *
143
+ * The shipped `Opc.Ua.NodeSet2.xml` declares `FindAliasVerbose`,
144
+ * `AddAliasesToCategory` and `DeleteAliasesFromCategory` on
145
+ * `AliasNameCategoryType` but instantiates none of them on `Aliases`,
146
+ * `TagVariables` or `Topics`. Upstream nonetheless reserves fixed NodeIds for
147
+ * those instances, so where one exists it is used in preference to a
148
+ * server-assigned id; an aggregating Server then sees the NodeId it expects.
149
+ */
150
+ export declare function ensureOptionalMethod(addressSpace: IAddressSpace, category: UAObject, name: "FindAliasVerbose" | "AddAliasesToCategory" | "DeleteAliasesFromCategory"): UAMethod | null;
151
+ /**
152
+ * Marks an address space as already carrying AliasName bindings, so a second
153
+ * `installAliasNames` is a no-op rather than a double binding.
154
+ */
155
+ export declare const INSTALLED: unique symbol;
156
+ /** What installation recorded on the address space, if it has run. */
157
+ export interface InstalledAliasNames {
158
+ store: IAliasStore;
159
+ categories: NodeId[];
160
+ bindingOptions: BindAliasCategoryOptions;
161
+ /** Keeps `LastChange` correct across the hierarchy (clause 6.3.1). */
162
+ lastChange?: LastChangeTracker;
163
+ }
164
+ /**
165
+ * What `installAliasNames` recorded on this address space, or undefined if it
166
+ * has not run.
167
+ *
168
+ * Exposed so a caller can rebind a late category with exactly the options
169
+ * installation used, without having to keep the install result around.
170
+ */
171
+ export declare function getInstalledAliasNames(addressSpace: IAddressSpace): InstalledAliasNames | undefined;
172
+ /** Record the installation on the address space. */
173
+ export declare function setInstalledAliasNames(addressSpace: IAddressSpace, value: InstalledAliasNames): void;