node-red-contrib-i3x 0.0.3 → 0.0.4
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 +28 -0
- package/README.md +4 -2
- package/lib/i3x-client.js +163 -48
- package/nodes/i3x-subscribe.js +1 -1
- package/nodes/icons/i3x-icon.svg +1 -8
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.4 (2026-04-12)
|
|
4
|
+
|
|
5
|
+
Migration to i3X API 1.0-Beta specification with enhanced query capabilities and improved API alignment.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Server Info Endpoint** – New `getInfo()` method retrieves server metadata (spec version, server version, capabilities) from `GET /info` endpoint with TTL caching
|
|
10
|
+
- **Single-Object History Query** – New `getHistory(elementId, options)` method for `GET /objects/{elementId}/history` endpoint
|
|
11
|
+
- **Partial Response Handling** – History queries now detect HTTP 206 status and set `_partial: true` flag on results when server returns incomplete data
|
|
12
|
+
- **Root Objects Filter** – `getObjects()` now supports `root: true` parameter to retrieve only root-level objects
|
|
13
|
+
- **Enhanced Subscribe Options** – Subscribe node now supports `maxDepth`, `includeMetadata`, and `returnMode` parameters for fine-grained control
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **API Version Update** – Updated from i3X v0.0.1 to 1.0-Beta specification
|
|
18
|
+
- **API Documentation URL** – Changed from `https://i3x.cesmii.net/docs` to `https://api.i3x.dev/v1/docs`
|
|
19
|
+
- **Parameter Naming** – `typeId` parameter renamed to `typeElementId` in `getObjects()` (legacy `typeId` still supported as alias)
|
|
20
|
+
- **Relationship Type Parameter** – Fixed casing from `relationshiptype` to `relationshipType` in `getRelatedObjects()`
|
|
21
|
+
- **History Query Implementation** – `getHistoryBulk()` now uses `_requestRaw()` to access HTTP status codes for partial response detection
|
|
22
|
+
|
|
23
|
+
### Tests
|
|
24
|
+
|
|
25
|
+
- Updated all test cases to reflect 1.0-Beta API changes
|
|
26
|
+
- Added tests for new `getInfo()` endpoint
|
|
27
|
+
- Added tests for single-object history query
|
|
28
|
+
- Added tests for partial response handling (HTTP 206)
|
|
29
|
+
- Updated integration tests for new parameter names
|
|
30
|
+
|
|
3
31
|
## 0.0.3 (2026-03-10)
|
|
4
32
|
|
|
5
33
|
Hardening, security improvements, and new browser widget features.
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Node-RED nodes for the **i3X** (Industrial Information Interoperability eXchange
|
|
|
4
4
|
|
|
5
5
|
i3X is an open, vendor-agnostic REST API specification for standardised access to contextualised manufacturing information platforms (Historians, MES, MOM, etc.).
|
|
6
6
|
|
|
7
|
-
> **Note:** The i3X API is currently in **
|
|
7
|
+
> **Note:** The i3X API is currently in **beta (v1.0-Beta)**. Response structures may change as the specification evolves.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -108,10 +108,11 @@ The shared HTTP client (`lib/i3x-client.js`) implements all [i3X Client Develope
|
|
|
108
108
|
|
|
109
109
|
## API Endpoints Used
|
|
110
110
|
|
|
111
|
-
This package targets the [i3X API
|
|
111
|
+
This package targets the [i3X API 1.0-Beta](https://api.i3x.dev/v1/docs):
|
|
112
112
|
|
|
113
113
|
| Category | Method | Endpoint |
|
|
114
114
|
| --------- | ------ | -------------------------------------------- |
|
|
115
|
+
| Info | GET | `/info` |
|
|
115
116
|
| Explore | GET | `/namespaces` |
|
|
116
117
|
| Explore | GET | `/objecttypes` |
|
|
117
118
|
| Explore | POST | `/objecttypes/query` |
|
|
@@ -121,6 +122,7 @@ This package targets the [i3X API Prototype v0.0.1](https://api.i3x.dev/v0/docs)
|
|
|
121
122
|
| Explore | POST | `/objects/list` |
|
|
122
123
|
| Explore | POST | `/objects/related` |
|
|
123
124
|
| Query | POST | `/objects/value` |
|
|
125
|
+
| Query | GET | `/objects/{elementId}/history` |
|
|
124
126
|
| Query | POST | `/objects/history` |
|
|
125
127
|
| Update | PUT | `/objects/{elementId}/value` |
|
|
126
128
|
| Update | PUT | `/objects/{elementId}/history` |
|
package/lib/i3x-client.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* I3XClient – Shared HTTP client for the i3X (CESMII) API.
|
|
3
3
|
*
|
|
4
|
-
* Wraps all REST endpoints described in the i3X OpenAPI
|
|
4
|
+
* Wraps all REST endpoints described in the i3X OpenAPI 1.0-Beta spec.
|
|
5
5
|
* Used by every node-red-contrib-i3x node via the i3x-server config node.
|
|
6
6
|
*
|
|
7
|
-
* @see https://i3x.
|
|
7
|
+
* @see https://api.i3x.dev/v1/docs
|
|
8
8
|
*/
|
|
9
9
|
"use strict";
|
|
10
10
|
|
|
@@ -118,6 +118,21 @@ class I3XClient extends EventEmitter {
|
|
|
118
118
|
this._rateLimiter = new RateLimiter();
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// ── Server Info ──────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Retrieve server information (no authentication required).
|
|
125
|
+
* @returns {Promise<{specVersion:string, serverVersion:string, serverName:string, capabilities:object}>}
|
|
126
|
+
*/
|
|
127
|
+
async getInfo() {
|
|
128
|
+
const cacheKey = "info";
|
|
129
|
+
const cached = this._cache.get(cacheKey);
|
|
130
|
+
if (cached) return cached;
|
|
131
|
+
const result = await this._get("/info");
|
|
132
|
+
this._cache.set(cacheKey, result);
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
121
136
|
// ── Explore ────────────────────────────────────────────────────────
|
|
122
137
|
|
|
123
138
|
/** @returns {Promise<Array<{uri:string, displayName:string}>>} */
|
|
@@ -166,14 +181,18 @@ class I3XClient extends EventEmitter {
|
|
|
166
181
|
}
|
|
167
182
|
|
|
168
183
|
/**
|
|
169
|
-
* @param {object}
|
|
170
|
-
* @param {string} [options.
|
|
184
|
+
* @param {object} [options]
|
|
185
|
+
* @param {string} [options.typeElementId] – filter by type (Beta spec name)
|
|
186
|
+
* @param {string} [options.typeId] – legacy alias for typeElementId
|
|
171
187
|
* @param {boolean} [options.includeMetadata]
|
|
188
|
+
* @param {boolean} [options.root] – return only root objects
|
|
172
189
|
*/
|
|
173
190
|
async getObjects(options = {}) {
|
|
174
191
|
const params = {};
|
|
175
|
-
|
|
192
|
+
const typeFilter = options.typeElementId || options.typeId;
|
|
193
|
+
if (typeFilter) params.typeElementId = typeFilter;
|
|
176
194
|
if (options.includeMetadata) params.includeMetadata = true;
|
|
195
|
+
if (options.root) params.root = true;
|
|
177
196
|
return this._get("/objects", params);
|
|
178
197
|
}
|
|
179
198
|
|
|
@@ -196,7 +215,7 @@ class I3XClient extends EventEmitter {
|
|
|
196
215
|
*/
|
|
197
216
|
async getRelatedObjects(elementIds, options = {}) {
|
|
198
217
|
const body = { elementIds };
|
|
199
|
-
if (options.relationshipType) body.
|
|
218
|
+
if (options.relationshipType) body.relationshipType = options.relationshipType;
|
|
200
219
|
if (options.includeMetadata) body.includeMetadata = true;
|
|
201
220
|
return this._post("/objects/related", body);
|
|
202
221
|
}
|
|
@@ -215,6 +234,7 @@ class I3XClient extends EventEmitter {
|
|
|
215
234
|
}
|
|
216
235
|
|
|
217
236
|
/**
|
|
237
|
+
* Bulk historical values query (POST).
|
|
218
238
|
* @param {string[]} elementIds
|
|
219
239
|
* @param {object} [options]
|
|
220
240
|
* @param {string} [options.startTime] – ISO 8601
|
|
@@ -226,7 +246,37 @@ class I3XClient extends EventEmitter {
|
|
|
226
246
|
if (options.startTime) body.startTime = options.startTime;
|
|
227
247
|
if (options.endTime) body.endTime = options.endTime;
|
|
228
248
|
if (options.maxDepth !== undefined) body.maxDepth = options.maxDepth;
|
|
229
|
-
|
|
249
|
+
const res = await this._requestRaw("post", "/objects/history", { data: body });
|
|
250
|
+
const result = I3XClient._unwrapEnvelope(res.data);
|
|
251
|
+
if (res.status === 206) {
|
|
252
|
+
result._partial = true;
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Single-object historical values query (GET).
|
|
259
|
+
* @param {string} elementId
|
|
260
|
+
* @param {object} [options]
|
|
261
|
+
* @param {string} [options.startTime] – ISO 8601
|
|
262
|
+
* @param {string} [options.endTime] – ISO 8601
|
|
263
|
+
* @param {number} [options.maxDepth]
|
|
264
|
+
*/
|
|
265
|
+
async getHistory(elementId, options = {}) {
|
|
266
|
+
const params = {};
|
|
267
|
+
if (options.startTime) params.startTime = options.startTime;
|
|
268
|
+
if (options.endTime) params.endTime = options.endTime;
|
|
269
|
+
if (options.maxDepth !== undefined) params.maxDepth = options.maxDepth;
|
|
270
|
+
const res = await this._requestRaw(
|
|
271
|
+
"get",
|
|
272
|
+
`/objects/${encodeURIComponent(elementId)}/history`,
|
|
273
|
+
{ params }
|
|
274
|
+
);
|
|
275
|
+
const result = I3XClient._unwrapEnvelope(res.data);
|
|
276
|
+
if (res.status === 206) {
|
|
277
|
+
result._partial = true;
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
230
280
|
}
|
|
231
281
|
|
|
232
282
|
// ── Update ─────────────────────────────────────────────────────────
|
|
@@ -267,71 +317,106 @@ class I3XClient extends EventEmitter {
|
|
|
267
317
|
}
|
|
268
318
|
}
|
|
269
319
|
|
|
270
|
-
// ── Subscribe
|
|
271
|
-
|
|
272
|
-
async listSubscriptions() {
|
|
273
|
-
return this._get("/subscriptions");
|
|
274
|
-
}
|
|
320
|
+
// ── Subscribe (Beta-Spec: body-based endpoints) ──────────────────
|
|
275
321
|
|
|
276
|
-
/**
|
|
277
|
-
|
|
278
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Create a new subscription.
|
|
324
|
+
* @param {object} [options]
|
|
325
|
+
* @param {string} [options.clientId] – optional client identifier
|
|
326
|
+
* @param {string} [options.displayName] – optional human-readable name
|
|
327
|
+
* @returns {Promise<{subscriptionId:string, clientId?:string, displayName?:string}>}
|
|
328
|
+
*/
|
|
329
|
+
async createSubscription(options = {}) {
|
|
330
|
+
const body = {};
|
|
331
|
+
if (options.clientId) body.clientId = options.clientId;
|
|
332
|
+
if (options.displayName) body.displayName = options.displayName;
|
|
333
|
+
return this._post("/subscriptions", body);
|
|
279
334
|
}
|
|
280
335
|
|
|
281
|
-
/**
|
|
282
|
-
|
|
283
|
-
|
|
336
|
+
/**
|
|
337
|
+
* List subscriptions by IDs.
|
|
338
|
+
* @param {string[]} subscriptionIds
|
|
339
|
+
* @param {object} [options]
|
|
340
|
+
* @param {string} [options.clientId]
|
|
341
|
+
*/
|
|
342
|
+
async listSubscriptions(subscriptionIds, options = {}) {
|
|
343
|
+
const body = { subscriptionIds };
|
|
344
|
+
if (options.clientId) body.clientId = options.clientId;
|
|
345
|
+
return this._post("/subscriptions/list", body);
|
|
284
346
|
}
|
|
285
347
|
|
|
286
|
-
/**
|
|
287
|
-
|
|
288
|
-
|
|
348
|
+
/**
|
|
349
|
+
* Delete one or more subscriptions.
|
|
350
|
+
* @param {string[]} subscriptionIds
|
|
351
|
+
* @param {object} [options]
|
|
352
|
+
* @param {string} [options.clientId]
|
|
353
|
+
*/
|
|
354
|
+
async deleteSubscriptions(subscriptionIds, options = {}) {
|
|
355
|
+
const body = { subscriptionIds };
|
|
356
|
+
if (options.clientId) body.clientId = options.clientId;
|
|
357
|
+
return this._post("/subscriptions/delete", body);
|
|
289
358
|
}
|
|
290
359
|
|
|
291
360
|
/**
|
|
361
|
+
* Register monitored items on a subscription.
|
|
292
362
|
* @param {string} subscriptionId
|
|
293
363
|
* @param {string[]} elementIds
|
|
294
|
-
* @param {
|
|
364
|
+
* @param {object} [options]
|
|
365
|
+
* @param {number} [options.maxDepth] – default 1
|
|
366
|
+
* @param {string} [options.clientId]
|
|
295
367
|
*/
|
|
296
|
-
async registerMonitoredItems(subscriptionId, elementIds,
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
368
|
+
async registerMonitoredItems(subscriptionId, elementIds, options = {}) {
|
|
369
|
+
// Support legacy positional maxDepth: registerMonitoredItems(id, ids, 2)
|
|
370
|
+
if (typeof options === "number") {
|
|
371
|
+
options = { maxDepth: options };
|
|
372
|
+
}
|
|
373
|
+
const body = { subscriptionId, elementIds, maxDepth: options.maxDepth !== undefined ? options.maxDepth : 1 };
|
|
374
|
+
if (options.clientId) body.clientId = options.clientId;
|
|
375
|
+
return this._post("/subscriptions/register", body);
|
|
302
376
|
}
|
|
303
377
|
|
|
304
378
|
/**
|
|
379
|
+
* Unregister monitored items from a subscription.
|
|
305
380
|
* @param {string} subscriptionId
|
|
306
381
|
* @param {string[]} elementIds
|
|
382
|
+
* @param {object} [options]
|
|
383
|
+
* @param {string} [options.clientId]
|
|
307
384
|
*/
|
|
308
|
-
async unregisterMonitoredItems(subscriptionId, elementIds) {
|
|
309
|
-
const body = { elementIds };
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
body
|
|
313
|
-
);
|
|
385
|
+
async unregisterMonitoredItems(subscriptionId, elementIds, options = {}) {
|
|
386
|
+
const body = { subscriptionId, elementIds };
|
|
387
|
+
if (options.clientId) body.clientId = options.clientId;
|
|
388
|
+
return this._post("/subscriptions/unregister", body);
|
|
314
389
|
}
|
|
315
390
|
|
|
316
391
|
/**
|
|
317
|
-
* Open an SSE stream for the given subscription.
|
|
392
|
+
* Open an SSE stream for the given subscription (POST).
|
|
318
393
|
* Supports automatic reconnection on stream errors.
|
|
319
394
|
*
|
|
320
395
|
* @param {string} subscriptionId
|
|
321
396
|
* @param {object} callbacks
|
|
322
397
|
* @param {function} callbacks.onData – called with each parsed SSE event
|
|
323
|
-
* @param {function} [callbacks.onError] – called on stream errors
|
|
398
|
+
* @param {function} [callbacks.onError] – called on stream errors
|
|
324
399
|
* @param {function} [callbacks.onReconnect] – called when a reconnection attempt starts
|
|
325
|
-
* @param {
|
|
400
|
+
* @param {object} [options]
|
|
401
|
+
* @param {string} [options.clientId]
|
|
402
|
+
* @param {number} [options.maxReconnects=5]
|
|
326
403
|
* @returns {{ close: function }} handle to close the stream
|
|
327
404
|
*/
|
|
328
|
-
streamSubscription(subscriptionId, callbacks,
|
|
405
|
+
streamSubscription(subscriptionId, callbacks, options = {}) {
|
|
329
406
|
if (typeof callbacks === "function") {
|
|
330
407
|
callbacks = { onData: callbacks };
|
|
331
408
|
}
|
|
409
|
+
// Support legacy positional maxReconnects number
|
|
410
|
+
if (typeof options === "number") {
|
|
411
|
+
options = { maxReconnects: options };
|
|
412
|
+
}
|
|
332
413
|
const { onData, onError, onReconnect } = callbacks;
|
|
414
|
+
const maxReconnects = options.maxReconnects !== undefined ? options.maxReconnects : 5;
|
|
415
|
+
|
|
416
|
+
const url = `${this._prefix()}/subscriptions/stream`;
|
|
417
|
+
const postBody = { subscriptionId };
|
|
418
|
+
if (options.clientId) postBody.clientId = options.clientId;
|
|
333
419
|
|
|
334
|
-
const url = `${this._prefix()}/subscriptions/${encodeURIComponent(subscriptionId)}/stream`;
|
|
335
420
|
let controller = new AbortController();
|
|
336
421
|
let closed = false;
|
|
337
422
|
let reconnectCount = 0;
|
|
@@ -340,6 +425,7 @@ class I3XClient extends EventEmitter {
|
|
|
340
425
|
...this.http.defaults.headers.common,
|
|
341
426
|
...this.http.defaults.headers,
|
|
342
427
|
Accept: "text/event-stream",
|
|
428
|
+
"Content-Type": "application/json",
|
|
343
429
|
};
|
|
344
430
|
// Remove non-header axios defaults that got spread in
|
|
345
431
|
delete headers.common;
|
|
@@ -361,8 +447,9 @@ class I3XClient extends EventEmitter {
|
|
|
361
447
|
try {
|
|
362
448
|
controller = new AbortController();
|
|
363
449
|
const response = await axios({
|
|
364
|
-
method: "
|
|
450
|
+
method: "post",
|
|
365
451
|
url,
|
|
452
|
+
data: postBody,
|
|
366
453
|
headers,
|
|
367
454
|
responseType: "stream",
|
|
368
455
|
signal: controller.signal,
|
|
@@ -424,14 +511,19 @@ class I3XClient extends EventEmitter {
|
|
|
424
511
|
}
|
|
425
512
|
|
|
426
513
|
/**
|
|
427
|
-
* Poll-based sync:
|
|
514
|
+
* Poll-based sync: acknowledge previously received updates and return pending ones.
|
|
428
515
|
* @param {string} subscriptionId
|
|
516
|
+
* @param {object} [options]
|
|
517
|
+
* @param {number} [options.lastSequenceNumber] – acknowledge events up to this sequence number
|
|
518
|
+
* @param {string} [options.clientId]
|
|
429
519
|
*/
|
|
430
|
-
async syncSubscription(subscriptionId) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
520
|
+
async syncSubscription(subscriptionId, options = {}) {
|
|
521
|
+
const body = { subscriptionId };
|
|
522
|
+
if (options.lastSequenceNumber !== undefined) {
|
|
523
|
+
body.lastSequenceNumber = options.lastSequenceNumber;
|
|
524
|
+
}
|
|
525
|
+
if (options.clientId) body.clientId = options.clientId;
|
|
526
|
+
return this._post("/subscriptions/sync", body);
|
|
435
527
|
}
|
|
436
528
|
|
|
437
529
|
// ── Utility ────────────────────────────────────────────────────────
|
|
@@ -484,15 +576,38 @@ class I3XClient extends EventEmitter {
|
|
|
484
576
|
|
|
485
577
|
/**
|
|
486
578
|
* Central request dispatcher with retry logic, Retry-After support, and rate limiting.
|
|
579
|
+
* Returns unwrapped result data. Use _requestRaw for full response access.
|
|
487
580
|
* @private
|
|
488
581
|
*/
|
|
489
582
|
async _request(method, path, opts = {}) {
|
|
583
|
+
const res = await this._requestRaw(method, path, opts);
|
|
584
|
+
return I3XClient._unwrapEnvelope(res.data);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Unwrap the Beta-Spec response envelope if present.
|
|
589
|
+
* SuccessResponse: {success, result} → result
|
|
590
|
+
* BulkResponse: {success, results} → results
|
|
591
|
+
* @private
|
|
592
|
+
*/
|
|
593
|
+
static _unwrapEnvelope(data) {
|
|
594
|
+
if (data && typeof data === "object" && data.success === true) {
|
|
595
|
+
if ("result" in data) return data.result;
|
|
596
|
+
if ("results" in data) return data.results;
|
|
597
|
+
}
|
|
598
|
+
return data;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Central request dispatcher returning the full axios response.
|
|
603
|
+
* @private
|
|
604
|
+
*/
|
|
605
|
+
async _requestRaw(method, path, opts = {}) {
|
|
490
606
|
await this._rateLimiter.acquire();
|
|
491
607
|
let lastErr;
|
|
492
608
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
493
609
|
try {
|
|
494
|
-
|
|
495
|
-
return res.data;
|
|
610
|
+
return await this.http.request({ method, url: path, ...opts });
|
|
496
611
|
} catch (err) {
|
|
497
612
|
lastErr = err;
|
|
498
613
|
const status = err.response && err.response.status;
|
package/nodes/i3x-subscribe.js
CHANGED
|
@@ -125,7 +125,7 @@ module.exports = function (RED) {
|
|
|
125
125
|
|
|
126
126
|
if (node._subscriptionId && node.server && node.server.client) {
|
|
127
127
|
try {
|
|
128
|
-
await node.server.client.
|
|
128
|
+
await node.server.client.deleteSubscriptions([node._subscriptionId]);
|
|
129
129
|
} catch (_) {
|
|
130
130
|
// best-effort cleanup
|
|
131
131
|
}
|
package/nodes/icons/i3x-icon.svg
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="40" height="40">
|
|
2
2
|
<rect width="40" height="40" rx="4" fill="#2E8B57"/>
|
|
3
|
-
<text x="20" y="
|
|
4
|
-
<line x1="8" y1="22" x2="32" y2="22" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
|
|
5
|
-
<circle cx="12" cy="30" r="3" fill="none" stroke="white" stroke-width="1.5"/>
|
|
6
|
-
<circle cx="20" cy="30" r="3" fill="none" stroke="white" stroke-width="1.5"/>
|
|
7
|
-
<circle cx="28" cy="30" r="3" fill="none" stroke="white" stroke-width="1.5"/>
|
|
8
|
-
<line x1="12" y1="27" x2="12" y2="22" stroke="white" stroke-width="1.5"/>
|
|
9
|
-
<line x1="20" y1="27" x2="20" y2="22" stroke="white" stroke-width="1.5"/>
|
|
10
|
-
<line x1="28" y1="27" x2="28" y2="22" stroke="white" stroke-width="1.5"/>
|
|
3
|
+
<text x="20" y="26" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" font-weight="bold" fill="white">i3X</text>
|
|
11
4
|
</svg>
|
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.4",
|
|
4
4
|
"description": "Node-RED nodes for the i3X (Industrial Information Interoperability eXchange) API by CESMII",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"test:docker": "docker compose run --rm test"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"axios": "^1.
|
|
30
|
+
"axios": "^1.15.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"mocha": "^
|
|
33
|
+
"mocha": "^11.0.0",
|
|
34
34
|
"chai": "^4.0.0",
|
|
35
|
-
"sinon": "^
|
|
36
|
-
"nock": "^
|
|
35
|
+
"sinon": "^21.0.0",
|
|
36
|
+
"nock": "^14.0.0",
|
|
37
37
|
"node-red": "^3.0.0",
|
|
38
38
|
"node-red-node-test-helper": "^0.3.0"
|
|
39
39
|
},
|