node-red-contrib-i3x 0.0.5 → 0.0.6
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/CHANGELOG.md +22 -0
- package/README.md +113 -1
- package/lib/i3x-client.d.ts +161 -0
- package/package.json +18 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.6 (2026-06-14)
|
|
4
|
+
|
|
5
|
+
Tooling and developer-experience release. No functional changes to node
|
|
6
|
+
behaviour since 0.0.5; the package remains feature-complete against the
|
|
7
|
+
**i3X API 1.0 Release** specification (finalized 2026-06-09).
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- TypeScript type definitions for the shared client (`lib/i3x-client.d.ts`,
|
|
12
|
+
exposed via the package `types` field)
|
|
13
|
+
- ESLint flat config and `npm run lint` / `lint:fix` scripts; CI now runs a
|
|
14
|
+
lint job before the test matrix
|
|
15
|
+
- Coverage tooling via c8 (`npm run test:coverage`)
|
|
16
|
+
- `CONTRIBUTING.md`, GitHub issue forms, and a pull-request template
|
|
17
|
+
- README badges and an architecture section with schematic diagrams
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Minimum Node.js raised to 18** (`engines.node` was incorrectly `>=14`;
|
|
22
|
+
CI and `axios ^1.15` already required 18+)
|
|
23
|
+
- Documentation links now consistently point at the 1.0 (`/v1`) API docs
|
|
24
|
+
|
|
3
25
|
## 0.0.5 (2026-06-12)
|
|
4
26
|
|
|
5
27
|
Migration to the **i3X API 1.0 Release** specification (finalized 2026-06-09). See the
|
package/README.md
CHANGED
|
@@ -1,11 +1,115 @@
|
|
|
1
1
|
# node-red-contrib-i3x
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/node-red-contrib-i3x)
|
|
4
|
+
[](https://github.com/blanpa/node-red-contrib-i3x/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](https://www.i3x.dev)
|
|
8
|
+
|
|
3
9
|
Node-RED nodes for the **i3X** (Industrial Information Interoperability eXchange) API by [CESMII](https://www.cesmii.org).
|
|
4
10
|
|
|
5
11
|
i3X is an open, vendor-agnostic REST API specification for standardised access to contextualised manufacturing information platforms (Historians, MES, MOM, etc.).
|
|
6
12
|
|
|
13
|
+
This package is an i3X **client**: it lets your Node-RED flows browse, read, write, query history, and subscribe against any compliant i3X server. (If you instead need to *expose* an OPC UA address space as an i3X server, see projects like [`node-opcua/node-i3x`](https://github.com/node-opcua/node-i3x) — the two are complementary.)
|
|
14
|
+
|
|
7
15
|
> **Note:** This package targets the **i3X API 1.0 Release** (finalized 2026-06-09). The specification is stable; the next revision is not expected before the vNext working group convenes in late 2026.
|
|
8
16
|
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
### Where i3X sits
|
|
20
|
+
|
|
21
|
+
i3X is a standardised REST facade in front of heterogeneous manufacturing
|
|
22
|
+
information platforms. This package lets Node-RED act as an **i3X client**,
|
|
23
|
+
talking to any compliant i3X server over HTTPS.
|
|
24
|
+
|
|
25
|
+
```mermaid
|
|
26
|
+
flowchart LR
|
|
27
|
+
subgraph SHOP["Shop floor / OT"]
|
|
28
|
+
PLC["PLCs · Sensors"]
|
|
29
|
+
OPC["OPC UA"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
subgraph PLATFORM["Information platforms"]
|
|
33
|
+
HIST["Historian"]
|
|
34
|
+
MES["MES / MOM"]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
subgraph I3X["i3X Server (REST API 1.0)"]
|
|
38
|
+
MODEL["Information Model\n(namespaces · types · objects · relationships)"]
|
|
39
|
+
API["REST endpoints\n/objects · /history · /subscriptions"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
subgraph NR["Node-RED + node-red-contrib-i3x"]
|
|
43
|
+
NODES["i3x nodes"]
|
|
44
|
+
FLOWS["Your flows / dashboards"]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
PLC --> OPC --> PLATFORM
|
|
48
|
+
PLATFORM --> I3X
|
|
49
|
+
MODEL --- API
|
|
50
|
+
API -- "HTTPS / SSE" --> NODES --> FLOWS
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Package internals
|
|
54
|
+
|
|
55
|
+
Every node delegates HTTP work to a single shared `I3XClient`, configured once
|
|
56
|
+
through the `i3x-server` config node. The client centralises all resilience
|
|
57
|
+
features (retry, caching, rate limiting, TLS, SSE reconnection).
|
|
58
|
+
|
|
59
|
+
```mermaid
|
|
60
|
+
flowchart TB
|
|
61
|
+
subgraph EDITOR["Node-RED flow"]
|
|
62
|
+
BROWSE["i3x-browse\nexplore model"]
|
|
63
|
+
READ["i3x-read\nlast values"]
|
|
64
|
+
WRITE["i3x-write\nvalue / history"]
|
|
65
|
+
HISTORY["i3x-history\ntime series"]
|
|
66
|
+
SUB["i3x-subscribe\nSSE / polling"]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
CONFIG["i3x-server (config node)\nBase URL · Auth · TLS · Timeout · clientId"]
|
|
70
|
+
|
|
71
|
+
subgraph CLIENT["lib/i3x-client.js — shared HTTP client"]
|
|
72
|
+
RETRY["Retry + backoff\n(429/502/503/504)"]
|
|
73
|
+
CACHE["TTL cache\n(namespaces, types)"]
|
|
74
|
+
RATE["Rate limiter\n(100 / 60s)"]
|
|
75
|
+
SSE["SSE stream\n+ polling fallback"]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
SERVER[("i3X REST API")]
|
|
79
|
+
|
|
80
|
+
BROWSE --> CONFIG
|
|
81
|
+
READ --> CONFIG
|
|
82
|
+
WRITE --> CONFIG
|
|
83
|
+
HISTORY --> CONFIG
|
|
84
|
+
SUB --> CONFIG
|
|
85
|
+
|
|
86
|
+
CONFIG --> CLIENT
|
|
87
|
+
CLIENT -- "axios · HTTPS" --> SERVER
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Request flow (example: read a value)
|
|
91
|
+
|
|
92
|
+
```mermaid
|
|
93
|
+
sequenceDiagram
|
|
94
|
+
participant F as Flow (msg)
|
|
95
|
+
participant N as i3x-read
|
|
96
|
+
participant C as I3XClient
|
|
97
|
+
participant S as i3X Server
|
|
98
|
+
|
|
99
|
+
F->>N: msg.elementIds
|
|
100
|
+
N->>C: getValues(ids, maxDepth)
|
|
101
|
+
C->>C: rate-limit check / cache lookup
|
|
102
|
+
C->>S: POST /objects/value
|
|
103
|
+
alt 429 / 5xx
|
|
104
|
+
S-->>C: error + Retry-After
|
|
105
|
+
C->>C: exponential backoff
|
|
106
|
+
C->>S: retry (max 3)
|
|
107
|
+
end
|
|
108
|
+
S-->>C: VQT values
|
|
109
|
+
C-->>N: result
|
|
110
|
+
N-->>F: msg.payload
|
|
111
|
+
```
|
|
112
|
+
|
|
9
113
|
## Installation
|
|
10
114
|
|
|
11
115
|
Install from within Node-RED via **Manage palette → Install**, or from the command line:
|
|
@@ -196,10 +300,18 @@ npm run test:unit
|
|
|
196
300
|
# Integration tests only (requires network access to demo server)
|
|
197
301
|
npm run test:integration
|
|
198
302
|
|
|
303
|
+
# Unit tests with coverage report
|
|
304
|
+
npm run test:coverage
|
|
305
|
+
|
|
306
|
+
# Lint (ESLint over lib/, nodes/, test/)
|
|
307
|
+
npm run lint
|
|
308
|
+
|
|
199
309
|
# Run all tests in Docker
|
|
200
310
|
npm run test:docker
|
|
201
311
|
```
|
|
202
312
|
|
|
313
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full development workflow.
|
|
314
|
+
|
|
203
315
|
## Docker
|
|
204
316
|
|
|
205
317
|
```bash
|
|
@@ -229,7 +341,7 @@ node-red
|
|
|
229
341
|
|
|
230
342
|
## References
|
|
231
343
|
|
|
232
|
-
- [i3X API Documentation](https://api.i3x.dev/
|
|
344
|
+
- [i3X API Documentation](https://api.i3x.dev/v1/docs)
|
|
233
345
|
- [i3X Client Developer Guide](https://www.i3x.dev/sdk/category/client-developers)
|
|
234
346
|
- [i3X Specification & RFC](https://github.com/cesmii/i3X)
|
|
235
347
|
- [i3X SDK Documentation](https://www.i3x.dev/sdk)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Type definitions for the shared i3X (CESMII) HTTP client.
|
|
2
|
+
// Targets the i3X API 1.0 Release specification.
|
|
3
|
+
// Project: https://github.com/blanpa/node-red-contrib-i3x
|
|
4
|
+
|
|
5
|
+
/// <reference types="node" />
|
|
6
|
+
|
|
7
|
+
import { EventEmitter } from "events";
|
|
8
|
+
|
|
9
|
+
export interface TlsOptions {
|
|
10
|
+
rejectUnauthorized?: boolean;
|
|
11
|
+
ca?: string | Buffer | Array<string | Buffer>;
|
|
12
|
+
cert?: string | Buffer;
|
|
13
|
+
key?: string | Buffer;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface I3XClientConfig {
|
|
17
|
+
/** Root URL of the i3X server, e.g. "https://i3x.cesmii.net". */
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
/** Optional path prefix, e.g. "v1". */
|
|
20
|
+
apiVersion?: string;
|
|
21
|
+
authType?: "none" | "basic" | "bearer" | "apikey";
|
|
22
|
+
username?: string;
|
|
23
|
+
password?: string;
|
|
24
|
+
token?: string;
|
|
25
|
+
apiKey?: string;
|
|
26
|
+
tlsOptions?: TlsOptions;
|
|
27
|
+
/** HTTP timeout in milliseconds (default 10000). */
|
|
28
|
+
timeout?: number;
|
|
29
|
+
/** Client identifier; required by the 1.0 spec on all subscription endpoints. */
|
|
30
|
+
clientId?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Value-Quality-Timestamp record. */
|
|
34
|
+
export interface VQT {
|
|
35
|
+
value: unknown;
|
|
36
|
+
quality?: string;
|
|
37
|
+
timestamp?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ValueUpdate {
|
|
41
|
+
elementId: string;
|
|
42
|
+
value: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ServerInfo {
|
|
46
|
+
specVersion: string;
|
|
47
|
+
serverVersion: string;
|
|
48
|
+
serverName: string;
|
|
49
|
+
capabilities: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Namespace {
|
|
53
|
+
uri: string;
|
|
54
|
+
displayName: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface Subscription {
|
|
58
|
+
subscriptionId: string;
|
|
59
|
+
clientId?: string;
|
|
60
|
+
displayName?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface StreamCallbacks {
|
|
64
|
+
onData: (event: unknown) => void;
|
|
65
|
+
onError?: (err: Error) => void;
|
|
66
|
+
onReconnect?: (attempt: number) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface StreamOptions {
|
|
70
|
+
clientId?: string;
|
|
71
|
+
maxReconnects?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface StreamHandle {
|
|
75
|
+
close: () => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Normalised error thrown by client requests. */
|
|
79
|
+
export interface I3XError extends Error {
|
|
80
|
+
_i3x: true;
|
|
81
|
+
statusCode?: number;
|
|
82
|
+
statusText?: string;
|
|
83
|
+
body?: unknown;
|
|
84
|
+
code?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default class I3XClient extends EventEmitter {
|
|
88
|
+
baseUrl: string;
|
|
89
|
+
apiVersion: string;
|
|
90
|
+
authType: string;
|
|
91
|
+
timeout: number;
|
|
92
|
+
clientId: string;
|
|
93
|
+
|
|
94
|
+
constructor(config: I3XClientConfig);
|
|
95
|
+
|
|
96
|
+
// Server info
|
|
97
|
+
getInfo(): Promise<ServerInfo>;
|
|
98
|
+
|
|
99
|
+
// Explore
|
|
100
|
+
getNamespaces(): Promise<Namespace[]>;
|
|
101
|
+
getObjectTypes(options?: { namespaceUri?: string }): Promise<unknown[]>;
|
|
102
|
+
queryObjectTypes(elementIds: string[]): Promise<unknown[]>;
|
|
103
|
+
getRelationshipTypes(options?: { namespaceUri?: string }): Promise<unknown[]>;
|
|
104
|
+
queryRelationshipTypes(elementIds: string[]): Promise<unknown[]>;
|
|
105
|
+
getObjects(options?: {
|
|
106
|
+
typeElementId?: string;
|
|
107
|
+
typeId?: string;
|
|
108
|
+
includeMetadata?: boolean;
|
|
109
|
+
root?: boolean;
|
|
110
|
+
}): Promise<unknown[]>;
|
|
111
|
+
listObjects(elementIds: string[], options?: { includeMetadata?: boolean }): Promise<unknown[]>;
|
|
112
|
+
getRelatedObjects(
|
|
113
|
+
elementIds: string[],
|
|
114
|
+
options?: { relationshipType?: string; includeMetadata?: boolean }
|
|
115
|
+
): Promise<unknown[]>;
|
|
116
|
+
|
|
117
|
+
// Query
|
|
118
|
+
readValues(elementIds: string[], options?: { maxDepth?: number }): Promise<unknown[]>;
|
|
119
|
+
readHistory(
|
|
120
|
+
elementIds: string[],
|
|
121
|
+
options?: { startTime?: string; endTime?: string; maxDepth?: number }
|
|
122
|
+
): Promise<unknown[] & { _partial?: boolean }>;
|
|
123
|
+
/** @deprecated Delegates to {@link readHistory}; the per-element endpoint was removed in 1.0. */
|
|
124
|
+
getHistory(
|
|
125
|
+
elementId: string,
|
|
126
|
+
options?: { startTime?: string; endTime?: string; maxDepth?: number }
|
|
127
|
+
): Promise<unknown[]>;
|
|
128
|
+
|
|
129
|
+
// Update (bulk-only in 1.0)
|
|
130
|
+
writeValue(elementId: string, value: unknown): Promise<unknown>;
|
|
131
|
+
writeValues(updates: ValueUpdate[]): Promise<unknown>;
|
|
132
|
+
writeHistory(elementId: string, data: VQT | VQT[] | unknown): Promise<unknown>;
|
|
133
|
+
|
|
134
|
+
// Subscribe
|
|
135
|
+
createSubscription(options?: { clientId?: string; displayName?: string }): Promise<Subscription>;
|
|
136
|
+
listSubscriptions(subscriptionIds: string[], options?: { clientId?: string }): Promise<unknown[]>;
|
|
137
|
+
deleteSubscriptions(subscriptionIds: string[], options?: { clientId?: string }): Promise<unknown>;
|
|
138
|
+
registerMonitoredItems(
|
|
139
|
+
subscriptionId: string,
|
|
140
|
+
elementIds: string[],
|
|
141
|
+
options?: { maxDepth?: number; clientId?: string } | number
|
|
142
|
+
): Promise<unknown>;
|
|
143
|
+
unregisterMonitoredItems(
|
|
144
|
+
subscriptionId: string,
|
|
145
|
+
elementIds: string[],
|
|
146
|
+
options?: { clientId?: string }
|
|
147
|
+
): Promise<unknown>;
|
|
148
|
+
streamSubscription(
|
|
149
|
+
subscriptionId: string,
|
|
150
|
+
callbacks: StreamCallbacks | ((event: unknown) => void),
|
|
151
|
+
options?: StreamOptions | number
|
|
152
|
+
): StreamHandle;
|
|
153
|
+
syncSubscription(
|
|
154
|
+
subscriptionId: string,
|
|
155
|
+
options?: { lastSequenceNumber?: number; clientId?: string }
|
|
156
|
+
): Promise<unknown>;
|
|
157
|
+
|
|
158
|
+
// Utility
|
|
159
|
+
testConnection(): Promise<boolean>;
|
|
160
|
+
destroy(): void;
|
|
161
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-i3x",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Node-RED nodes for the i3X (Industrial Information Interoperability eXchange) API by CESMII",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
|
@@ -24,18 +24,25 @@
|
|
|
24
24
|
"test": "mocha --exit --timeout 15000 'test/**/*_spec.js'",
|
|
25
25
|
"test:unit": "mocha --exit --timeout 10000 'test/unit/**/*_spec.js'",
|
|
26
26
|
"test:integration": "mocha --exit --timeout 30000 'test/integration/**/*_spec.js'",
|
|
27
|
-
"test:
|
|
27
|
+
"test:coverage": "c8 --reporter=text --reporter=lcov npm run test:unit",
|
|
28
|
+
"test:docker": "docker compose run --rm test",
|
|
29
|
+
"lint": "eslint lib nodes test",
|
|
30
|
+
"lint:fix": "eslint lib nodes test --fix"
|
|
28
31
|
},
|
|
32
|
+
"types": "lib/i3x-client.d.ts",
|
|
29
33
|
"dependencies": {
|
|
30
34
|
"axios": "^1.15.0"
|
|
31
35
|
},
|
|
32
36
|
"devDependencies": {
|
|
33
|
-
"
|
|
37
|
+
"@eslint/js": "^9.20.0",
|
|
38
|
+
"c8": "^10.1.3",
|
|
34
39
|
"chai": "^4.0.0",
|
|
35
|
-
"
|
|
40
|
+
"eslint": "^9.20.0",
|
|
41
|
+
"mocha": "^11.0.0",
|
|
36
42
|
"nock": "^14.0.0",
|
|
37
|
-
"node-red": "^
|
|
38
|
-
"node-red-node-test-helper": "^0.3.
|
|
43
|
+
"node-red": "^4.1.11",
|
|
44
|
+
"node-red-node-test-helper": "^0.3.6",
|
|
45
|
+
"sinon": "^21.0.0"
|
|
39
46
|
},
|
|
40
47
|
"node-red": {
|
|
41
48
|
"version": ">=2.0.0",
|
|
@@ -48,11 +55,14 @@
|
|
|
48
55
|
"i3x-subscribe": "nodes/i3x-subscribe.js"
|
|
49
56
|
},
|
|
50
57
|
"examples": [
|
|
51
|
-
{
|
|
58
|
+
{
|
|
59
|
+
"name": "i3x-complete-demo",
|
|
60
|
+
"path": "examples/i3x-complete-demo.json"
|
|
61
|
+
}
|
|
52
62
|
]
|
|
53
63
|
},
|
|
54
64
|
"engines": {
|
|
55
|
-
"node": ">=
|
|
65
|
+
"node": ">=18.0.0"
|
|
56
66
|
},
|
|
57
67
|
"author": "blanpa",
|
|
58
68
|
"homepage": "https://www.i3x.dev"
|