@winkjs/composer 0.8.0 → 0.8.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/README.md
CHANGED
|
@@ -38,7 +38,7 @@ The Quick Start below builds one such pipeline end to end.
|
|
|
38
38
|
|
|
39
39
|
## Quick Start
|
|
40
40
|
|
|
41
|
-
Requires Node.js 22 or newer.
|
|
41
|
+
Requires Node.js 22 or newer. Each release is certified on the newest long-term support line of Node.js, 26 today, and tested on 22 and 24.
|
|
42
42
|
|
|
43
43
|
The Quick Start flow replays a CSV feed of pump motor temperatures. It cleans each reading, flags a hot motor, confirms the heat is not a blip, and prints an alert. One command scaffolds it as a runnable project:
|
|
44
44
|
|
|
@@ -106,15 +106,15 @@ The example above replays a CSV file. A live deployment reads from an MQTT broke
|
|
|
106
106
|
|
|
107
107
|
The numbers below come from a pure compute benchmark — every step a live message takes through an 8-node flow, from arrival to final output. **Storage and MQTT I/O are excluded** — these are compute ceilings, not full-deployment numbers.
|
|
108
108
|
|
|
109
|
-
| Configuration
|
|
110
|
-
|
|
111
|
-
|
|
|
112
|
-
| MacBook Pro (M4 Max)
|
|
113
|
-
| Tracking 10K assets
|
|
109
|
+
| Configuration | Throughput |
|
|
110
|
+
|-------------------------------|-------------------------|
|
|
111
|
+
| Revolution Pi Connect 5 (CM5) | 200K+ messages/second |
|
|
112
|
+
| MacBook Pro (M4 Max) | ~1.1M messages/second |
|
|
113
|
+
| Tracking 10K assets | ~500K messages/second |
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
The first row was measured on a Revolution Pi Connect 5, an industrial computer built on the Raspberry Pi Compute Module 5. The other two rows were measured on a MacBook Pro (M4 Max). All three ran under Node.js 26. Every figure is single-core.
|
|
116
116
|
|
|
117
|
-
Two configurations produced these numbers. The first two rows interleave 10 asset pipelines in random order — 4.5 million messages (10 pipelines × 900 data points × 500 iterations). The third row runs the same flow with 10,000 assets — 10,000 isolated states alive at once, 9 million messages,
|
|
117
|
+
Two configurations produced these numbers. The first two rows interleave 10 asset pipelines in random order — 4.5 million messages (10 pipelines × 900 data points × 500 iterations). The third row runs the same flow with 10,000 assets — 10,000 isolated states alive at once, 9 million messages, about 150 MB of heap. Pipelines are created dynamically as each asset first appears; timing uses `process.hrtime.bigint()`. The same pipeline [runs in your browser](https://composer.winkjs.org/docs/benchmark) — browser performance varies from native Node.js due to JIT differences.
|
|
118
118
|
|
|
119
119
|
Reproduce them from a repo clone. `node benchmark/compare.js 10 500` reports the median of three rounds. `node benchmark/run-benchmark.js static 10000 1` gives the 10K-asset row.
|
|
120
120
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@winkjs/composer",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Composable Streaming Intelligence — turn streaming data into actionable insights, from edge to cloud",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"streaming",
|
|
@@ -61,23 +61,25 @@
|
|
|
61
61
|
"access": "public"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"c8": "^
|
|
64
|
+
"c8": "^12.0.0",
|
|
65
65
|
"chai": "^5.2.0",
|
|
66
66
|
"classic-level": "^3.0.0",
|
|
67
67
|
"eslint": "^9.21.0",
|
|
68
68
|
"eslint-plugin-security": "^4.0.0",
|
|
69
|
-
"mocha": "^
|
|
69
|
+
"mocha": "^12.0.3",
|
|
70
70
|
"sinon": "^19.0.2"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@questdb/nodejs-client": "~4.2.0",
|
|
74
|
-
"glob": "^
|
|
75
|
-
"mqtt": "^5.
|
|
74
|
+
"glob": "^14.0.3",
|
|
75
|
+
"mqtt": "^5.16.1",
|
|
76
76
|
"msgpackr": "^1.11.10",
|
|
77
77
|
"pg": "^8.20.0"
|
|
78
78
|
},
|
|
79
79
|
"overrides": {
|
|
80
80
|
"serialize-javascript": "^7.1.0",
|
|
81
|
-
"diff": "^8.0.3"
|
|
81
|
+
"diff": "^8.0.3",
|
|
82
|
+
"minimatch": "^11.0.3",
|
|
83
|
+
"glob": "^14.0.3"
|
|
82
84
|
}
|
|
83
85
|
}
|
|
@@ -61,11 +61,27 @@ export const QUEUE_CRITICAL_THRESHOLD = 0.8;
|
|
|
61
61
|
*
|
|
62
62
|
* The emitter uses the client's UniqueMessageIdProvider, which never
|
|
63
63
|
* reissues an id that is in use. So the unacked window must never
|
|
64
|
-
* hold more messages than the id space has ids.
|
|
65
|
-
*
|
|
64
|
+
* hold more messages than the id space has ids. The id space alone
|
|
65
|
+
* would allow about 60,000.
|
|
66
|
+
*
|
|
67
|
+
* A second, tighter limit sets the value. libuv 1.52 and later (the
|
|
68
|
+
* version inside Node 24 and Node 26) refuses one socket write call
|
|
69
|
+
* that carries more than 1,048,576 buffer pieces. The call fails
|
|
70
|
+
* with `write EINVAL` and the connection drops. Source:
|
|
71
|
+
* `uv__check_before_write` in libuv `src/unix/stream.c`, added by
|
|
72
|
+
* libuv PR #5014. mqtt-packet corks the socket, so every publish
|
|
73
|
+
* accepted inside one event-loop turn leaves in ONE write call. One
|
|
74
|
+
* MQTT 5 publish with this emitter's properties is 29 pieces. The
|
|
75
|
+
* accepted window is 0.9 × maxQueueSize, and the whole window must
|
|
76
|
+
* fit one call: 33,000 × 0.9 × 29 = 861,300 pieces, 82% of the
|
|
77
|
+
* limit. Even the full cap fits: 33,000 × 29 = 957,000, 91%.
|
|
78
|
+
* mqtt.js resends a stored backlog one packet per acknowledgement,
|
|
79
|
+
* but it releases the publishes queued during that resend in one
|
|
80
|
+
* turn, so the window bounds that path too. The constant keeps its
|
|
81
|
+
* original name. The id space is still the first of its two reasons.
|
|
66
82
|
* @type {number}
|
|
67
83
|
*/
|
|
68
|
-
export const MQTT_INFLIGHT_ID_LIMIT =
|
|
84
|
+
export const MQTT_INFLIGHT_ID_LIMIT = 33000;
|
|
69
85
|
|
|
70
86
|
// Message expiry intervals (seconds), indexed by the caller's
|
|
71
87
|
// `options.type`. A dictionary indexed by an external key has no
|
|
@@ -105,9 +105,10 @@
|
|
|
105
105
|
* - Pre-flight reject fires at `pressure >= STORAGE_PRESSURE_LIMIT` (0.9),
|
|
106
106
|
* leaving 10% headroom for in-flight drain on shutdown and a clean band
|
|
107
107
|
* between the yellow-health threshold (0.66) and exhaustion (1.0).
|
|
108
|
-
* - The cap is `maxQueueSize`, clamped to
|
|
109
|
-
*
|
|
110
|
-
*
|
|
108
|
+
* - The cap is `maxQueueSize`, clamped to MQTT_INFLIGHT_ID_LIMIT
|
|
109
|
+
* (33,000). Two limits set that ceiling: the 16-bit packet-id space,
|
|
110
|
+
* and the libuv cap on buffer pieces per socket write. constants.js
|
|
111
|
+
* carries both with their sources.
|
|
111
112
|
* - The old byte-axis limit (`maxQueueBytes`) died with the disk store;
|
|
112
113
|
* the count cap bounds memory at cap × payload size.
|
|
113
114
|
*
|
|
@@ -65,7 +65,9 @@
|
|
|
65
65
|
*
|
|
66
66
|
* `brokerUrl` falls back to `MQTT_BROKER_URL`. The reconnect period
|
|
67
67
|
* gains a random share of up to 20% per client (`utils/jitter`).
|
|
68
|
-
* The cap is clamped to
|
|
68
|
+
* The cap is clamped to 33,000, the in-flight ceiling. constants.js
|
|
69
|
+
* carries its two reasons: the packet-id space and the libuv write
|
|
70
|
+
* limit.
|
|
69
71
|
*
|
|
70
72
|
* Related decisions beyond ADR-018 and ADR-021. ADR-022: this adapter
|
|
71
73
|
* writes the `winkDedupId` the source dedups on. ADR-023: `publishNow`
|
|
@@ -213,11 +213,11 @@ const resolveGrace = function ( config ) {
|
|
|
213
213
|
}; // resolveGrace()
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
|
-
* Resolves the unacked cap
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
216
|
+
* Resolves the unacked cap, clamped to MQTT_INFLIGHT_ID_LIMIT. Two
|
|
217
|
+
* limits set that ceiling: the 16-bit packet-id space, and the libuv
|
|
218
|
+
* cap on buffer pieces per socket write. constants.js carries both
|
|
219
|
+
* with their sources. The clamp prints the same warning the LevelDB
|
|
220
|
+
* store applied.
|
|
221
221
|
*
|
|
222
222
|
* @param {Object} config - emitter config
|
|
223
223
|
* @returns {number} the effective cap
|
|
@@ -227,7 +227,7 @@ const resolveQueueSize = function ( config ) {
|
|
|
227
227
|
if ( requestedQueueSize > MQTT_INFLIGHT_ID_LIMIT ) {
|
|
228
228
|
logger.warn(
|
|
229
229
|
`winkComposer/mqttEmitter: maxQueueSize ${requestedQueueSize} exceeds the MQTT ` +
|
|
230
|
-
`
|
|
230
|
+
`in-flight ceiling — clamped to ${MQTT_INFLIGHT_ID_LIMIT}`
|
|
231
231
|
);
|
|
232
232
|
}
|
|
233
233
|
return Math.min( requestedQueueSize, MQTT_INFLIGHT_ID_LIMIT );
|