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 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
+ [![npm version](https://img.shields.io/npm/v/node-red-contrib-i3x.svg)](https://www.npmjs.com/package/node-red-contrib-i3x)
4
+ [![CI](https://github.com/blanpa/node-red-contrib-i3x/actions/workflows/ci.yml/badge.svg)](https://github.com/blanpa/node-red-contrib-i3x/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
6
+ [![Node.js](https://img.shields.io/node/v/node-red-contrib-i3x.svg)](https://nodejs.org)
7
+ [![i3X API](https://img.shields.io/badge/i3X%20API-1.0%20Release-5DB87C.svg)](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/v0/docs)
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.5",
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:docker": "docker compose run --rm 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
- "mocha": "^11.0.0",
37
+ "@eslint/js": "^9.20.0",
38
+ "c8": "^10.1.3",
34
39
  "chai": "^4.0.0",
35
- "sinon": "^21.0.0",
40
+ "eslint": "^9.20.0",
41
+ "mocha": "^11.0.0",
36
42
  "nock": "^14.0.0",
37
- "node-red": "^3.0.0",
38
- "node-red-node-test-helper": "^0.3.0"
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
- { "name": "i3x-complete-demo", "path": "examples/i3x-complete-demo.json" }
58
+ {
59
+ "name": "i3x-complete-demo",
60
+ "path": "examples/i3x-complete-demo.json"
61
+ }
52
62
  ]
53
63
  },
54
64
  "engines": {
55
- "node": ">=14.0.0"
65
+ "node": ">=18.0.0"
56
66
  },
57
67
  "author": "blanpa",
58
68
  "homepage": "https://www.i3x.dev"