node-opcua-address-space 2.186.17 → 2.187.1
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/api/address_space_public.ts +16 -0
- package/api/helpers/resolve_opaque_on_address_space.ts +13 -1
- package/api/index.ts +14 -3
- package/api/loader/store_record_applier.ts +267 -0
- package/api/session_context.ts +116 -27
- package/dist/api/address_space_public.d.ts +14 -0
- package/dist/api/address_space_public.d.ts.map +1 -1
- package/dist/api/address_space_public.js +3 -0
- package/dist/api/address_space_public.js.map +1 -1
- package/dist/api/helpers/resolve_opaque_on_address_space.d.ts +6 -0
- package/dist/api/helpers/resolve_opaque_on_address_space.d.ts.map +1 -1
- package/dist/api/helpers/resolve_opaque_on_address_space.js +12 -1
- package/dist/api/helpers/resolve_opaque_on_address_space.js.map +1 -1
- package/dist/api/index.d.ts +4 -3
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +4 -3
- package/dist/api/index.js.map +1 -1
- package/dist/api/loader/store_record_applier.d.ts +49 -0
- package/dist/api/loader/store_record_applier.d.ts.map +1 -0
- package/dist/api/loader/store_record_applier.js +235 -0
- package/dist/api/loader/store_record_applier.js.map +1 -0
- package/dist/api/session_context.d.ts +10 -0
- package/dist/api/session_context.d.ts.map +1 -1
- package/dist/api/session_context.js +100 -27
- package/dist/api/session_context.js.map +1 -1
- package/dist/impl/address_space.d.ts +2 -0
- package/dist/impl/address_space.d.ts.map +1 -1
- package/dist/impl/address_space.js +12 -2
- package/dist/impl/address_space.js.map +1 -1
- package/dist/impl/base_node_impl.d.ts +12 -0
- package/dist/impl/base_node_impl.d.ts.map +1 -1
- package/dist/impl/base_node_impl.js +35 -29
- package/dist/impl/base_node_impl.js.map +1 -1
- package/dist/impl/base_node_private.d.ts +5 -5
- package/dist/impl/base_node_private.d.ts.map +1 -1
- package/dist/impl/base_node_private.js +11 -5
- package/dist/impl/base_node_private.js.map +1 -1
- package/dist/impl/extension_object_array_node.d.ts +4 -2
- package/dist/impl/extension_object_array_node.d.ts.map +1 -1
- package/dist/impl/extension_object_array_node.js +5 -2
- package/dist/impl/extension_object_array_node.js.map +1 -1
- package/dist/impl/historical_access/address_space_historical_data_node.d.ts +26 -2
- package/dist/impl/historical_access/address_space_historical_data_node.d.ts.map +1 -1
- package/dist/impl/historical_access/address_space_historical_data_node.js +39 -0
- package/dist/impl/historical_access/address_space_historical_data_node.js.map +1 -1
- package/dist/impl/namespace_impl.d.ts.map +1 -1
- package/dist/impl/namespace_impl.js +3 -1
- package/dist/impl/namespace_impl.js.map +1 -1
- package/dist/impl/nodeid_manager.d.ts.map +1 -1
- package/dist/impl/nodeid_manager.js +10 -7
- package/dist/impl/nodeid_manager.js.map +1 -1
- package/dist/impl/nodeset_tools/construct_namespace_dependency.d.ts.map +1 -1
- package/dist/impl/nodeset_tools/construct_namespace_dependency.js +28 -0
- package/dist/impl/nodeset_tools/construct_namespace_dependency.js.map +1 -1
- package/dist/impl/reference_impl.d.ts +2 -1
- package/dist/impl/reference_impl.d.ts.map +1 -1
- package/dist/impl/reference_impl.js +6 -5
- package/dist/impl/reference_impl.js.map +1 -1
- package/dist/impl/reference_index.d.ts +22 -0
- package/dist/impl/reference_index.d.ts.map +1 -0
- package/dist/impl/reference_index.js +104 -0
- package/dist/impl/reference_index.js.map +1 -0
- package/dist/impl/ua_variable_impl.d.ts +10 -0
- package/dist/impl/ua_variable_impl.d.ts.map +1 -1
- package/dist/impl/ua_variable_impl.js +152 -106
- package/dist/impl/ua_variable_impl.js.map +1 -1
- package/dist/tsconfig_base.tsbuildinfo +1 -1
- package/distNodeJS/generate_address_space.d.ts +6 -1
- package/distNodeJS/generate_address_space.d.ts.map +1 -1
- package/distNodeJS/generate_address_space.js +33 -1
- package/distNodeJS/generate_address_space.js.map +1 -1
- package/impl/address_space.ts +16 -2
- package/impl/base_node_impl.ts +52 -30
- package/impl/base_node_private.ts +16 -10
- package/impl/extension_object_array_node.ts +12 -3
- package/impl/historical_access/address_space_historical_data_node.ts +64 -1
- package/impl/namespace_impl.ts +3 -1
- package/impl/nodeid_manager.ts +10 -7
- package/impl/nodeset_tools/construct_namespace_dependency.ts +23 -1
- package/impl/reference_impl.ts +6 -5
- package/impl/reference_index.ts +116 -0
- package/impl/ua_variable_impl.ts +203 -115
- package/package.json +34 -33
- package/source_nodejs/generate_address_space.ts +46 -1
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
* @module node-opcua-address-space
|
|
3
3
|
*/
|
|
4
4
|
import type { IAddressSpace } from "node-opcua-address-space-base";
|
|
5
|
+
import { type StoreAddressSpace, type StoreAddressSpaceOptions, StoreServices } from "node-opcua-address-space-store";
|
|
5
6
|
import { AddressSpaceImpl } from "../impl/address_space.js";
|
|
6
7
|
import type { IHistorizerFactory } from "./address_space_ts.js";
|
|
7
8
|
import type { Namespace } from "./namespace.js";
|
|
8
9
|
import type { UARootFolder } from "./ua_root_folder.js";
|
|
9
10
|
|
|
11
|
+
/** the published names of the compact address space and its options */
|
|
12
|
+
export type CompactAddressSpace = StoreAddressSpace;
|
|
13
|
+
export type CompactAddressSpaceOptions = StoreAddressSpaceOptions;
|
|
14
|
+
/** the Read, Write, Browse and TranslateBrowsePaths services on a compact address space */
|
|
15
|
+
export const CompactAddressSpaceServices = StoreServices;
|
|
16
|
+
export type CompactAddressSpaceServices = StoreServices;
|
|
17
|
+
|
|
10
18
|
/**
|
|
11
19
|
* The address space: the set of nodes a server exposes, and the namespaces they live in.
|
|
12
20
|
*
|
|
@@ -40,6 +48,14 @@ export interface AddressSpaceConstructor {
|
|
|
40
48
|
/** build an empty address space */
|
|
41
49
|
create(): AddressSpace;
|
|
42
50
|
|
|
51
|
+
/**
|
|
52
|
+
* build an empty compact address space: the nodes in typed columns, node objects built
|
|
53
|
+
* on demand. Cheaper by far for a large model, and not yet an {@link AddressSpace}: it
|
|
54
|
+
* answers finds, browses, attribute and value reads, bindings and writes, and a server
|
|
55
|
+
* cannot be built on it yet.
|
|
56
|
+
*/
|
|
57
|
+
createCompact(options?: CompactAddressSpaceOptions): CompactAddressSpace;
|
|
58
|
+
|
|
43
59
|
/**
|
|
44
60
|
* How historized variables get their historian. Assigning here replaces the default that
|
|
45
61
|
* the historical-access code installs on first use.
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import type { IAddressSpace } from "node-opcua-address-space-base";
|
|
2
2
|
import { resolveDynamicExtensionObject } from "node-opcua-client-dynamic-extension-object";
|
|
3
|
-
import { Variant } from "node-opcua-variant";
|
|
3
|
+
import { DataType, Variant } from "node-opcua-variant";
|
|
4
4
|
import { ensureDatatypeExtracted } from "../loader/ensure_datatype_extracted.js";
|
|
5
5
|
import { PseudoSession } from "../pseudo_session.js";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* true when the variant may hold an opaque structure to resolve: only an ExtensionObject, or a
|
|
9
|
+
* Variant nested in one, can. Any other value is left as it is by resolveDynamicExtensionObject,
|
|
10
|
+
* so a Write of a number or a string needs neither a PseudoSession nor the DataType extraction.
|
|
11
|
+
*/
|
|
12
|
+
export function mayHoldOpaqueStructure(variant: Variant | null | undefined): boolean {
|
|
13
|
+
return !!variant && (variant.dataType === DataType.ExtensionObject || variant.dataType === DataType.Variant);
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
export async function resolveOpaqueOnAddressSpace(
|
|
8
17
|
addressSpace: IAddressSpace,
|
|
9
18
|
variants: (Variant | null) | (Variant | null)[]
|
|
@@ -11,6 +20,9 @@ export async function resolveOpaqueOnAddressSpace(
|
|
|
11
20
|
if (!variants) {
|
|
12
21
|
return;
|
|
13
22
|
}
|
|
23
|
+
if (variants instanceof Variant ? !mayHoldOpaqueStructure(variants) : !variants.some(mayHoldOpaqueStructure)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
14
26
|
const session = new PseudoSession(addressSpace);
|
|
15
27
|
const extraDataTypeManager = await ensureDatatypeExtracted(addressSpace);
|
|
16
28
|
if (variants instanceof Variant) {
|
package/api/index.ts
CHANGED
|
@@ -91,7 +91,12 @@ export * from "../impl/event_data.js";
|
|
|
91
91
|
/** Building and maintaining a variable whose value is an array of extension objects. */
|
|
92
92
|
export { addElement, bindExtObjArrayNode, createExtObjArrayNode, removeElement } from "../impl/extension_object_array_node.js";
|
|
93
93
|
/** The default historian, installed through {@link AddressSpace.historizerFactory}. */
|
|
94
|
-
export {
|
|
94
|
+
export {
|
|
95
|
+
createDetachedHistorian,
|
|
96
|
+
historyReadThrough,
|
|
97
|
+
type IHistoryReadTarget,
|
|
98
|
+
VariableHistorian
|
|
99
|
+
} from "../impl/historical_access/address_space_historical_data_node.js";
|
|
95
100
|
export {
|
|
96
101
|
installReadProcessedDetails,
|
|
97
102
|
type ReadProcessedDetailsFunc
|
|
@@ -135,7 +140,7 @@ export * from "./helpers/call_helpers.js";
|
|
|
135
140
|
export * from "./helpers/dump_tools.js";
|
|
136
141
|
export * from "./helpers/ensure_secure_access.js";
|
|
137
142
|
export * from "./helpers/make_optionals_map.js";
|
|
138
|
-
export { resolveOpaqueOnAddressSpace } from "./helpers/resolve_opaque_on_address_space.js";
|
|
143
|
+
export { mayHoldOpaqueStructure, resolveOpaqueOnAddressSpace } from "./helpers/resolve_opaque_on_address_space.js";
|
|
139
144
|
export * from "./interfaces/alarms_and_conditions/condition_info_i.js";
|
|
140
145
|
export * from "./interfaces/alarms_and_conditions/condition_snapshot.js";
|
|
141
146
|
export * from "./interfaces/alarms_and_conditions/instantiate_alarm_condition_options.js";
|
|
@@ -236,12 +241,18 @@ export type {
|
|
|
236
241
|
NodesetSource,
|
|
237
242
|
NodesetSourceKind
|
|
238
243
|
} from "./loader/nodeset_source.js";
|
|
239
|
-
export { sha256Hex } from "./loader/nodeset_source.js";
|
|
244
|
+
export { openNodesetSource, sha256Hex } from "./loader/nodeset_source.js";
|
|
240
245
|
export * from "./loader/nodeset_source_helpers.js";
|
|
241
246
|
export { type NodesetToImageOptions, nodesetToImage } from "./loader/nodeset_to_image.js";
|
|
242
247
|
export { headerComplete, parseDependencies, sliceHeader } from "./loader/nodeset_xml_header.js";
|
|
243
248
|
export { makeXmlNodesetRecordReader, type XmlNodesetRecordReader, xmlNodesetRecords } from "./loader/nodeset_xml_producer.js";
|
|
244
249
|
export * from "./loader/register_node_promoter.js";
|
|
250
|
+
export {
|
|
251
|
+
type CompactRecordConsumer,
|
|
252
|
+
compactRecordConsumer,
|
|
253
|
+
StoreRecordApplier,
|
|
254
|
+
type StoreRecordApplierOptions
|
|
255
|
+
} from "./loader/store_record_applier.js";
|
|
245
256
|
export * from "./namespace.js";
|
|
246
257
|
export type { INamespaceAlarmAndCondition } from "./namespace_alarm_and_condition.js";
|
|
247
258
|
export * from "./namespace_data_access.js";
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-address-space
|
|
3
|
+
*
|
|
4
|
+
* The nodeset records written straight into a compact store: no node object is created.
|
|
5
|
+
* Same producer as the object applier (NodesetRecordApplier), other sink.
|
|
6
|
+
*/
|
|
7
|
+
import { type CompactStore, NO_NODE, type RolePermissionEntry, type StoreAddressSpace } from "node-opcua-address-space-store";
|
|
8
|
+
import { NodeClass } from "node-opcua-data-model";
|
|
9
|
+
import { NodeId } from "node-opcua-nodeid";
|
|
10
|
+
import { DataType, VariantArrayType, type VariantOptions } from "node-opcua-variant";
|
|
11
|
+
import type { NodeSetPermissionsPolicy } from "../interfaces/nodeset_loader_options.js";
|
|
12
|
+
import {
|
|
13
|
+
type NodesetHeaderRecord,
|
|
14
|
+
type NodesetNodeRecord,
|
|
15
|
+
type NodesetRecord,
|
|
16
|
+
type NodesetRecordConsumer,
|
|
17
|
+
XmlExtensionObjectFragment
|
|
18
|
+
} from "./nodeset_record.js";
|
|
19
|
+
|
|
20
|
+
const UA_NAMESPACE_URI = "http://opcfoundation.org/UA/";
|
|
21
|
+
|
|
22
|
+
/** who hands out namespace indexes: the address space, or a plain table in a test */
|
|
23
|
+
export interface NamespaceRegistry {
|
|
24
|
+
/** the index of a namespace, registering it when it is new */
|
|
25
|
+
indexOf(namespaceUri: string): number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** the same two policies as the object loader, with the same defaults */
|
|
29
|
+
export interface StoreRecordApplierOptions {
|
|
30
|
+
/** the nodeset's RolePermissions: kept (the default) or dropped */
|
|
31
|
+
permissions?: NodeSetPermissionsPolicy;
|
|
32
|
+
/** the nodeset's AccessRestrictions: kept, or dropped (the default) */
|
|
33
|
+
accessRestrictions?: NodeSetPermissionsPolicy;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class StoreRecordApplier implements NodesetRecordConsumer {
|
|
37
|
+
// per document: the file's namespace table to the store's
|
|
38
|
+
#table: number[] = [];
|
|
39
|
+
#headerSeen = false;
|
|
40
|
+
#nodeCount = 0;
|
|
41
|
+
#pendingValues = 0;
|
|
42
|
+
#deferredValues = 0;
|
|
43
|
+
// a type definition or data type declared before its node exists: set at finish()
|
|
44
|
+
#deferredTypeDefinitions: [number, NodeId][] = [];
|
|
45
|
+
#deferredDataTypes: [number, NodeId][] = [];
|
|
46
|
+
#deferredParents: [number, NodeId][] = [];
|
|
47
|
+
readonly #store: CompactStore;
|
|
48
|
+
readonly #namespaces: NamespaceRegistry;
|
|
49
|
+
readonly #loadTime = Date.now();
|
|
50
|
+
readonly #keepPermissions: boolean;
|
|
51
|
+
readonly #keepAccessRestrictions: boolean;
|
|
52
|
+
|
|
53
|
+
constructor(store: CompactStore, namespaces: NamespaceRegistry, options: StoreRecordApplierOptions = {}) {
|
|
54
|
+
this.#store = store;
|
|
55
|
+
this.#namespaces = namespaces;
|
|
56
|
+
this.#keepPermissions = (options.permissions ?? "apply") === "apply";
|
|
57
|
+
this.#keepAccessRestrictions = (options.accessRestrictions ?? "ignore") === "apply";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** nodes applied by this applier */
|
|
61
|
+
public get nodeCount(): number {
|
|
62
|
+
return this.#nodeCount;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public apply(record: NodesetRecord): void {
|
|
66
|
+
if (record.kind === "header") {
|
|
67
|
+
this.#applyHeader(record);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!this.#headerSeen) {
|
|
71
|
+
throw new Error("StoreRecordApplier: a node record came before the header record");
|
|
72
|
+
}
|
|
73
|
+
this.#applyNode(record);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** at the end of a document, or of a set of documents: resolve the references that waited */
|
|
77
|
+
public finish(): { unresolved: number } {
|
|
78
|
+
const store = this.#store;
|
|
79
|
+
const unresolved = store.resolvePending();
|
|
80
|
+
for (const [i, nodeId] of this.#deferredTypeDefinitions) {
|
|
81
|
+
const t = store.find(nodeId);
|
|
82
|
+
if (t !== NO_NODE) store.nodes.setTypeDefinition(i, t);
|
|
83
|
+
}
|
|
84
|
+
for (const [i, nodeId] of this.#deferredDataTypes) {
|
|
85
|
+
const t = store.find(nodeId);
|
|
86
|
+
if (t !== NO_NODE) store.nodes.setDataType(i, t);
|
|
87
|
+
}
|
|
88
|
+
for (const [i, nodeId] of this.#deferredParents) {
|
|
89
|
+
const t = store.find(nodeId);
|
|
90
|
+
if (t !== NO_NODE) store.nodes.setParent(i, t);
|
|
91
|
+
}
|
|
92
|
+
this.#deferredTypeDefinitions = [];
|
|
93
|
+
this.#deferredDataTypes = [];
|
|
94
|
+
this.#deferredParents = [];
|
|
95
|
+
store.finish();
|
|
96
|
+
return { unresolved: unresolved.length };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#applyHeader(header: NodesetHeaderRecord): void {
|
|
100
|
+
this.#headerSeen = true;
|
|
101
|
+
this.#table = [];
|
|
102
|
+
for (const model of header.models) {
|
|
103
|
+
this.#namespaces.indexOf(model.modelUri);
|
|
104
|
+
}
|
|
105
|
+
if (header.models.length === 0) {
|
|
106
|
+
for (const uri of header.namespaceUris) {
|
|
107
|
+
this.#namespaces.indexOf(uri);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// the file's table: the UA namespace, then the declared URIs in order; a repeat takes no slot
|
|
111
|
+
const seen = new Set<string>();
|
|
112
|
+
for (const uri of [UA_NAMESPACE_URI, ...header.namespaceUris]) {
|
|
113
|
+
if (seen.has(uri)) continue;
|
|
114
|
+
seen.add(uri);
|
|
115
|
+
this.#table.push(this.#namespaces.indexOf(uri));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#translateNamespace(fileIndex: number): number {
|
|
120
|
+
const index = this.#table[fileIndex];
|
|
121
|
+
if (index === undefined) {
|
|
122
|
+
throw new Error(`StoreRecordApplier: no namespace for file index ${fileIndex}`);
|
|
123
|
+
}
|
|
124
|
+
return index;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#translate(fileLocal: NodeId): NodeId {
|
|
128
|
+
const namespace = this.#translateNamespace(fileLocal.namespace);
|
|
129
|
+
return namespace === fileLocal.namespace ? fileLocal : new NodeId(fileLocal.identifierType, fileLocal.value, namespace);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#applyNode(record: NodesetNodeRecord): void {
|
|
133
|
+
const store = this.#store;
|
|
134
|
+
const nodeId = this.#translate(record.nodeId);
|
|
135
|
+
const i = store.addNode({
|
|
136
|
+
nodeId,
|
|
137
|
+
nodeClass: record.nodeClass,
|
|
138
|
+
browseName: record.browseName.name ?? "",
|
|
139
|
+
browseNameNamespace: this.#translateNamespace(record.browseName.namespaceIndex),
|
|
140
|
+
displayName: record.displayName && record.displayName !== record.browseName.name ? record.displayName : null,
|
|
141
|
+
description: record.description ?? null,
|
|
142
|
+
valueRank: record.valueRank,
|
|
143
|
+
// the document gives the levels as numbers; CurrentRead when it says nothing, and the
|
|
144
|
+
// user level follows the node's unless declared (same reading as the object applier)
|
|
145
|
+
accessLevel: parseAccessLevel(record.accessLevel, 1),
|
|
146
|
+
userAccessLevel: record.userAccessLevel ? parseAccessLevel(record.userAccessLevel, 1) : undefined,
|
|
147
|
+
minimumSamplingInterval: record.minimumSamplingInterval,
|
|
148
|
+
historizing: record.historizing,
|
|
149
|
+
eventNotifier: record.eventNotifier,
|
|
150
|
+
isAbstract: record.isAbstract,
|
|
151
|
+
symmetric: record.symmetric,
|
|
152
|
+
inverseName: record.inverseName ?? null,
|
|
153
|
+
containsNoLoops: record.containsNoLoops,
|
|
154
|
+
accessRestrictions: this.#keepAccessRestrictions ? parseAccessRestrictions(record.accessRestrictions) : undefined,
|
|
155
|
+
rolePermissions: this.#keepPermissions ? this.#rolePermissions(record) : undefined
|
|
156
|
+
});
|
|
157
|
+
this.#nodeCount++;
|
|
158
|
+
for (const reference of record.references) {
|
|
159
|
+
const referenceType = this.#translate(reference.referenceType);
|
|
160
|
+
const target = this.#translate(reference.nodeId);
|
|
161
|
+
store.addReference(i, referenceType, reference.isForward, target);
|
|
162
|
+
// the type definition and the data type are also columns, kept resolved by index
|
|
163
|
+
if (reference.isForward && referenceType.namespace === 0 && referenceType.value === 40 /* HasTypeDefinition */) {
|
|
164
|
+
const t = store.find(target);
|
|
165
|
+
if (t !== NO_NODE) store.nodes.setTypeDefinition(i, t);
|
|
166
|
+
else this.#deferredTypeDefinitions.push([i, target]);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (record.dataType) {
|
|
170
|
+
const dataTypeId = this.#translate(record.dataType);
|
|
171
|
+
const dataType = store.find(dataTypeId);
|
|
172
|
+
if (dataType !== NO_NODE) store.nodes.setDataType(i, dataType);
|
|
173
|
+
else this.#deferredDataTypes.push([i, dataTypeId]);
|
|
174
|
+
}
|
|
175
|
+
if (record.parentNodeId) {
|
|
176
|
+
const parentId = this.#translate(record.parentNodeId);
|
|
177
|
+
const parent = store.find(parentId);
|
|
178
|
+
if (parent !== NO_NODE) store.nodes.setParent(i, parent);
|
|
179
|
+
else this.#deferredParents.push([i, parentId]);
|
|
180
|
+
}
|
|
181
|
+
if (record.value && (record.nodeClass === NodeClass.Variable || record.nodeClass === NodeClass.VariableType)) {
|
|
182
|
+
this.#applyValue(i, record.value);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#rolePermissions(record: NodesetNodeRecord): RolePermissionEntry[] | undefined {
|
|
187
|
+
if (record.rolePermissions) {
|
|
188
|
+
return record.rolePermissions.map((r) => ({ roleId: this.#translate(r.roleId), permissions: r.permissions }));
|
|
189
|
+
}
|
|
190
|
+
// HasNoPermissions grants nothing; an absent RolePermissions inherits the namespace default
|
|
191
|
+
return record.hasNoPermissions ? [] : undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#applyValue(i: number, value: VariantOptions): void {
|
|
195
|
+
if (holdsXmlFragment(value.value)) {
|
|
196
|
+
// an extension object the loader left as its XML: the node objects decode it once every
|
|
197
|
+
// document is in; the store has no decoder yet, so the Variable waits for a value rather
|
|
198
|
+
// than serve something a client cannot decode
|
|
199
|
+
this.#deferredValues++;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const now = this.#loadTime;
|
|
203
|
+
const dataType =
|
|
204
|
+
(typeof value.dataType === "string" ? DataType[value.dataType as keyof typeof DataType] : value.dataType) ??
|
|
205
|
+
DataType.Null;
|
|
206
|
+
const isScalar = value.arrayType === undefined || value.arrayType === VariantArrayType.Scalar;
|
|
207
|
+
const v = value.value;
|
|
208
|
+
if (isScalar && (typeof v === "number" || typeof v === "boolean")) {
|
|
209
|
+
this.#store.values.setScalar(i, dataType, v, 0, now, now);
|
|
210
|
+
} else {
|
|
211
|
+
this.#store.values.setObject(i, dataType, value, 0, now, now);
|
|
212
|
+
this.#pendingValues++;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** values kept as objects (strings, arrays, structures) */
|
|
217
|
+
public get objectValueCount(): number {
|
|
218
|
+
return this.#pendingValues;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** extension object values the document gave as XML, left unset (BadWaitingForInitialData) */
|
|
222
|
+
public get deferredValueCount(): number {
|
|
223
|
+
return this.#deferredValues;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** true when a value is, or holds, an extension object still in its XML form */
|
|
228
|
+
function holdsXmlFragment(value: unknown): boolean {
|
|
229
|
+
if (value instanceof XmlExtensionObjectFragment) return true;
|
|
230
|
+
return Array.isArray(value) && value.some((element) => element instanceof XmlExtensionObjectFragment);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function parseAccessLevel(text: string | undefined, absent: number): number {
|
|
234
|
+
const value = parseInt(text || "", 10);
|
|
235
|
+
return Number.isNaN(value) ? absent : value;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** the AccessRestrictions attribute of the document: a number; absent or invalid inherits */
|
|
239
|
+
function parseAccessRestrictions(text: string | undefined): number | undefined {
|
|
240
|
+
if (text === undefined || text === "") {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
const value = parseInt(text, 10);
|
|
244
|
+
return Number.isNaN(value) ? undefined : value & 0xf;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** what a compact address space is loaded through: the records go in, finish() once they are all in */
|
|
248
|
+
export interface CompactRecordConsumer extends NodesetRecordConsumer {
|
|
249
|
+
finish(): { unresolved: number };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* the consumer a nodeset producer feeds to load a compact address space: the records written
|
|
254
|
+
* into its store, the namespaces registered on it, and what the space derives from its columns
|
|
255
|
+
* derived again at the end
|
|
256
|
+
*/
|
|
257
|
+
export function compactRecordConsumer(space: StoreAddressSpace, options: StoreRecordApplierOptions = {}): CompactRecordConsumer {
|
|
258
|
+
const applier = new StoreRecordApplier(space.store, { indexOf: (uri) => space.namespaceIndexOf(uri) }, options);
|
|
259
|
+
return {
|
|
260
|
+
apply: (record) => applier.apply(record),
|
|
261
|
+
finish: () => {
|
|
262
|
+
const result = applier.finish();
|
|
263
|
+
space.finishLoad();
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
package/api/session_context.ts
CHANGED
|
@@ -219,27 +219,39 @@ function getPermissionForRole(
|
|
|
219
219
|
function isDefaultContext(context: SessionContext) {
|
|
220
220
|
return context === SessionContext.defaultContext;
|
|
221
221
|
}
|
|
222
|
+
const serverNamespacesNodeId = resolveNodeId(ObjectIds.Server_Namespaces);
|
|
223
|
+
/**
|
|
224
|
+
* the Namespaces Object of the Server (RootFolder/Objects/Server/Namespaces), which holds the
|
|
225
|
+
* NamespaceMetadata of every namespace.
|
|
226
|
+
*
|
|
227
|
+
* It is consulted for each permission and each access-restriction check, that is several times
|
|
228
|
+
* per node read, so it is found by its NodeId: `rootFolder.objects.server.namespaces` resolves
|
|
229
|
+
* three children by name every time it is evaluated.
|
|
230
|
+
*/
|
|
231
|
+
function getServerNamespaces(namespace: NamespacePrivate): BaseNode | null {
|
|
232
|
+
return namespace.addressSpace.findNode(serverNamespacesNodeId);
|
|
233
|
+
}
|
|
234
|
+
|
|
222
235
|
function getAccessRestrictionsOnNamespace(namespace: NamespacePrivate, context: SessionContext): AccessRestrictionsFlag {
|
|
223
236
|
// ignore permission when default context is provided (to avoid recursion)
|
|
224
237
|
if (isDefaultContext(context)) {
|
|
225
238
|
return AccessRestrictionsFlag.None;
|
|
226
239
|
}
|
|
227
|
-
const namespaces = namespace
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return AccessRestrictionsFlag.None;
|
|
240
|
+
const namespaces = getServerNamespaces(namespace);
|
|
241
|
+
const namespaceObject = namespaces?.getChildByName(namespace.namespaceUri);
|
|
242
|
+
const defaultAccessRestrictions = namespaceObject?.getChildByName("DefaultAccessRestrictions");
|
|
243
|
+
if (defaultAccessRestrictions) {
|
|
244
|
+
const dataValue = defaultAccessRestrictions.readAttribute(null, AttributeIds.Value);
|
|
245
|
+
const value = dataValue?.value?.value as AccessRestrictionsFlag | null | undefined;
|
|
246
|
+
// the NamespaceMetadata nodes of the standard nodesets declare the property without a
|
|
247
|
+
// value: a null falls through to the namespace's own setting below
|
|
248
|
+
if (dataValue?.statusCode.isGood() && value !== null && value !== undefined) {
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// same fallback as getDefaultUserRolePermissionsOnNamespace: the policy set with
|
|
253
|
+
// Namespace.setDefaultAccessRestrictions() applies even without setNamespaceMetaData()
|
|
254
|
+
return namespace.getDefaultAccessRestrictions();
|
|
243
255
|
}
|
|
244
256
|
|
|
245
257
|
function getDefaultUserRolePermissionsOnNamespace(
|
|
@@ -251,7 +263,7 @@ function getDefaultUserRolePermissionsOnNamespace(
|
|
|
251
263
|
return null;
|
|
252
264
|
}
|
|
253
265
|
|
|
254
|
-
const namespaces = namespace
|
|
266
|
+
const namespaces = getServerNamespaces(namespace);
|
|
255
267
|
const uaNamespaceObject = namespaces?.getChildByName(namespace.namespaceUri);
|
|
256
268
|
if (uaNamespaceObject) {
|
|
257
269
|
// DefaultUserRolePermissions is the user-specific policy and wins when it carries a value.
|
|
@@ -437,6 +449,17 @@ export function makeRoles(roleIds: RoleIdLike[] | string | WellKnownRoles, addre
|
|
|
437
449
|
return roleIds.map((role) => resolveRole(role, addressSpace));
|
|
438
450
|
}
|
|
439
451
|
|
|
452
|
+
/** what {@link SessionContext.withPermissionCache} remembers while its action runs */
|
|
453
|
+
interface PermissionCache {
|
|
454
|
+
roles?: NodeId[];
|
|
455
|
+
evaluationRoles?: NodeId[];
|
|
456
|
+
/** keyed by the RolePermissions that apply, which most nodes share with their namespace */
|
|
457
|
+
permissions: Map<RolePermissionType[] | null, PermissionFlag>;
|
|
458
|
+
/** keyed by namespace index: a Read stays within one address space */
|
|
459
|
+
defaultRolePermissions: Map<number, RolePermissionType[] | null>;
|
|
460
|
+
defaultAccessRestrictions: Map<number, AccessRestrictionsFlag>;
|
|
461
|
+
}
|
|
462
|
+
|
|
440
463
|
export class SessionContext implements ISessionContext {
|
|
441
464
|
public static defaultContext = new SessionContext({});
|
|
442
465
|
|
|
@@ -447,6 +470,8 @@ export class SessionContext implements ISessionContext {
|
|
|
447
470
|
public readonly server?: IServerBase;
|
|
448
471
|
public readonly requestHeader?: RequestHeader;
|
|
449
472
|
|
|
473
|
+
#permissionCache: PermissionCache | null = null;
|
|
474
|
+
|
|
450
475
|
constructor(options?: SessionContextOptions) {
|
|
451
476
|
options = options || {};
|
|
452
477
|
this.session = options.session;
|
|
@@ -480,6 +505,26 @@ export class SessionContext implements ISessionContext {
|
|
|
480
505
|
return this.requestHeader?.auditEntryId || undefined;
|
|
481
506
|
}
|
|
482
507
|
|
|
508
|
+
/**
|
|
509
|
+
* see {@link ISessionContext.withPermissionCache}.
|
|
510
|
+
*
|
|
511
|
+
* A Session's sessionContext is shared by every request in flight on it, so the cache only
|
|
512
|
+
* lives while `action` runs, and `action` must not wait on anything: a request served in
|
|
513
|
+
* one synchronous run cannot be interleaved with another one. Nested calls share the
|
|
514
|
+
* outermost cache.
|
|
515
|
+
*/
|
|
516
|
+
public withPermissionCache<T>(action: () => T): T {
|
|
517
|
+
if (this.#permissionCache) {
|
|
518
|
+
return action();
|
|
519
|
+
}
|
|
520
|
+
this.#permissionCache = { permissions: new Map(), defaultRolePermissions: new Map(), defaultAccessRestrictions: new Map() };
|
|
521
|
+
try {
|
|
522
|
+
return action();
|
|
523
|
+
} finally {
|
|
524
|
+
this.#permissionCache = null;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
483
528
|
/**
|
|
484
529
|
* The client's application-instance certificate,
|
|
485
530
|
* or `null` if no secure channel is available.
|
|
@@ -558,6 +603,15 @@ export class SessionContext implements ISessionContext {
|
|
|
558
603
|
*
|
|
559
604
|
*/
|
|
560
605
|
public getCurrentUserRoles(): NodeId[] {
|
|
606
|
+
const cache = this.#permissionCache;
|
|
607
|
+
if (!cache) {
|
|
608
|
+
return this.#resolveCurrentUserRoles();
|
|
609
|
+
}
|
|
610
|
+
cache.roles ??= this.#resolveCurrentUserRoles();
|
|
611
|
+
return cache.roles;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
#resolveCurrentUserRoles(): NodeId[] {
|
|
561
615
|
if (!this.session) {
|
|
562
616
|
// no Session: an in-process caller. getPermissions grants it everything —
|
|
563
617
|
// an empty list here does NOT mean "no rights", see unresolvedPermissions.
|
|
@@ -573,7 +627,8 @@ export class SessionContext implements ISessionContext {
|
|
|
573
627
|
return [];
|
|
574
628
|
}
|
|
575
629
|
|
|
576
|
-
|
|
630
|
+
// a fresh array each time, a caller may change it; the NodeId is resolved once, at load
|
|
631
|
+
const anonymous = (): NodeId[] => [anonymousRoleNodeId];
|
|
577
632
|
|
|
578
633
|
const username = getUserName(userIdentityToken);
|
|
579
634
|
|
|
@@ -587,16 +642,16 @@ export class SessionContext implements ISessionContext {
|
|
|
587
642
|
}
|
|
588
643
|
|
|
589
644
|
if (username === "anonymous") {
|
|
590
|
-
return anonymous;
|
|
645
|
+
return anonymous();
|
|
591
646
|
}
|
|
592
647
|
if (!this.server?.userManager) {
|
|
593
|
-
return anonymous;
|
|
648
|
+
return anonymous();
|
|
594
649
|
}
|
|
595
650
|
|
|
596
651
|
assert(this.server != null, "expecting a server");
|
|
597
652
|
|
|
598
653
|
if (typeof this.server.userManager.getUserRoles !== "function") {
|
|
599
|
-
return anonymous;
|
|
654
|
+
return anonymous();
|
|
600
655
|
}
|
|
601
656
|
|
|
602
657
|
const rolesNodeId = this.server.userManager.getUserRoles(username) || [];
|
|
@@ -627,8 +682,16 @@ export class SessionContext implements ISessionContext {
|
|
|
627
682
|
|
|
628
683
|
public getApplicableRolePermissions(node: BaseNode): RolePermissionType[] | null {
|
|
629
684
|
if (!node.rolePermissions) {
|
|
630
|
-
const
|
|
631
|
-
|
|
685
|
+
const cache = this.#permissionCache;
|
|
686
|
+
if (!cache) {
|
|
687
|
+
return getDefaultUserRolePermissionsOnNamespace(node.namespace as NamespacePrivate, this);
|
|
688
|
+
}
|
|
689
|
+
const namespaceIndex = node.nodeId.namespace;
|
|
690
|
+
let defaultUserRolePermissions = cache.defaultRolePermissions.get(namespaceIndex);
|
|
691
|
+
if (defaultUserRolePermissions === undefined) {
|
|
692
|
+
defaultUserRolePermissions = getDefaultUserRolePermissionsOnNamespace(node.namespace as NamespacePrivate, this);
|
|
693
|
+
cache.defaultRolePermissions.set(namespaceIndex, defaultUserRolePermissions);
|
|
694
|
+
}
|
|
632
695
|
return defaultUserRolePermissions;
|
|
633
696
|
}
|
|
634
697
|
return node.rolePermissions;
|
|
@@ -684,7 +747,11 @@ export class SessionContext implements ISessionContext {
|
|
|
684
747
|
public getPermissions(node: BaseNode): PermissionFlag {
|
|
685
748
|
const applicableRolePermissions = this.getApplicableRolePermissions(node);
|
|
686
749
|
|
|
687
|
-
const
|
|
750
|
+
const cache = this.#permissionCache;
|
|
751
|
+
if (cache) {
|
|
752
|
+
cache.roles ??= this.getCurrentUserRoles();
|
|
753
|
+
}
|
|
754
|
+
const roles = cache?.roles ?? this.getCurrentUserRoles();
|
|
688
755
|
if (roles.length === 0) {
|
|
689
756
|
// Two very different situations land here:
|
|
690
757
|
// - no Session at all: an in-process caller, always trusted (see isInProcessCaller)
|
|
@@ -694,17 +761,39 @@ export class SessionContext implements ISessionContext {
|
|
|
694
761
|
// can set unresolvedPermissionPolicy: "deny" to fail closed instead.
|
|
695
762
|
return this.unresolvedPermissions;
|
|
696
763
|
}
|
|
764
|
+
if (!cache) {
|
|
765
|
+
return this.#evaluatePermissions(applicableRolePermissions, this.getRolesForPermissionEvaluation(roles));
|
|
766
|
+
}
|
|
767
|
+
let permissions = cache.permissions.get(applicableRolePermissions);
|
|
768
|
+
if (permissions === undefined) {
|
|
769
|
+
cache.evaluationRoles ??= this.getRolesForPermissionEvaluation(roles);
|
|
770
|
+
permissions = this.#evaluatePermissions(applicableRolePermissions, cache.evaluationRoles);
|
|
771
|
+
cache.permissions.set(applicableRolePermissions, permissions);
|
|
772
|
+
}
|
|
773
|
+
return permissions;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
#evaluatePermissions(applicableRolePermissions: RolePermissionType[] | null, roles: NodeId[]): PermissionFlag {
|
|
697
777
|
const unresolved = this.unresolvedPermissions;
|
|
698
778
|
let orFlags: PermissionFlag = 0;
|
|
699
|
-
for (const role of
|
|
779
|
+
for (const role of roles) {
|
|
700
780
|
orFlags = orFlags | getPermissionForRole(applicableRolePermissions, role, unresolved);
|
|
701
781
|
}
|
|
702
782
|
return orFlags;
|
|
703
783
|
}
|
|
784
|
+
|
|
704
785
|
public getAccessRestrictions(node: BaseNode): AccessRestrictionsFlag {
|
|
705
786
|
if (node.accessRestrictions === undefined) {
|
|
706
|
-
const
|
|
707
|
-
|
|
787
|
+
const cache = this.#permissionCache;
|
|
788
|
+
if (!cache) {
|
|
789
|
+
return getAccessRestrictionsOnNamespace(node.namespace as NamespacePrivate, this);
|
|
790
|
+
}
|
|
791
|
+
const namespaceIndex = node.nodeId.namespace;
|
|
792
|
+
let accessRestrictions = cache.defaultAccessRestrictions.get(namespaceIndex);
|
|
793
|
+
if (accessRestrictions === undefined) {
|
|
794
|
+
accessRestrictions = getAccessRestrictionsOnNamespace(node.namespace as NamespacePrivate, this);
|
|
795
|
+
cache.defaultAccessRestrictions.set(namespaceIndex, accessRestrictions);
|
|
796
|
+
}
|
|
708
797
|
return accessRestrictions;
|
|
709
798
|
}
|
|
710
799
|
return node.accessRestrictions;
|
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
* @module node-opcua-address-space
|
|
3
3
|
*/
|
|
4
4
|
import type { IAddressSpace } from "node-opcua-address-space-base";
|
|
5
|
+
import { type StoreAddressSpace, type StoreAddressSpaceOptions, StoreServices } from "node-opcua-address-space-store";
|
|
5
6
|
import type { IHistorizerFactory } from "./address_space_ts.js";
|
|
6
7
|
import type { Namespace } from "./namespace.js";
|
|
7
8
|
import type { UARootFolder } from "./ua_root_folder.js";
|
|
9
|
+
/** the published names of the compact address space and its options */
|
|
10
|
+
export type CompactAddressSpace = StoreAddressSpace;
|
|
11
|
+
export type CompactAddressSpaceOptions = StoreAddressSpaceOptions;
|
|
12
|
+
/** the Read, Write, Browse and TranslateBrowsePaths services on a compact address space */
|
|
13
|
+
export declare const CompactAddressSpaceServices: typeof StoreServices;
|
|
14
|
+
export type CompactAddressSpaceServices = StoreServices;
|
|
8
15
|
/**
|
|
9
16
|
* The address space: the set of nodes a server exposes, and the namespaces they live in.
|
|
10
17
|
*
|
|
@@ -35,6 +42,13 @@ export interface AddressSpace extends IAddressSpace {
|
|
|
35
42
|
export interface AddressSpaceConstructor {
|
|
36
43
|
/** build an empty address space */
|
|
37
44
|
create(): AddressSpace;
|
|
45
|
+
/**
|
|
46
|
+
* build an empty compact address space: the nodes in typed columns, node objects built
|
|
47
|
+
* on demand. Cheaper by far for a large model, and not yet an {@link AddressSpace}: it
|
|
48
|
+
* answers finds, browses, attribute and value reads, bindings and writes, and a server
|
|
49
|
+
* cannot be built on it yet.
|
|
50
|
+
*/
|
|
51
|
+
createCompact(options?: CompactAddressSpaceOptions): CompactAddressSpace;
|
|
38
52
|
/**
|
|
39
53
|
* How historized variables get their historian. Assigning here replaces the default that
|
|
40
54
|
* the historical-access code installs on first use.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"address_space_public.d.ts","sourceRoot":"","sources":["../../api/address_space_public.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"address_space_public.d.ts","sourceRoot":"","sources":["../../api/address_space_public.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAEtH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,uEAAuE;AACvE,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AACpD,MAAM,MAAM,0BAA0B,GAAG,wBAAwB,CAAC;AAClE,2FAA2F;AAC3F,eAAO,MAAM,2BAA2B,sBAAgB,CAAC;AACzD,MAAM,MAAM,2BAA2B,GAAG,aAAa,CAAC;AAExD;;;;;GAKG;AACH,MAAM,WAAW,YAAa,SAAQ,aAAa;IAC/C,eAAe,IAAI,SAAS,CAAC;IAC7B,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACnD,UAAU,EAAE,YAAY,CAAC;IAEzB;;;;;;;OAOG;IACH,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAC3D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,uBAAuB;IACpC,mCAAmC;IACnC,MAAM,IAAI,YAAY,CAAC;IAEvB;;;;;OAKG;IACH,aAAa,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,mBAAmB,CAAC;IAEzE;;;OAGG;IACH,iBAAiB,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,EAAE,uBAA0C,CAAC"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { StoreServices } from "node-opcua-address-space-store";
|
|
1
2
|
import { AddressSpaceImpl } from "../impl/address_space.js";
|
|
3
|
+
/** the Read, Write, Browse and TranslateBrowsePaths services on a compact address space */
|
|
4
|
+
export const CompactAddressSpaceServices = StoreServices;
|
|
2
5
|
/**
|
|
3
6
|
* An interface and a variable may share a name, so `AddressSpace` is both the type you
|
|
4
7
|
* annotate with and the value you call `create()` on, while the implementation class stays
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"address_space_public.js","sourceRoot":"","sources":["../../api/address_space_public.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"address_space_public.js","sourceRoot":"","sources":["../../api/address_space_public.ts"],"names":[],"mappings":"AAIA,OAAO,EAAyD,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACtH,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAQ5D,2FAA2F;AAC3F,MAAM,CAAC,MAAM,2BAA2B,GAAG,aAAa,CAAC;AAmDzD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,YAAY,GAA4B,gBAAgB,CAAC"}
|