/** * Logger port so the domain never depends on pino / winston / console. * Adapters inject a concrete logger at composition time. */ interface ILogger { debug(msg: string, ...args: unknown[]): void; info(msg: string, ...args: unknown[]): void; warn(msg: string, ...args: unknown[]): void; error(msg: string, ...args: unknown[]): void; } declare const nullLogger: ILogger; declare const consoleLogger: ILogger; /** A node discovered during a browse / tree-walk of the source system. */ interface SourceNodeInfo { readonly sourceNodeId: string; readonly parentSourceNodeId: string | null; readonly browseName: string; /** Namespace-URI-qualified browse name: `"nsu=http://…:Name"` */ readonly nsuQualifiedName: string; readonly displayName: string; /** Source-native class name, e.g. 'Object', 'Variable', 'Method'. */ readonly nodeClass: string; /** Type definition of this node (e.g. 'ns=0;i=63' for BaseObjectType). */ readonly typeDefinition: string | null; /** Namespace URI for this node's browse name. */ readonly namespaceUri: string; readonly eventNotifier: boolean; readonly dataType?: string | null; readonly dataTypeName?: string | null; readonly valueRank?: number | null; } /** * Controls which subtrees under the OPC UA Objects folder * are exposed via the i3X REST API. * * - `'application-only'` — skip standard OPC UA nodes (ns=0) * such as `Server`, `Aliases`, etc. **This is the default.** * - `'all'` — expose every node under ObjectsFolder. * - `string[]` — explicit list of NodeIds or BrowseNames * of top-level objects to include (e.g. `['ns=1;s=SmartFactory']`). */ type BrowseFilter = 'application-only' | 'all' | string[]; /** Namespace metadata from the source system. */ interface NamespaceInfo { readonly uri: string; readonly displayName: string; } /** Object-type metadata from the source system. */ interface ObjectTypeInfo { readonly sourceNodeId: string; readonly parentSourceNodeId: string | null; readonly browseName: string; readonly displayName: string; readonly namespaceUri: string; /** Members (variables, objects, methods) declared by this type. */ readonly members?: readonly ObjectTypeMemberInfo[]; } /** A member (variable / property / object / method) of an ObjectType. */ interface ObjectTypeMemberInfo { readonly browseName: string; readonly displayName: string; /** 'Variable' | 'Object' | 'Method' */ readonly nodeClass: string; /** OPC UA data type name, e.g. 'Double', 'Boolean', 'i=11'. */ readonly dataType: string | null; /** 'Mandatory' | 'Optional' | null */ readonly modellingRule: string | null; readonly valueRank?: number | null; } /** A single value read from the source, with quality + timestamp. */ interface SourceDataValue { readonly value: unknown; readonly quality: string; /** RFC 3339 UTC timestamp. */ readonly timestamp: string; readonly statusCode?: number; } /** A single historical value from the source. */ interface SourceHistoricalValue { readonly value: unknown; readonly quality: string; readonly timestamp: string; } type DataChangeCallback = (sourceNodeId: string, value: unknown, quality: string, timestamp: string) => void; interface MonitoredSubscriptionOptions { readonly publishingIntervalMs: number; readonly samplingIntervalMs: number; } /** * A handle to a live, source-level monitored subscription. * * Adapters return this from `createMonitoredSubscription`. * The domain uses it to add/remove items and receive callbacks. */ interface IMonitoredSubscription { readonly id: string; addItems(sourceNodeIds: string[]): Promise; removeItems(sourceNodeIds: string[]): Promise; onDataChange(cb: DataChangeCallback): void; close(): Promise; } /** * Outbound port — the domain's single contract with the data layer. * * Any data source (OPC UA, MQTT, database, flat file, mock test * double) must implement this interface to be usable by the i3X * domain services. */ interface IDataSourcePort { connect(): Promise; disconnect(): Promise; isConnected(): boolean; browseTree(): Promise; getNamespaces(): Promise; getObjectTypes(): Promise; readValue(sourceNodeId: string): Promise; readValues(sourceNodeIds: string[]): Promise; writeValue(sourceNodeId: string, value: unknown): Promise; readHistory(sourceNodeId: string, startTime: Date, endTime: Date): Promise; createMonitoredSubscription(options: MonitoredSubscriptionOptions): Promise; getStats?(): { transactionsPerformed: number; bytesRead: number; bytesWritten: number; services: Record; }; } /** * Factory function type for creating data-source adapters. * * The composition root uses this to wire adapters without * importing concrete classes. */ type DataSourceFactory = (logger: ILogger) => IDataSourcePort; /** The four i3X node kinds mapped from OPC UA node classes. */ type NodeKind = 'instance' | 'property' | 'action' | 'eventSource'; /** OPC UA data quality indicators. */ type DataQuality = 'Good' | 'GoodNoData' | 'Bad' | 'Uncertain'; /** * A single node in the i3X model tree. * * Every node in the browsed address-space is projected to exactly one * ModelNode. The `id` is a stable, hash-based identifier derived from * the source system's native node id and the inferred kind. */ interface ModelNode { readonly id: string; readonly name: string; readonly kind: NodeKind; readonly type: string | null; readonly children: readonly string[]; readonly sourceNodeId: string; readonly namespaceUri: string; readonly engUnit?: string | null; readonly sourceTypeId?: string | null; } /** * The fully-resolved model snapshot produced by a model build. * * All maps are keyed by stable i3X element id. */ interface BuildResult { readonly nodesById: ReadonlyMap; readonly rootIds: readonly string[]; readonly childrenById: ReadonlyMap; /** property element-id → source node id */ readonly propertyToSource: ReadonlyMap; /** action element-id → [parent source node id, method source node id] */ readonly actionToMethod: ReadonlyMap; } /** Value / Quality / Timestamp — the universal data-exchange atom. */ interface VQT { readonly value: unknown; readonly quality: DataQuality; /** RFC 3339 UTC timestamp. */ readonly timestamp: string; } /** A VQT with additional composition information. */ interface CurrentValueResult { readonly isComposition: boolean; readonly value: unknown; readonly quality: DataQuality; readonly timestamp: string; readonly components?: Readonly> | null; } /** Historical values for a single element. */ interface HistoricalValueResult { readonly isComposition: boolean; readonly values: readonly VQT[]; readonly components?: Readonly> | null; } interface SuccessResponse { success: boolean; result: T | null; } interface ErrorDetail { title: string; status: number; detail: string; } interface ErrorResponse { success: false; responseDetail: ErrorDetail; } interface BulkResultItem { success: boolean; elementId?: string | null; subscriptionId?: string | null; result?: T | null; responseDetail?: ErrorDetail | null; } interface BulkResponse { success: boolean; results: BulkResultItem[]; } interface QueryCapabilities { history: boolean; } interface UpdateCapabilities { current: boolean; history: boolean; } interface SubscribeCapabilities { stream: boolean; } interface ServerCapabilities { query: QueryCapabilities; update: UpdateCapabilities; subscribe: SubscribeCapabilities; } interface ServerInfo { specVersion: string; serverVersion?: string | null; serverName?: string | null; capabilities: ServerCapabilities; } interface ObjectInstanceMetadata { typeNamespaceUri?: string | null; sourceTypeId?: string | null; engUnit?: string | null; description?: string | null; relationships?: Record | null; schemaExtensions?: Record | null; system?: Record | null; } interface ObjectInstanceResponse { elementId: string; displayName: string; typeElementId: string; parentId?: string | null; isComposition: boolean; isExtended?: boolean; metadata?: ObjectInstanceMetadata | null; } interface RelatedObjectResult { sourceRelationship: string; object: ObjectInstanceResponse; } interface SyncUpdateEntry { elementId: string; value: unknown; quality: string; timestamp: string; } interface SyncBatch { sequenceNumber: number; updates: SyncUpdateEntry[]; } interface CreateSubscriptionRequest { clientId?: string | null; displayName?: string | null; } interface CreateSubscriptionResponse { subscriptionId: string; clientId?: string | null; displayName?: string | null; } interface SubscriptionDetailResponse { subscriptionId: string; clientId?: string | null; displayName?: string | null; monitoredObjects: Array>; mode?: string | null; } interface ElementIdsRequest { elementIds: string[]; } interface GetObjectsRequest { elementIds: string[]; includeMetadata?: boolean; } interface GetRelatedObjectsRequest { elementIds: string[]; relationshipType?: string | null; includeMetadata?: boolean; } interface GetObjectValueRequest { elementIds: string[]; maxDepth?: number; } interface GetObjectHistoryRequest { elementIds: string[]; startTime?: string | null; endTime?: string | null; maxDepth?: number; } interface RegisterMonitoredItemsRequest { subscriptionId: string; elementIds: string[]; maxDepth?: number; } interface SyncRequest { clientId?: string | null; subscriptionId: string; lastSequenceNumber?: number; } interface StreamRequest { clientId?: string | null; subscriptionId: string; lastSequenceNumber?: number; } interface ListSubscriptionsRequest { clientId?: string | null; subscriptionIds: string[]; } interface DeleteSubscriptionsRequest { clientId?: string | null; subscriptionIds: string[]; } /** An i3X object-type projection. */ interface ObjectType { readonly elementId: string; readonly displayName: string; readonly namespaceUri: string; readonly sourceTypeId: string; readonly version: string | null; readonly schema: Record; readonly related: Record | null; } /** A relationship type between i3X objects. */ interface RelationshipType { readonly elementId: string; readonly displayName: string; readonly namespaceUri: string; readonly relationshipId: string; readonly reverseOf: string; } declare class ModelService { private readonly dataSource; private readonly logger; private readonly options?; private _cache; private _buildPromise; readonly dataTypeTypes: Map; constructor(dataSource: IDataSourcePort, logger: ILogger, options?: { typeIdFormat?: "hash" | "name" | "prefixed-name"; } | undefined); getOrBuildModel(): Promise; preloadModel(): Promise; invalidateCache(): void; setCache(result: BuildResult): void; findNode(model: BuildResult, elementId: string): ModelNode | null; parentIdOf(model: BuildResult, nodeId: string): string | null; /** * Internal method to build the OPC UA object and variable model. * Discovers the server's type hierarchy, browses the node tree, * constructs stable browse path identifiers, and maps the components * to domain-level ModelNodes. * * @returns A promise resolving to the built model result. */ private _build; private _formatDataTypeAndRegister; /** * Builds a map from OPC UA type definition identifier to its string classification. * * @param types List of object type info fetched from the source. * @returns Map of type IDs to their mapped string names. */ private _buildTypeIdMap; } /** * Build a map of OPC UA sourceNodeId → i3X type elementId. * * Constructs a full nsu-qualified browse path for each type * by walking the parent chain up to the hierarchy root. * This guarantees unique elementIds even when multiple types * share the same browseName (siblings are always unique). */ declare function buildTypeIdMap(types: readonly ObjectTypeInfo[], format?: 'hash' | 'name' | 'prefixed-name'): Map; declare function emptyBuildResult(): BuildResult; declare class HistoryService { private readonly dataSource; private readonly modelService; readonly _logger: ILogger; constructor(dataSource: IDataSourcePort, modelService: ModelService, _logger: ILogger); readHistory(elementIds: string[], startTime: Date | null, endTime: Date | null, maxDepth?: number): Promise[]>; private _readComponentsHistory; } /** * A single subscription update in the update queue. * * The `value` field carries the full composite structure * matching the i3X CurrentValueResult shape — so the * explorer sees the same format from stream as from * POST /objects/value. */ interface SubscriptionUpdate { readonly sequenceNumber: number; /** The registered asset / object elementId. */ readonly elementId: string; /** The composite value snapshot. */ readonly value: CurrentValueResult; readonly quality: string; /** RFC 3339 UTC timestamp. */ readonly timestamp: string; } /** Result of a sync operation — pending updates for the client. */ interface SubscriptionSyncResult { readonly updates: readonly SubscriptionUpdate[]; } /** Result of a delete operation — per-subscription success/failure. */ interface SubscriptionDeleteResult { readonly success: boolean; readonly subscriptionId: string; readonly responseDetail?: { readonly title: string; readonly status: number; readonly detail: string; } | null; } /** Public view of a subscription's current state. */ interface SubscriptionDetail { readonly subscriptionId: string; readonly clientId: string | null; readonly displayName: string | null; readonly monitoredObjects: readonly MonitoredObjectEntry[]; readonly mode: string; } interface MonitoredObjectEntry { readonly elementId: string; readonly maxDepth: number; } /** Options for creating a new subscription. */ interface CreateSubscriptionOptions { readonly clientId?: string | null; readonly displayName?: string | null; } declare class SubscriptionService { private readonly dataSource; private readonly modelService; private readonly logger; private readonly _subs; private readonly _publishIntervalMs; private readonly _samplingIntervalMs; constructor(dataSource: IDataSourcePort, modelService: ModelService, logger: ILogger, publishIntervalMs?: number, samplingIntervalMs?: number); create(opts?: CreateSubscriptionOptions): { subscriptionId: string; clientId: string | null; displayName: string | null; }; register(subscriptionId: string, elementIds: string[], maxDepth?: number): Promise<{ registered: string[]; errors: Array<{ elementId: string; error: string; }>; }>; unregister(subscriptionId: string, elementIds: string[]): Promise<{ registered: string[]; errors: Array<{ elementId: string; error: string; }>; }>; sync(subscriptionId: string, lastSequenceNumber?: number): SyncBatch[]; /** * Trim updates that have been delivered to the client. * Call after stream/sync to prevent re-delivery. */ acknowledge(subscriptionId: string, upToSequence: number): void; waitForUpdates(subscriptionId: string, afterSequence: number, timeoutMs?: number): Promise; /** * Register an active SSE stream for the subscription. * If another stream is already active, it is closed first * (enforcing single-stream-per-subscription). */ registerActiveStream(subscriptionId: string, closeCallback: () => void): void; /** * Clear the active stream reference when a stream closes. * Only clears if the callback matches (prevents stale clears). */ clearActiveStream(subscriptionId: string, closeCallback: () => void): void; deleteSubscriptions(subscriptionIds: string[]): Promise; list(filterIds?: string[]): SubscriptionDetail[]; close(): Promise; /** * Helper to retrieve a subscription by its ID, throwing a 404 error * if the subscription does not exist. * * @param id The subscription identifier. * @returns The SubState object for the subscription. * @throws Error with statusCode 404 if not found. */ private _requireSub; /** * Return the subscription if it exists, otherwise auto-create * it with the caller-provided ID. This supports the i3X * Explorer pattern where the client generates a subscriptionId * and calls register() directly without a prior create(). */ private _getOrCreateSub; /** * Collect source-node → property-element mappings for a node * tree, recursing into composition children up to maxDepth. * * Returns Map. */ private _collectSourceMappings; /** * Ensures the underlying native OPC UA monitored subscription runtime is created * and registers the monitored items. If native subscriptions fail, it falls back * to software-based polling. * * @param sub The subscription state object. * @param newSourceNodeIds The list of new source node IDs to monitor. */ private _ensureRuntime; /** * Called when a single OPC UA property value changes. * Updates the asset's VQT cache and starts a debounce timer * to flush the composite value once all changes settle. */ private _onDataChange; /** * Build the composite CurrentValueResult from the asset's * cached property values and push it as a SubscriptionUpdate. */ private _flushAsset; /** * Starts a software polling loop for the subscription when native OPC UA * subscriptions are not supported or fail. * * @param sub The subscription state object. */ private _startPolling; } declare class TypeService { private readonly dataSource; private readonly logger; private readonly options?; private _cache; private _buildPromise; constructor(dataSource: IDataSourcePort, logger: ILogger, options?: { typeIdFormat?: "hash" | "name" | "prefixed-name"; modelService?: ModelService; } | undefined); /** * Preload types at startup. * Always fetches fresh data and replaces the cache. */ preloadTypes(): Promise; /** * Get all object types (optionally filtered by namespace). * Returns cached data — O(1). */ getObjectTypes(namespaceUri?: string): Promise; /** * Query specific types by elementId. * Returns results in request order with null for unknown ids. */ queryObjectTypes(elementIds: string[]): Promise>; /** Drop the cache (e.g. after reconnect). */ invalidateCache(): void; private _getOrBuild; private _build; } declare class ValueService { private readonly dataSource; private readonly modelService; readonly _logger: ILogger; constructor(dataSource: IDataSourcePort, modelService: ModelService, _logger: ILogger); readValues(elementIds: string[], maxDepth?: number): Promise[]>; writeValue(elementId: string, value: unknown): Promise; private _readComponents; } interface I3xStackOptions { publishIntervalMs?: number; samplingIntervalMs?: number; typeIdFormat?: 'hash' | 'name' | 'prefixed-name'; } interface I3xStack { modelService: ModelService; valueService: ValueService; historyService: HistoryService; subscriptionService: SubscriptionService; typeService: TypeService; } /** * Instantiate and wire all i3X domain services with the given data source port. * Reduces duplication across servers, demos, and E2E tests. */ declare function createI3xStack(dataSource: IDataSourcePort, logger: ILogger, options?: I3xStackOptions): I3xStack; /** An i3X namespace (maps 1-to-1 from OPC UA namespace table). */ interface Namespace { readonly uri: string; readonly displayName: string; } declare const NODE_CLASS_NAMES: Record; /** Convert a QualifiedName-like object to its namespace-URI-qualified form. */ declare function qualifiedNameToNsu(browseName: { namespaceIndex?: number; name?: string | null; } | null | undefined, namespaceArray: readonly string[]): string; /** Convert an index-based NodeId string (e.g. "ns=17;i=1008") to its namespace-URI-qualified (nsu) form. */ declare function toNsuNodeId(nodeIdStr: string, namespaceArray: readonly string[]): string; /** Convert a value from node-opcua to a JSON-compatible type. */ declare function cleanOpcuaValue(val: unknown): unknown; declare function cleanOpcuaVariant(variant: any): unknown; /** Convert a DataValue-like object to a SourceDataValue. */ declare function dataValueToSource(dv: { statusCode?: { value: number; } | null; value?: { value: unknown; } | null; sourceTimestamp?: Date | null; serverTimestamp?: Date | null; }): SourceDataValue; /** Convert a DataValue-like object to a SourceHistoricalValue. */ declare function dataValueToHistorical(dv: { statusCode?: { value: number; } | null; value?: { value: unknown; } | null; sourceTimestamp?: Date | null; serverTimestamp?: Date | null; }): SourceHistoricalValue; /** * Normalize a value and quality code according to the i3X specification: * - If quality is 'Bad', value MUST be null. * - If value is null or undefined (and quality is not Bad), quality MUST be 'GoodNoData'. */ declare function normalizeVqt(value: unknown, quality: string): { value: unknown; quality: DataQuality; }; /** * Derive a stable, deterministic i3X element ID. * * The input should be a namespace-URI-qualified browse path * (e.g. `"nsu=http://…:DeviceSet/nsu=http://…:Pump/nsu=http://…:Temp"`). * Hashing this instead of the raw OPC UA NodeId ensures the * element ID survives server restarts even when namespace * indices shift. * * Format: `{kind}-{sha1_prefix_16}` */ declare function stableI3xId(browsePath: string, kind: NodeKind | 'type'): string; /** Infer the i3X NodeKind from source node metadata. */ declare function inferKind(node: SourceNodeInfo): NodeKind; declare function mapType(node: SourceNodeInfo, kind: NodeKind): string | null; /** Project a source node into an i3X ModelNode. Pure function. */ declare function mapNode(node: SourceNodeInfo, childIds: readonly string[], browsePath: string, typeOverride?: string | null): ModelNode; /** * Build a JSON Schema (draft 2020-12) for an ObjectType, * walking the type inheritance chain. * * @param type – the ObjectType to generate a schema for * @param allTypes – all known ObjectTypes (for resolving parents) */ declare function buildObjectTypeSchema(type: ObjectTypeInfo, allTypes: readonly ObjectTypeInfo[]): Record; /** * Build JSON Schemas for ALL object types in a single pass. * Builds the `bySourceId` lookup once → O(n) instead of O(n²). * * @returns Map from sourceNodeId to its JSON Schema */ declare function buildAllObjectTypeSchemas(allTypes: readonly ObjectTypeInfo[]): Map>; export { type BrowseFilter, type BuildResult, type BulkResponse, type BulkResultItem, type CreateSubscriptionOptions, type CreateSubscriptionRequest, type CreateSubscriptionResponse, type CurrentValueResult, type DataChangeCallback, type DataQuality, type DataSourceFactory, type DeleteSubscriptionsRequest, type ElementIdsRequest, type ErrorDetail, type ErrorResponse, type GetObjectHistoryRequest, type GetObjectValueRequest, type GetObjectsRequest, type GetRelatedObjectsRequest, type HistoricalValueResult, HistoryService, type I3xStack, type I3xStackOptions, type IDataSourcePort, type ILogger, type IMonitoredSubscription, type ListSubscriptionsRequest, type ModelNode, ModelService, type MonitoredObjectEntry, type MonitoredSubscriptionOptions, NODE_CLASS_NAMES, type Namespace, type NamespaceInfo, type NodeKind, type ObjectInstanceMetadata, type ObjectInstanceResponse, type ObjectType, type ObjectTypeInfo, type ObjectTypeMemberInfo, type QueryCapabilities, type RegisterMonitoredItemsRequest, type RelatedObjectResult, type RelationshipType, type ServerCapabilities, type ServerInfo, type SourceDataValue, type SourceHistoricalValue, type SourceNodeInfo, type StreamRequest, type SubscribeCapabilities, type SubscriptionDeleteResult, type SubscriptionDetail, type SubscriptionDetailResponse, SubscriptionService, type SubscriptionSyncResult, type SubscriptionUpdate, type SuccessResponse, type SyncBatch, type SyncRequest, type SyncUpdateEntry, TypeService, type UpdateCapabilities, type VQT, ValueService, buildAllObjectTypeSchemas, buildObjectTypeSchema, buildTypeIdMap, cleanOpcuaValue, cleanOpcuaVariant, consoleLogger, createI3xStack, dataValueToHistorical, dataValueToSource, emptyBuildResult, inferKind, mapNode, mapType, normalizeVqt, nullLogger, qualifiedNameToNsu, stableI3xId, toNsuNodeId };