@winkjs/composer 0.0.2 → 0.4.0
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/LICENSE +1 -1
- package/README.md +42 -103
- package/package.json +53 -29
- package/src/composer.js +75 -0
- package/src/core/batch/aoo-encoder.js +148 -0
- package/src/core/batch/index.js +12 -0
- package/src/core/batch/sliding-window.js +91 -0
- package/src/core/codec/index.js +137 -0
- package/src/core/emitter-manager/mqtt/constants.js +102 -0
- package/src/core/emitter-manager/mqtt/emitter.js +780 -0
- package/src/core/emitter-manager/mqtt/index.js +190 -0
- package/src/core/emitter-manager/mqtt/mqtt-store.js +538 -0
- package/src/core/emitter-manager/terminal/assert-columns.js +98 -0
- package/src/core/emitter-manager/terminal/formatters.js +160 -0
- package/src/core/emitter-manager/terminal/index.js +341 -0
- package/src/core/env-vars.js +245 -0
- package/src/core/mqtt-protocol.js +47 -0
- package/src/core/partition-manager/index.js +7 -0
- package/src/core/partition-manager/init.js +44 -0
- package/src/core/partition-manager/introspect.js +15 -0
- package/src/core/partition-manager/publish-to.js +6 -0
- package/src/core/partition-manager/recompute.js +5 -0
- package/src/core/partition-manager/reset.js +5 -0
- package/src/core/partition-manager/update.js +264 -0
- package/src/core/partition-manager/validate-flow.js +157 -0
- package/src/core/semantics/digest.js +128 -0
- package/src/core/semantics/index.js +83 -0
- package/src/core/semantics/loader.js +571 -0
- package/src/core/semantics/schemas/asset-class-schema.js +218 -0
- package/src/core/semantics/schemas/column-schema.js +651 -0
- package/src/core/semantics/schemas/enum-schema.js +153 -0
- package/src/core/semantics/schemas/index.js +51 -0
- package/src/core/semantics/warnings.js +141 -0
- package/src/core/shutdown-manager/index.js +289 -0
- package/src/core/source-manager/csv/index.js +171 -0
- package/src/core/source-manager/csv/start.js +456 -0
- package/src/core/source-manager/mqtt/client.js +406 -0
- package/src/core/source-manager/mqtt/constants.js +147 -0
- package/src/core/source-manager/mqtt/dedup.js +244 -0
- package/src/core/source-manager/mqtt/index.js +260 -0
- package/src/core/source-manager/mqtt/status.js +546 -0
- package/src/core/source-manager/test-harness/comparator.js +226 -0
- package/src/core/source-manager/test-harness/field-generator.js +85 -0
- package/src/core/source-manager/test-harness/fuzz.js +87 -0
- package/src/core/source-manager/test-harness/index.js +115 -0
- package/src/core/source-manager/test-harness/prng.js +57 -0
- package/src/core/source-manager/test-harness/start.js +232 -0
- package/src/core/source-manager/test-harness/validate.js +213 -0
- package/src/core/storage-manager/questdb/assert-columns.js +120 -0
- package/src/core/storage-manager/questdb/ensure-tables.js +187 -0
- package/src/core/storage-manager/questdb/index.js +1251 -0
- package/src/core/storage-manager/questdb/persist-plan.js +488 -0
- package/src/core/storage-manager/questdb/writers.js +142 -0
- package/src/core/test-utils/sealed-record.js +80 -0
- package/src/core/test-utils/soak-signature.js +149 -0
- package/src/core/test-utils/tcp-proxy.js +106 -0
- package/src/core/tunable/helpers.js +254 -0
- package/src/core/tunable/index.js +87 -0
- package/src/core/utils/console-formatter/index.js +207 -0
- package/src/core/utils/create-smart-store-as/index.js +98 -0
- package/src/core/utils/create-smart-store-as/sanitize-field.js +46 -0
- package/src/core/utils/create-smart-store-as/smart-truncate.js +93 -0
- package/src/core/utils/flow/index.js +5 -0
- package/src/core/utils/flow/make-collision-checker.js +23 -0
- package/src/core/utils/flow/to-kebab.js +20 -0
- package/src/core/utils/half-life/index.js +111 -0
- package/src/core/utils/node/disable.js +11 -0
- package/src/core/utils/node/enable.js +11 -0
- package/src/core/utils/node/execute-triggers.js +40 -0
- package/src/core/utils/node/index.js +10 -0
- package/src/core/utils/node/pause.js +11 -0
- package/src/core/utils/node/populate-predicate-inputs.js +14 -0
- package/src/core/utils/node/publish-nan.js +19 -0
- package/src/core/utils/node/reset-predicate-inputs.js +11 -0
- package/src/core/utils/node/unpause.js +11 -0
- package/src/core/utils/node/validate-spec.js +22 -0
- package/src/core/utils/options/resolve-field-keyed.js +115 -0
- package/src/core/utils/quantize/index.js +75 -0
- package/src/core/utils/stats/formulas.js +67 -0
- package/src/core/utils/stats/index.js +3 -0
- package/src/core/utils/validate/composer-validators.js +141 -0
- package/src/core/utils/validate/helpers.js +206 -0
- package/src/core/utils/validate/index.js +12 -0
- package/src/core/utils/validate/type-validators.js +538 -0
- package/src/core/utils/validate/validate.js +107 -0
- package/src/core/utils/validate/validators.js +177 -0
- package/src/core/wiring/assert-handle.js +57 -0
- package/src/core/wiring/assert-module.js +63 -0
- package/src/core/wiring/index.js +5 -0
- package/src/core/wiring/node-type-to-module.js +17 -0
- package/src/core/wiring/wire-emitters.js +256 -0
- package/src/core/wiring/wire-linear-graph.js +25 -0
- package/src/core/wiring/wire-node.js +157 -0
- package/src/core/wiring/wire-semantics.js +173 -0
- package/src/core/wiring/wire-storages.js +344 -0
- package/src/flow/check-output-collisions.js +93 -0
- package/src/flow/consts.js +9 -0
- package/src/flow/deep-clone-spec.js +64 -0
- package/src/flow/driver.js +171 -0
- package/src/flow/expand-single-input-array.js +76 -0
- package/src/flow/expand-template-spec.js +93 -0
- package/src/flow/flow-api-schemas.js +300 -0
- package/src/flow/flow.js +658 -0
- package/src/flow/for-each.js +225 -0
- package/src/flow/get-signature-pattern.js +31 -0
- package/src/flow/get-target-array.js +46 -0
- package/src/flow/inspect.js +67 -0
- package/src/flow/load-node-module.js +81 -0
- package/src/flow/make-node-method.js +112 -0
- package/src/flow/resolve-pick-by-field.js +77 -0
- package/src/flow/resolve-tunables.js +76 -0
- package/src/flow/run.js +392 -0
- package/src/flow/serialize.js +161 -0
- package/src/flow/signatures-schema.js +222 -0
- package/src/flow/spec-builder.js +65 -0
- package/src/flow/validate-triggers.js +250 -0
- package/src/flow/validate.js +261 -0
- package/src/nodes/accumulate/index.js +21 -0
- package/src/nodes/accumulate/init.js +48 -0
- package/src/nodes/accumulate/introspect.js +123 -0
- package/src/nodes/accumulate/publish-to.js +34 -0
- package/src/nodes/accumulate/recompute.js +27 -0
- package/src/nodes/accumulate/reset.js +25 -0
- package/src/nodes/accumulate/update.js +42 -0
- package/src/nodes/appraise/calibrate.js +64 -0
- package/src/nodes/appraise/decision.js +66 -0
- package/src/nodes/appraise/deviation.js +140 -0
- package/src/nodes/appraise/index.js +23 -0
- package/src/nodes/appraise/init.js +165 -0
- package/src/nodes/appraise/integrate.js +45 -0
- package/src/nodes/appraise/introspect.js +352 -0
- package/src/nodes/appraise/publish-to.js +93 -0
- package/src/nodes/appraise/receptor.js +69 -0
- package/src/nodes/appraise/recompute.js +79 -0
- package/src/nodes/appraise/reset.js +53 -0
- package/src/nodes/appraise/update.js +130 -0
- package/src/nodes/archive/csv/index.js +6 -0
- package/src/nodes/archive/csv/init.js +36 -0
- package/src/nodes/archive/csv/load.js +70 -0
- package/src/nodes/archive/csv/reset.js +14 -0
- package/src/nodes/archive/csv/simple-parser.js +216 -0
- package/src/nodes/archive/csv/test.js +173 -0
- package/src/nodes/archive/csv/update.js +63 -0
- package/src/nodes/butterworth-filter/index.js +18 -0
- package/src/nodes/butterworth-filter/init.js +184 -0
- package/src/nodes/butterworth-filter/introspect.js +228 -0
- package/src/nodes/butterworth-filter/publish-to.js +21 -0
- package/src/nodes/butterworth-filter/recompute.js +13 -0
- package/src/nodes/butterworth-filter/reset.js +30 -0
- package/src/nodes/butterworth-filter/update.js +38 -0
- package/src/nodes/categorize/index.js +19 -0
- package/src/nodes/categorize/init.js +58 -0
- package/src/nodes/categorize/introspect.js +177 -0
- package/src/nodes/categorize/publish-to.js +29 -0
- package/src/nodes/categorize/recompute.js +14 -0
- package/src/nodes/categorize/reset.js +16 -0
- package/src/nodes/categorize/update.js +53 -0
- package/src/nodes/controller/index.js +9 -0
- package/src/nodes/controller/init.js +55 -0
- package/src/nodes/controller/introspect.js +91 -0
- package/src/nodes/controller/publish-to.js +8 -0
- package/src/nodes/controller/recompute.js +9 -0
- package/src/nodes/controller/reset.js +17 -0
- package/src/nodes/controller/update.js +63 -0
- package/src/nodes/diff/index.js +19 -0
- package/src/nodes/diff/init.js +37 -0
- package/src/nodes/diff/introspect.js +132 -0
- package/src/nodes/diff/publish-to.js +21 -0
- package/src/nodes/diff/recompute.js +12 -0
- package/src/nodes/diff/reset.js +12 -0
- package/src/nodes/diff/update.js +37 -0
- package/src/nodes/digest-moments/formulas.js +12 -0
- package/src/nodes/digest-moments/index.js +14 -0
- package/src/nodes/digest-moments/init.js +67 -0
- package/src/nodes/digest-moments/introspect.js +154 -0
- package/src/nodes/digest-moments/publish-to.js +34 -0
- package/src/nodes/digest-moments/recompute.js +14 -0
- package/src/nodes/digest-moments/reset.js +14 -0
- package/src/nodes/digest-moments/update.js +120 -0
- package/src/nodes/dwell-time-tracker/index.js +14 -0
- package/src/nodes/dwell-time-tracker/init.js +59 -0
- package/src/nodes/dwell-time-tracker/introspect.js +137 -0
- package/src/nodes/dwell-time-tracker/publish-to.js +22 -0
- package/src/nodes/dwell-time-tracker/recompute.js +8 -0
- package/src/nodes/dwell-time-tracker/reset.js +23 -0
- package/src/nodes/dwell-time-tracker/update.js +103 -0
- package/src/nodes/emit-if/helpers.js +94 -0
- package/src/nodes/emit-if/index.js +16 -0
- package/src/nodes/emit-if/init.js +67 -0
- package/src/nodes/emit-if/introspect.js +102 -0
- package/src/nodes/emit-if/publish-to.js +13 -0
- package/src/nodes/emit-if/recompute.js +12 -0
- package/src/nodes/emit-if/reset.js +27 -0
- package/src/nodes/emit-if/update.js +93 -0
- package/src/nodes/es-correlation/index.js +19 -0
- package/src/nodes/es-correlation/init.js +73 -0
- package/src/nodes/es-correlation/introspect.js +176 -0
- package/src/nodes/es-correlation/publish-to.js +32 -0
- package/src/nodes/es-correlation/recompute.js +56 -0
- package/src/nodes/es-correlation/reset.js +27 -0
- package/src/nodes/es-correlation/update.js +110 -0
- package/src/nodes/es-mean/index.js +20 -0
- package/src/nodes/es-mean/init.js +58 -0
- package/src/nodes/es-mean/introspect.js +129 -0
- package/src/nodes/es-mean/publish-to.js +19 -0
- package/src/nodes/es-mean/recompute.js +16 -0
- package/src/nodes/es-mean/reset.js +24 -0
- package/src/nodes/es-mean/update.js +69 -0
- package/src/nodes/es-pairwise-correlation/index.js +19 -0
- package/src/nodes/es-pairwise-correlation/init.js +98 -0
- package/src/nodes/es-pairwise-correlation/introspect.js +187 -0
- package/src/nodes/es-pairwise-correlation/publish-to.js +31 -0
- package/src/nodes/es-pairwise-correlation/recompute.js +69 -0
- package/src/nodes/es-pairwise-correlation/reset.js +29 -0
- package/src/nodes/es-pairwise-correlation/update.js +89 -0
- package/src/nodes/es-stats/index.js +21 -0
- package/src/nodes/es-stats/init.js +105 -0
- package/src/nodes/es-stats/introspect.js +157 -0
- package/src/nodes/es-stats/publish-to.js +42 -0
- package/src/nodes/es-stats/recompute.js +74 -0
- package/src/nodes/es-stats/reset.js +49 -0
- package/src/nodes/es-stats/update-envelope.js +44 -0
- package/src/nodes/es-stats/update-welford.js +64 -0
- package/src/nodes/es-stats/update.js +59 -0
- package/src/nodes/index.js +83 -0
- package/src/nodes/invert-flag/index.js +20 -0
- package/src/nodes/invert-flag/init.js +36 -0
- package/src/nodes/invert-flag/introspect.js +115 -0
- package/src/nodes/invert-flag/publish-to.js +23 -0
- package/src/nodes/invert-flag/recompute.js +12 -0
- package/src/nodes/invert-flag/reset.js +12 -0
- package/src/nodes/invert-flag/update.js +32 -0
- package/src/nodes/kalman1d/index.js +20 -0
- package/src/nodes/kalman1d/init.js +103 -0
- package/src/nodes/kalman1d/introspect.js +250 -0
- package/src/nodes/kalman1d/publish-to.js +34 -0
- package/src/nodes/kalman1d/recompute.js +23 -0
- package/src/nodes/kalman1d/reset.js +29 -0
- package/src/nodes/kalman1d/update.js +144 -0
- package/src/nodes/kernel/index.js +18 -0
- package/src/nodes/kernel/init.js +66 -0
- package/src/nodes/kernel/introspect.js +163 -0
- package/src/nodes/kernel/presets.js +74 -0
- package/src/nodes/kernel/publish-to.js +25 -0
- package/src/nodes/kernel/recompute.js +14 -0
- package/src/nodes/kernel/reset.js +20 -0
- package/src/nodes/kernel/update.js +69 -0
- package/src/nodes/lag/index.js +21 -0
- package/src/nodes/lag/init.js +87 -0
- package/src/nodes/lag/introspect.js +202 -0
- package/src/nodes/lag/publish-to.js +64 -0
- package/src/nodes/lag/recompute.js +33 -0
- package/src/nodes/lag/reset.js +49 -0
- package/src/nodes/lag/update.js +133 -0
- package/src/nodes/median3/index.js +19 -0
- package/src/nodes/median3/init.js +40 -0
- package/src/nodes/median3/introspect.js +116 -0
- package/src/nodes/median3/publish-to.js +21 -0
- package/src/nodes/median3/recompute.js +12 -0
- package/src/nodes/median3/reset.js +15 -0
- package/src/nodes/median3/update.js +58 -0
- package/src/nodes/moments-digest/copy-from.js +12 -0
- package/src/nodes/moments-digest/flush.js +17 -0
- package/src/nodes/moments-digest/index.js +132 -0
- package/src/nodes/moments-digest/init.js +84 -0
- package/src/nodes/moments-digest/introspect.js +122 -0
- package/src/nodes/moments-digest/prelude.js +28 -0
- package/src/nodes/moments-digest/publish-to.js +31 -0
- package/src/nodes/moments-digest/recompute.js +9 -0
- package/src/nodes/moments-digest/reset.js +20 -0
- package/src/nodes/moments-digest/update.js +182 -0
- package/src/nodes/node-names.js +67 -0
- package/src/nodes/page-hinkley/index.js +20 -0
- package/src/nodes/page-hinkley/init.js +66 -0
- package/src/nodes/page-hinkley/introspect.js +193 -0
- package/src/nodes/page-hinkley/publish-to.js +28 -0
- package/src/nodes/page-hinkley/recompute.js +14 -0
- package/src/nodes/page-hinkley/reset.js +23 -0
- package/src/nodes/page-hinkley/update.js +105 -0
- package/src/nodes/pass-if/index.js +21 -0
- package/src/nodes/pass-if/init.js +37 -0
- package/src/nodes/pass-if/introspect.js +93 -0
- package/src/nodes/pass-if/publish-to.js +15 -0
- package/src/nodes/pass-if/recompute.js +15 -0
- package/src/nodes/pass-if/reset.js +18 -0
- package/src/nodes/pass-if/update.js +37 -0
- package/src/nodes/persist-if/helpers.js +111 -0
- package/src/nodes/persist-if/index.js +9 -0
- package/src/nodes/persist-if/init.js +98 -0
- package/src/nodes/persist-if/introspect.js +106 -0
- package/src/nodes/persist-if/publish-to.js +21 -0
- package/src/nodes/persist-if/recompute.js +20 -0
- package/src/nodes/persist-if/reset.js +35 -0
- package/src/nodes/persist-if/update.js +112 -0
- package/src/nodes/persistence-check/index.js +14 -0
- package/src/nodes/persistence-check/init.js +38 -0
- package/src/nodes/persistence-check/introspect.js +144 -0
- package/src/nodes/persistence-check/publish-to.js +11 -0
- package/src/nodes/persistence-check/recompute.js +7 -0
- package/src/nodes/persistence-check/reset.js +12 -0
- package/src/nodes/persistence-check/update.js +57 -0
- package/src/nodes/process-index/index.js +15 -0
- package/src/nodes/process-index/init.js +48 -0
- package/src/nodes/process-index/introspect.js +182 -0
- package/src/nodes/process-index/publish-to.js +29 -0
- package/src/nodes/process-index/recompute.js +8 -0
- package/src/nodes/process-index/reset.js +8 -0
- package/src/nodes/process-index/update.js +75 -0
- package/src/nodes/ratio/index.js +14 -0
- package/src/nodes/ratio/init.js +37 -0
- package/src/nodes/ratio/introspect.js +155 -0
- package/src/nodes/ratio/publish-to.js +15 -0
- package/src/nodes/ratio/recompute.js +7 -0
- package/src/nodes/ratio/reset.js +7 -0
- package/src/nodes/ratio/update.js +47 -0
- package/src/nodes/sanitize/index.js +82 -0
- package/src/nodes/sanitize/init.js +82 -0
- package/src/nodes/sanitize/introspect.js +194 -0
- package/src/nodes/sanitize/publish-to.js +15 -0
- package/src/nodes/sanitize/recompute.js +8 -0
- package/src/nodes/sanitize/reset.js +14 -0
- package/src/nodes/sanitize/update.js +102 -0
- package/src/nodes/spike-guard/index.js +14 -0
- package/src/nodes/spike-guard/init.js +47 -0
- package/src/nodes/spike-guard/introspect.js +130 -0
- package/src/nodes/spike-guard/publish-to.js +35 -0
- package/src/nodes/spike-guard/recompute.js +14 -0
- package/src/nodes/spike-guard/reset.js +19 -0
- package/src/nodes/spike-guard/update.js +89 -0
- package/src/nodes/sse-emitter/index.js +7 -0
- package/src/nodes/sse-emitter/init.js +105 -0
- package/src/nodes/sse-emitter/introspect.js +134 -0
- package/src/nodes/sse-emitter/publish-to.js +6 -0
- package/src/nodes/sse-emitter/recompute.js +16 -0
- package/src/nodes/sse-emitter/reset.js +19 -0
- package/src/nodes/sse-emitter/update.js +69 -0
- package/src/nodes/sse-emitter/utils/broadcast-heartbeat.js +36 -0
- package/src/nodes/sse-emitter/utils/build-message.js +31 -0
- package/src/nodes/sse-emitter/utils/connection-manager.js +45 -0
- package/src/nodes/sse-emitter/utils/endpoint-initializer.js +20 -0
- package/src/nodes/sse-emitter/utils/health-monitor.js +38 -0
- package/src/nodes/sse-emitter/utils/qos-handler.js +41 -0
- package/src/nodes/state-change-detector/helpers.js +35 -0
- package/src/nodes/state-change-detector/index.js +12 -0
- package/src/nodes/state-change-detector/init.js +56 -0
- package/src/nodes/state-change-detector/introspect.js +146 -0
- package/src/nodes/state-change-detector/publish-to.js +20 -0
- package/src/nodes/state-change-detector/recompute.js +6 -0
- package/src/nodes/state-change-detector/reset.js +20 -0
- package/src/nodes/state-change-detector/update.js +97 -0
- package/src/nodes/sw-stats/index.js +14 -0
- package/src/nodes/sw-stats/init.js +51 -0
- package/src/nodes/sw-stats/introspect.js +131 -0
- package/src/nodes/sw-stats/publish-to.js +45 -0
- package/src/nodes/sw-stats/recompute.js +33 -0
- package/src/nodes/sw-stats/reset.js +15 -0
- package/src/nodes/sw-stats/update.js +39 -0
- package/src/nodes/swing-watch/index.js +17 -0
- package/src/nodes/swing-watch/init.js +144 -0
- package/src/nodes/swing-watch/introspect.js +188 -0
- package/src/nodes/swing-watch/publish-to.js +53 -0
- package/src/nodes/swing-watch/recompute.js +22 -0
- package/src/nodes/swing-watch/reset.js +55 -0
- package/src/nodes/swing-watch/update.js +767 -0
- package/src/nodes/tally/index.js +15 -0
- package/src/nodes/tally/init.js +32 -0
- package/src/nodes/tally/introspect.js +116 -0
- package/src/nodes/tally/publish-to.js +28 -0
- package/src/nodes/tally/recompute.js +10 -0
- package/src/nodes/tally/reset.js +10 -0
- package/src/nodes/tally/update.js +44 -0
- package/src/nodes/threshold/index.js +14 -0
- package/src/nodes/threshold/init.js +121 -0
- package/src/nodes/threshold/introspect.js +192 -0
- package/src/nodes/threshold/publish-to.js +11 -0
- package/src/nodes/threshold/recompute.js +8 -0
- package/src/nodes/threshold/reset.js +14 -0
- package/src/nodes/threshold/update.js +54 -0
- package/src/nodes/transform/helpers.js +39 -0
- package/src/nodes/transform/index.js +15 -0
- package/src/nodes/transform/init.js +35 -0
- package/src/nodes/transform/introspect.js +118 -0
- package/src/nodes/transform/publish-to.js +23 -0
- package/src/nodes/transform/recompute.js +12 -0
- package/src/nodes/transform/reset.js +11 -0
- package/src/nodes/transform/update.js +32 -0
- package/src/nodes/trend/compute-confidence.js +112 -0
- package/src/nodes/trend/index.js +14 -0
- package/src/nodes/trend/init.js +81 -0
- package/src/nodes/trend/introspect.js +151 -0
- package/src/nodes/trend/publish-to.js +22 -0
- package/src/nodes/trend/recompute.js +15 -0
- package/src/nodes/trend/reset.js +26 -0
- package/src/nodes/trend/update.js +93 -0
- package/src/nodes/tw-stats/copy-from.js +16 -0
- package/src/nodes/tw-stats/flush.js +12 -0
- package/src/nodes/tw-stats/index.js +41 -0
- package/src/nodes/tw-stats/init.js +92 -0
- package/src/nodes/tw-stats/introspect.js +171 -0
- package/src/nodes/tw-stats/publish-to.js +108 -0
- package/src/nodes/tw-stats/recompute.js +9 -0
- package/src/nodes/tw-stats/reset.js +16 -0
- package/src/nodes/tw-stats/update.js +113 -0
- package/src/nodes/unbalance/index.js +15 -0
- package/src/nodes/unbalance/init.js +59 -0
- package/src/nodes/unbalance/introspect.js +167 -0
- package/src/nodes/unbalance/publish-to.js +52 -0
- package/src/nodes/unbalance/recompute.js +11 -0
- package/src/nodes/unbalance/reset.js +10 -0
- package/src/nodes/unbalance/update.js +83 -0
- package/src/nodes/vector-distance/index.js +14 -0
- package/src/nodes/vector-distance/init.js +54 -0
- package/src/nodes/vector-distance/introspect.js +149 -0
- package/src/nodes/vector-distance/publish-to.js +35 -0
- package/src/nodes/vector-distance/recompute.js +11 -0
- package/src/nodes/vector-distance/reset.js +20 -0
- package/src/nodes/vector-distance/update.js +139 -0
- package/src/nodes/winnow/index.js +12 -0
- package/src/nodes/winnow/init.js +82 -0
- package/src/nodes/winnow/introspect.js +253 -0
- package/src/nodes/winnow/publish-to.js +51 -0
- package/src/nodes/winnow/recompute.js +14 -0
- package/src/nodes/winnow/reset.js +34 -0
- package/src/nodes/winnow/update.js +203 -0
- package/src/tools/training/features.js +227 -0
- package/src/tools/training/index.js +21 -0
- package/src/tools/training/logistic.js +238 -0
- package/src/tools/training/metrics.js +126 -0
- package/src/tools/training/scale.js +239 -0
- package/src/tools/training/validate.js +103 -0
- package/src/windowing/count-sliding/create.js +16 -0
- package/src/windowing/count-sliding/get-reverse-chronological-index.js +19 -0
- package/src/windowing/count-sliding/index.js +6 -0
- package/src/windowing/count-sliding/is-not-full.js +5 -0
- package/src/windowing/count-sliding/push.js +15 -0
- package/src/windowing/count-sliding/reset.js +11 -0
- package/src/windowing/count-sliding/size.js +5 -0
- package/src/windowing/time-sliding/create.js +25 -0
- package/src/windowing/time-sliding/push.js +42 -0
- package/CODE_OF_CONDUCT.md +0 -46
- package/CONTRIBUTING.md +0 -132
- package/docs/.nojekyll +0 -0
- package/docs/CONTRIBUTING.md +0 -134
- package/docs/README.md +0 -132
- package/docs/_coverpage.md +0 -26
- package/docs/_media/bg.png +0 -0
- package/docs/_media/wink-logo.png +0 -0
- package/docs/_navbar.md +0 -14
- package/docs/_sidebar.md +0 -2
- package/docs/index.html +0 -44
- package/eslint.config.js +0 -283
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// core/source-manager/mqtt/client.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview MQTT client wrapper for source adapter.
|
|
5
|
+
*
|
|
6
|
+
* Handles:
|
|
7
|
+
* - MQTT v5 connection with session persistence
|
|
8
|
+
* - Topic subscription with QoS 1
|
|
9
|
+
* - Deduplication via winkDedupId from user properties (ADR-022)
|
|
10
|
+
* - Automatic reconnection on disconnect (owned by mqtt.js, as
|
|
11
|
+
* ADR-018 requires — reconnection is the transport library's job)
|
|
12
|
+
* - Structured health/metrics reporting (via status.js)
|
|
13
|
+
* - Graceful shutdown with a time budget
|
|
14
|
+
*
|
|
15
|
+
* Message flow:
|
|
16
|
+
* MQTT broker → subscribe → extract userProperties → check dedup cache
|
|
17
|
+
* → if duplicate: skip, count (dedupHits — not a status event)
|
|
18
|
+
* → if new: decode payload → transform (optional) → onMessage(msg)
|
|
19
|
+
*
|
|
20
|
+
* Status shape — the ADR-018 core, plus this adapter's additions:
|
|
21
|
+
* `{status: 'green' | 'yellow' | 'red', connected, phase,
|
|
22
|
+
* error?: {code, message}}` is the shape every source shares;
|
|
23
|
+
* `msSinceLastMsg` and `note?` are MQTT-source extras.
|
|
24
|
+
* Phases: `starting` → `running`, transient `offline` / `reconnecting`
|
|
25
|
+
* while mqtt.js retries, `stopped` on stop. This source never emits
|
|
26
|
+
* `phase: 'errored'`: mqtt.js retries forever (the ADR-018 recovering
|
|
27
|
+
* posture), so there is no give-up path. A permanent outage is visible
|
|
28
|
+
* as a red CONNECTION_LOST whose `connected: false` never clears — a
|
|
29
|
+
* limit ADR-018 accepts for recovering sources: a wrong address and a
|
|
30
|
+
* long outage look identical from here.
|
|
31
|
+
*
|
|
32
|
+
* `err.code` vocabulary (per-adapter, documented here per ADR-018):
|
|
33
|
+
* - `INVALID_CONFIG` — setup-time; missing or malformed config
|
|
34
|
+
* field. Thrown synchronously from the factory (ADR-018 fail-fast
|
|
35
|
+
* setup), never emitted.
|
|
36
|
+
* - `DECODE_ERROR` — runtime, yellow. Two faces: a per-record
|
|
37
|
+
* report for every payload that cannot be decoded (skip, classify,
|
|
38
|
+
* continue — ADR-018), and a health flip when the decode-error ratio over
|
|
39
|
+
* the last 1,000 messages exceeds 1 %.
|
|
40
|
+
* - `CALLBACK_FAILED` — runtime, yellow. The user's `transform`
|
|
41
|
+
* threw; that one message is skipped (counted in `skipped`) and the
|
|
42
|
+
* stream continues. Fix the transform function — the report names
|
|
43
|
+
* the topic and the thrown message. Uniform with the CSV source
|
|
44
|
+
* (transform contract, 2026-07-11).
|
|
45
|
+
* - `SUBSCRIBE_FAILED` — runtime, red. The broker refused the
|
|
46
|
+
* subscription (typically ACL). Red immediately: nothing retries a
|
|
47
|
+
* subscribe until the next reconnect, so a deaf-but-connected source
|
|
48
|
+
* would otherwise look healthy forever.
|
|
49
|
+
* - `CONNECT_FAILED` — runtime, yellow. A transport-level error
|
|
50
|
+
* while the library retries (or a rare error event while running).
|
|
51
|
+
* Attached to the transient status once per retry streak — a storm
|
|
52
|
+
* of identical failures cannot flood the channel.
|
|
53
|
+
* - `CONNECTION_LOST` — runtime, red. Disconnected for more than
|
|
54
|
+
* 30 s (strictly greater) while the library keeps retrying.
|
|
55
|
+
* - `QUIET_PERIOD_EXCEEDED` — runtime, yellow, opt-in. No packet for
|
|
56
|
+
* longer than the configured `expectedQuietPeriodMs`.
|
|
57
|
+
*
|
|
58
|
+
* Metrics (optional `onMetrics`, ~1 Hz + on transitions): monotonic
|
|
59
|
+
* counters `{delivered, skipped, decodeErrors, reconnects, dedupHits,
|
|
60
|
+
* dedupMisses, dedupBypassed, dedupCacheSize}`. `dedupBypassed` counts
|
|
61
|
+
* messages that arrived without a `winkDedupId` — the signal that a
|
|
62
|
+
* publisher is not stamping ids (dedup is opt-in by construction).
|
|
63
|
+
*
|
|
64
|
+
* ASSUMPTIONS
|
|
65
|
+
* -----------
|
|
66
|
+
* 1. The broker speaks MQTT v5 and has session persistence enabled.
|
|
67
|
+
* The queued-while-disconnected guarantee is the BROKER's — a
|
|
68
|
+
* broker without persistence (default Mosquitto image) accepts
|
|
69
|
+
* the connection but keeps nothing (see constants.js).
|
|
70
|
+
* 2. mqtt.js owns reconnection entirely (ADR-018: reconnection is
|
|
71
|
+
* the transport library's job). This file
|
|
72
|
+
* never retries anything itself.
|
|
73
|
+
* 3. `onMessage` keeps up with the arrival rate. Delivery is
|
|
74
|
+
* fire-and-forget; a consumer slower than the broker backs up
|
|
75
|
+
* in Node's socket buffer and mqtt.js's queue, not here.
|
|
76
|
+
* 4. Publishers that want duplicate protection stamp `winkDedupId`
|
|
77
|
+
* (the emitter does). Unstamped messages pass through unfiltered
|
|
78
|
+
* and are counted in `dedupBypassed`.
|
|
79
|
+
*
|
|
80
|
+
* LIMITATIONS
|
|
81
|
+
* -----------
|
|
82
|
+
* 1. QoS 1 is at-least-once: duplicates WILL arrive after connection
|
|
83
|
+
* breaks. The dedup cache drops repeats within its time/count
|
|
84
|
+
* bounds (ADR-022); beyond them — or after a subscriber restart —
|
|
85
|
+
* QuestDB's at-rest dedup is the backstop.
|
|
86
|
+
* 2. No broker-level flow control: the source never delays PUBACK,
|
|
87
|
+
* so nothing tells the broker to slow down (an opt-in
|
|
88
|
+
* `flowControl` is future work, post-release).
|
|
89
|
+
* 3. No give-up path: mqtt.js retries forever, so a wrong broker
|
|
90
|
+
* address and a long outage look identical from here (a limit
|
|
91
|
+
* ADR-018 accepts for recovering sources) — both
|
|
92
|
+
* show as red CONNECTION_LOST whose `connected: false` never
|
|
93
|
+
* clears. The operator tells them apart, not the code.
|
|
94
|
+
* 4. Safe to kill and re-create: no disk state, no side effects.
|
|
95
|
+
* The cost of a crash is the dedup cache (see dedup.js).
|
|
96
|
+
*
|
|
97
|
+
* Performance (benchmark/mqtt-source/BASELINE.md, 2026-04-24, M4 Max;
|
|
98
|
+
* pre-ADR-022 dedup, whose replacement has the same O(1) per-message
|
|
99
|
+
* profile): composer's own decode/dedup/dispatch path sustains
|
|
100
|
+
* 897 k msg/s at 100 B payloads (317 k at 1 KB); end-to-end through
|
|
101
|
+
* Mosquitto at QoS 1 the single-process ceiling was ~12.9 k msg/s —
|
|
102
|
+
* owned by mqtt.js and the broker round-trip, not this code. At
|
|
103
|
+
* 10 k msg/s steady state every cell ran clean; a 30 M-message run
|
|
104
|
+
* showed no subscriber-side leak.
|
|
105
|
+
*
|
|
106
|
+
* @see src/core/source-manager/mqtt/status.js - The reporting rules
|
|
107
|
+
* @see src/core/emitter-manager/mqtt/emitter.js - Emitter counterpart
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
import mqtt from 'mqtt';
|
|
111
|
+
|
|
112
|
+
import {
|
|
113
|
+
MQTT_SOURCE_CONFIG,
|
|
114
|
+
QOS,
|
|
115
|
+
WINK_NAMESPACE,
|
|
116
|
+
DEFAULT_DEDUP_WINDOW_MS,
|
|
117
|
+
DEFAULT_DEDUP_MAX_ENTRIES,
|
|
118
|
+
METRICS_INTERVAL_MS
|
|
119
|
+
} from './constants.js';
|
|
120
|
+
import { createDedupCache } from './dedup.js';
|
|
121
|
+
import { createStatusReporter } from './status.js';
|
|
122
|
+
|
|
123
|
+
// ============================================================================
|
|
124
|
+
// CLIENT FACTORY
|
|
125
|
+
// ============================================================================
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create MQTT source client with deduplication and structured
|
|
129
|
+
* health/metrics reporting.
|
|
130
|
+
*
|
|
131
|
+
* @param {Object} config - Client configuration
|
|
132
|
+
* @param {string} config.brokerUrl - MQTT broker URL (e.g., 'mqtt://localhost:1883')
|
|
133
|
+
* @param {string|string[]} config.topics - Topic(s) to subscribe to (supports wildcards)
|
|
134
|
+
* @param {function} config.onMessage - Message handler: (message) => void
|
|
135
|
+
* @param {Object} [config.codec] - Codec for payload decoding (default: JSON.parse)
|
|
136
|
+
* @param {function} [config.transform] - Optional message transform: (msg) => transformedMsg
|
|
137
|
+
* @param {number} [config.dedupWindowMs=120000] - Dedup time bound (ADR-022)
|
|
138
|
+
* @param {number} [config.dedupMaxEntries=65536] - Dedup count cap (ADR-022)
|
|
139
|
+
* @param {string} [config.clientId] - MQTT client ID (auto-generated if omitted)
|
|
140
|
+
* @param {function} [config.onStatus] - Status callback; receives the
|
|
141
|
+
* structured ADR-018 status payload on every transition and per decode
|
|
142
|
+
* failure (see the fileoverview for the full vocabulary)
|
|
143
|
+
* @param {function} [config.onMetrics] - Counter-snapshot callback,
|
|
144
|
+
* called at ~1 Hz and on every health transition
|
|
145
|
+
* @param {number} [config.expectedQuietPeriodMs] - Opt-in quiet rule:
|
|
146
|
+
* health goes yellow when no packet arrives for longer than this
|
|
147
|
+
* @param {boolean} [config.cleanStart] - Override MQTT cleanStart (default: false for persistent sessions)
|
|
148
|
+
* @param {function} [config.mqttConnectFn] - MQTT connect function (for testing)
|
|
149
|
+
* @returns {function} Stop function that returns a Promise
|
|
150
|
+
*/
|
|
151
|
+
const createMQTTSourceClient = function ( config ) {
|
|
152
|
+
const {
|
|
153
|
+
brokerUrl,
|
|
154
|
+
topics,
|
|
155
|
+
onMessage,
|
|
156
|
+
codec,
|
|
157
|
+
transform,
|
|
158
|
+
dedupWindowMs = DEFAULT_DEDUP_WINDOW_MS,
|
|
159
|
+
dedupMaxEntries = DEFAULT_DEDUP_MAX_ENTRIES,
|
|
160
|
+
clientId,
|
|
161
|
+
onStatus,
|
|
162
|
+
onMetrics,
|
|
163
|
+
expectedQuietPeriodMs,
|
|
164
|
+
cleanStart,
|
|
165
|
+
mqttConnectFn = mqtt.connect
|
|
166
|
+
} = config;
|
|
167
|
+
|
|
168
|
+
// Validate required config. Per ADR-018, setup-time throws carry
|
|
169
|
+
// classified err.code (INVALID_CONFIG for missing/malformed config fields).
|
|
170
|
+
if ( !brokerUrl ) {
|
|
171
|
+
const err = new Error( 'WinkComposer/mqtt-source: brokerUrl is required' );
|
|
172
|
+
err.code = 'INVALID_CONFIG';
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
if ( !topics || ( Array.isArray( topics ) && topics.length === 0 ) ) {
|
|
176
|
+
const err = new Error( 'WinkComposer/mqtt-source: topics is required' );
|
|
177
|
+
err.code = 'INVALID_CONFIG';
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
if ( typeof onMessage !== 'function' ) {
|
|
181
|
+
const err = new Error( 'WinkComposer/mqtt-source: onMessage must be a function' );
|
|
182
|
+
err.code = 'INVALID_CONFIG';
|
|
183
|
+
throw err;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Normalize topics to array
|
|
187
|
+
const topicList = Array.isArray( topics ) ? topics : [ topics ];
|
|
188
|
+
|
|
189
|
+
// Create dedup cache — time-bounded, count-capped (ADR-022)
|
|
190
|
+
const dedup = createDedupCache( { windowMs: dedupWindowMs, maxEntries: dedupMaxEntries } );
|
|
191
|
+
|
|
192
|
+
// The status reporter owns every emission rule (status.js). It
|
|
193
|
+
// validates onStatus / onMetrics / expectedQuietPeriodMs itself,
|
|
194
|
+
// so ALL setup throws happen before any side effect below.
|
|
195
|
+
const reporter = createStatusReporter( {
|
|
196
|
+
onStatus,
|
|
197
|
+
onMetrics,
|
|
198
|
+
expectedQuietPeriodMs,
|
|
199
|
+
dedupSizeFn: dedup.size
|
|
200
|
+
} );
|
|
201
|
+
|
|
202
|
+
reporter.starting();
|
|
203
|
+
|
|
204
|
+
// Generate client ID if not provided
|
|
205
|
+
const generatedClientId = clientId || `wink-source-${Date.now()}`;
|
|
206
|
+
|
|
207
|
+
// A persistent session is filed at the broker under the client's
|
|
208
|
+
// name. The auto-generated name changes on every start, so the
|
|
209
|
+
// backlog the broker saved under the previous run's name is never
|
|
210
|
+
// delivered — it waits, unclaimed, until the session expires.
|
|
211
|
+
// Warn once at startup; the fix is one config line. Setup path
|
|
212
|
+
// only — nothing here touches the message path.
|
|
213
|
+
if ( !clientId && cleanStart !== true ) {
|
|
214
|
+
console.warn(
|
|
215
|
+
'WinkComposer/mqtt-source: no clientId configured — this session ' +
|
|
216
|
+
`is persistent under the auto-generated name '${generatedClientId}'. ` +
|
|
217
|
+
'After a restart, composer connects under a NEW name, so messages ' +
|
|
218
|
+
'the broker saved during the downtime are never delivered. Set a ' +
|
|
219
|
+
'fixed clientId (unique on your broker) in the source config.'
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Build MQTT options, allowing cleanStart override
|
|
224
|
+
const mqttOptions = {
|
|
225
|
+
...MQTT_SOURCE_CONFIG,
|
|
226
|
+
clientId: generatedClientId
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Map the user-facing `cleanStart` key (MQTT 5 term) onto the
|
|
230
|
+
// option name mqtt.js reads (`clean`). See constants.js for the
|
|
231
|
+
// 2026-07-09 defect this spelling closed.
|
|
232
|
+
if ( cleanStart !== undefined ) {
|
|
233
|
+
mqttOptions.clean = cleanStart;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Create MQTT client
|
|
237
|
+
const client = mqttConnectFn( brokerUrl, mqttOptions );
|
|
238
|
+
|
|
239
|
+
// One heartbeat per second: re-evaluates the time-based health
|
|
240
|
+
// rules (a silent source produces no events to evaluate on) and
|
|
241
|
+
// drives the onMetrics cadence. unref'd so a stopping process is
|
|
242
|
+
// not held alive by observability.
|
|
243
|
+
const cadence = setInterval( reporter.tick, METRICS_INTERVAL_MS );
|
|
244
|
+
cadence.unref();
|
|
245
|
+
|
|
246
|
+
// Track subscription state
|
|
247
|
+
let isSubscribed = false;
|
|
248
|
+
|
|
249
|
+
// ========================================================================
|
|
250
|
+
// EVENT HANDLERS
|
|
251
|
+
// ========================================================================
|
|
252
|
+
|
|
253
|
+
client.on( 'connect', function () {
|
|
254
|
+
reporter.connected();
|
|
255
|
+
|
|
256
|
+
// Subscribe to topics
|
|
257
|
+
client.subscribe( topicList, { qos: QOS }, function ( err ) {
|
|
258
|
+
if ( err ) {
|
|
259
|
+
reporter.subscribeFailed( err );
|
|
260
|
+
} else {
|
|
261
|
+
isSubscribed = true;
|
|
262
|
+
reporter.subscribed();
|
|
263
|
+
}
|
|
264
|
+
} );
|
|
265
|
+
} );
|
|
266
|
+
|
|
267
|
+
client.on( 'message', function ( topic, payload, packet ) {
|
|
268
|
+
// Extract dedupId from MQTT v5 user properties. Guard instead
|
|
269
|
+
// of `|| {}` — the fallback object would be a fresh per-message
|
|
270
|
+
// allocation for every publisher that stamps no properties.
|
|
271
|
+
const userProps = packet.properties && packet.properties.userProperties;
|
|
272
|
+
const dedupId = userProps ? userProps[ WINK_NAMESPACE.dedupId ] : undefined;
|
|
273
|
+
|
|
274
|
+
// Dedup is opt-in by construction (ADR-022): no id → bypass.
|
|
275
|
+
if ( dedupId === null || dedupId === undefined ) {
|
|
276
|
+
reporter.bypassed();
|
|
277
|
+
} else if ( dedup.isDuplicate( dedupId ) ) {
|
|
278
|
+
reporter.dupSkipped();
|
|
279
|
+
return;
|
|
280
|
+
} else {
|
|
281
|
+
reporter.idAccepted();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Decode payload. A payload that cannot be decoded is skipped,
|
|
285
|
+
// classified, and reported per record (ADR-018) — the stream
|
|
286
|
+
// continues.
|
|
287
|
+
let message;
|
|
288
|
+
try {
|
|
289
|
+
if ( codec && typeof codec.unpack === 'function' ) {
|
|
290
|
+
message = codec.unpack( payload );
|
|
291
|
+
} else {
|
|
292
|
+
message = JSON.parse( payload.toString() );
|
|
293
|
+
}
|
|
294
|
+
reporter.decodeOk();
|
|
295
|
+
} catch ( err ) {
|
|
296
|
+
reporter.decodeFailed( `topic '${topic}': ${err.message} — message skipped` );
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Attach metadata for downstream use
|
|
301
|
+
message._topic = topic; // eslint-disable-line no-underscore-dangle
|
|
302
|
+
message._dedupId = dedupId; // eslint-disable-line no-underscore-dangle
|
|
303
|
+
|
|
304
|
+
// Apply optional transform. Guarded: a throw skips this one
|
|
305
|
+
// message with a per-record CALLBACK_FAILED report and the
|
|
306
|
+
// stream continues — user code must never propagate into
|
|
307
|
+
// mqtt.js's event processing (transform contract, 2026-07-11).
|
|
308
|
+
let finalMessage;
|
|
309
|
+
try {
|
|
310
|
+
finalMessage = transform ? transform( message ) : message;
|
|
311
|
+
} catch ( err ) {
|
|
312
|
+
reporter.transformFailed( `topic '${topic}': transform threw: ${err.message} — message skipped` );
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Deliver to handler
|
|
317
|
+
if ( finalMessage !== null && finalMessage !== undefined ) {
|
|
318
|
+
onMessage( finalMessage );
|
|
319
|
+
reporter.delivered();
|
|
320
|
+
} else {
|
|
321
|
+
reporter.transformDropped();
|
|
322
|
+
}
|
|
323
|
+
} );
|
|
324
|
+
|
|
325
|
+
client.on( 'offline', function () {
|
|
326
|
+
isSubscribed = false;
|
|
327
|
+
reporter.offline();
|
|
328
|
+
} );
|
|
329
|
+
|
|
330
|
+
client.on( 'error', function ( err ) {
|
|
331
|
+
reporter.connectError( err );
|
|
332
|
+
} );
|
|
333
|
+
|
|
334
|
+
client.on( 'reconnect', function () {
|
|
335
|
+
reporter.reconnecting();
|
|
336
|
+
} );
|
|
337
|
+
|
|
338
|
+
// ========================================================================
|
|
339
|
+
// STOP FUNCTION
|
|
340
|
+
// ========================================================================
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Stop the MQTT client, with a time budget.
|
|
344
|
+
*
|
|
345
|
+
* Per ADR-018, source stop functions take a `{ timeout }`. We
|
|
346
|
+
* first ask the broker for a clean disconnect (`client.end(false)`),
|
|
347
|
+
* which waits for any messages still being sent. If that does not
|
|
348
|
+
* finish within `timeout` ms, we ask for a forced disconnect
|
|
349
|
+
* (`client.end(true)`), which closes the socket right away — so the
|
|
350
|
+
* rest of the shutdown can proceed even if the broker is slow or
|
|
351
|
+
* unreachable. The forced path is reported with the sources' shared
|
|
352
|
+
* `note` convention.
|
|
353
|
+
*
|
|
354
|
+
* The timer is `unref()`ed so it does not keep Node alive while a
|
|
355
|
+
* clean disconnect is in progress.
|
|
356
|
+
*
|
|
357
|
+
* Default time budget (5000 ms) matches what sinks use.
|
|
358
|
+
*
|
|
359
|
+
* @param {Object} [options] - Stop options
|
|
360
|
+
* @param {number} [options.timeout=5000] - Max ms to wait for clean disconnect
|
|
361
|
+
* @returns {Promise<void>} Resolves once the client is closed (clean or forced)
|
|
362
|
+
*/
|
|
363
|
+
const stop = function ( { timeout = 5000 } = {} ) {
|
|
364
|
+
clearInterval( cadence );
|
|
365
|
+
return new Promise( function ( resolve ) {
|
|
366
|
+
let settled = false;
|
|
367
|
+
let forceTimer = null;
|
|
368
|
+
const onClosed = function () {
|
|
369
|
+
if ( settled ) return;
|
|
370
|
+
settled = true;
|
|
371
|
+
if ( forceTimer ) clearTimeout( forceTimer );
|
|
372
|
+
reporter.stopped();
|
|
373
|
+
resolve();
|
|
374
|
+
};
|
|
375
|
+
forceTimer = setTimeout( function () {
|
|
376
|
+
|
|
377
|
+
/* c8 ignore next -- defensive: settle always clears this timer (it is assigned before client.end can call back), so reaching here settled requires the timer callback to already sit in the event queue when settle runs — a race with no producer in the current design. Same guard shape as the testHarness stop. */
|
|
378
|
+
if ( settled ) return;
|
|
379
|
+
reporter.stopForced( timeout );
|
|
380
|
+
client.end( true, {}, onClosed );
|
|
381
|
+
}, timeout );
|
|
382
|
+
// Don't keep Node alive on the timer while the broker is
|
|
383
|
+
// closing cleanly. Either path resolves the Promise.
|
|
384
|
+
forceTimer.unref();
|
|
385
|
+
client.end( false, {}, onClosed );
|
|
386
|
+
} );
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Expose for testing
|
|
390
|
+
/* eslint-disable no-underscore-dangle */
|
|
391
|
+
stop._client = client;
|
|
392
|
+
stop._dedup = dedup;
|
|
393
|
+
stop._isSubscribed = function () {
|
|
394
|
+
return isSubscribed;
|
|
395
|
+
};
|
|
396
|
+
stop._metrics = reporter.snapshot;
|
|
397
|
+
/* eslint-enable no-underscore-dangle */
|
|
398
|
+
|
|
399
|
+
return stop;
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// ============================================================================
|
|
403
|
+
// EXPORTS
|
|
404
|
+
// ============================================================================
|
|
405
|
+
|
|
406
|
+
export { createMQTTSourceClient };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// core/source-manager/mqtt/constants.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Constants for MQTT source adapter.
|
|
5
|
+
*
|
|
6
|
+
* Protocol invariants (QoS, WINK_NAMESPACE, protocol version) are imported
|
|
7
|
+
* from the shared mqtt-protocol module. Deployment-configurable values
|
|
8
|
+
* (keepalive, reconnect, etc.) come from ENV_VARS.
|
|
9
|
+
*
|
|
10
|
+
* ASSUMPTIONS
|
|
11
|
+
* -----------
|
|
12
|
+
* 1. Environment variables were validated at import time by
|
|
13
|
+
* env-vars.js — every value here is already a sane number.
|
|
14
|
+
* 2. These are deployment knobs, not per-flow settings: two sources
|
|
15
|
+
* in one process share them (per-flow overrides go through the
|
|
16
|
+
* source config, e.g. dedupWindowMs).
|
|
17
|
+
*
|
|
18
|
+
* LIMITATIONS
|
|
19
|
+
* -----------
|
|
20
|
+
* 1. The persistent-session guarantee (`clean: false`) is only as
|
|
21
|
+
* real as the broker's persistence setting — the default
|
|
22
|
+
* Mosquitto image accepts the session and keeps nothing. See the
|
|
23
|
+
* MQTT_SOURCE_CONFIG note below.
|
|
24
|
+
*
|
|
25
|
+
* @see src/core/mqtt-protocol.js - Shared protocol invariants
|
|
26
|
+
* @see src/core/env-vars.js - Deployment configuration
|
|
27
|
+
* @see src/core/emitter-manager/mqtt/constants.js - Emitter counterpart
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { ENV_VARS } from '../../env-vars.js';
|
|
31
|
+
import {
|
|
32
|
+
WINK_NAMESPACE,
|
|
33
|
+
MQTT_QOS as QOS,
|
|
34
|
+
MQTT_PROTOCOL_V
|
|
35
|
+
} from '../../mqtt-protocol.js';
|
|
36
|
+
|
|
37
|
+
// Re-export protocol invariants for local consumers
|
|
38
|
+
export { WINK_NAMESPACE, QOS };
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// DEDUPLICATION DEFAULTS (ADR-022)
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default dedup time bound: an entry expires this many milliseconds
|
|
46
|
+
* after arrival. Two minutes covers typical broker retry windows —
|
|
47
|
+
* a duplicate arrives roughly one reconnect gap after its original.
|
|
48
|
+
* Override via MQTT_SOURCE_DEDUP_WINDOW_MS.
|
|
49
|
+
*
|
|
50
|
+
* @type {number}
|
|
51
|
+
*/
|
|
52
|
+
export const DEFAULT_DEDUP_WINDOW_MS = ENV_VARS.mqttSourceDedupWindowMs;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Default dedup count cap: the memory guarantee (~8 MB worst case at
|
|
56
|
+
* 65,536 retained UUIDs). When the cap binds before the time window,
|
|
57
|
+
* the effective window is maxEntries ÷ message rate — the arithmetic
|
|
58
|
+
* is documented in ADR-022. Override via MQTT_SOURCE_DEDUP_MAX_ENTRIES.
|
|
59
|
+
*
|
|
60
|
+
* @type {number}
|
|
61
|
+
*/
|
|
62
|
+
export const DEFAULT_DEDUP_MAX_ENTRIES = ENV_VARS.mqttSourceDedupMaxEntries;
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// HEALTH & METRICS
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* How long the source may sit disconnected before its health status
|
|
70
|
+
* escalates from yellow to red. The transport library keeps retrying
|
|
71
|
+
* either way (the ADR-018 recovering posture) — red is the operator signal
|
|
72
|
+
* that the outage has outlived a routine blip. Strictly greater-than:
|
|
73
|
+
* exactly 30,000 ms is still yellow.
|
|
74
|
+
*
|
|
75
|
+
* @type {number}
|
|
76
|
+
*/
|
|
77
|
+
export const DISCONNECT_RED_MS = 30000;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* How many recent decode outcomes the health check remembers. The
|
|
81
|
+
* yellow rule reads "more than 1 % decode errors over the last 1,000
|
|
82
|
+
* messages"; the ring is pre-allocated once (1 KB) so the hot path
|
|
83
|
+
* allocates nothing.
|
|
84
|
+
*
|
|
85
|
+
* @type {number}
|
|
86
|
+
*/
|
|
87
|
+
export const DECODE_RING_SIZE = 1000;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Cadence of the health/metrics timer: once per second the client
|
|
91
|
+
* re-evaluates the time-based health rules and, when the caller
|
|
92
|
+
* supplied `onMetrics`, emits a counter snapshot. One unref'd
|
|
93
|
+
* interval per source instance — the only timer this adapter owns.
|
|
94
|
+
*
|
|
95
|
+
* @type {number}
|
|
96
|
+
*/
|
|
97
|
+
export const METRICS_INTERVAL_MS = 1000;
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// MQTT CLIENT CONFIGURATION
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Default MQTT client configuration for source adapter.
|
|
105
|
+
* Uses MQTT v5 with session persistence for reliable message delivery.
|
|
106
|
+
*
|
|
107
|
+
* SESSION PERSISTENCE (cleanStart: false)
|
|
108
|
+
* ----------------------------------------
|
|
109
|
+
* When cleanStart is false, the broker maintains the client's session state
|
|
110
|
+
* (subscriptions and queued messages) across disconnections. This means:
|
|
111
|
+
*
|
|
112
|
+
* - Messages published while disconnected are queued by the broker
|
|
113
|
+
* - On reconnect, queued messages are delivered automatically
|
|
114
|
+
* - No telemetry data is lost during brief network interruptions
|
|
115
|
+
*
|
|
116
|
+
* This is the right default for production IoT/telemetry systems where
|
|
117
|
+
* data loss is unacceptable.
|
|
118
|
+
*
|
|
119
|
+
* BROKER REQUIREMENT
|
|
120
|
+
* ------------------
|
|
121
|
+
* Session persistence requires the MQTT broker to be configured with
|
|
122
|
+
* persistence enabled. The default Eclipse Mosquitto docker image does
|
|
123
|
+
* NOT have persistence enabled - it will accept the connection but won't
|
|
124
|
+
* actually persist sessions.
|
|
125
|
+
*
|
|
126
|
+
* For testing with default Mosquitto, pass `cleanStart: true` to the
|
|
127
|
+
* source config to use fresh sessions (no persistence requirement).
|
|
128
|
+
*
|
|
129
|
+
* @type {Object}
|
|
130
|
+
*/
|
|
131
|
+
export const MQTT_SOURCE_CONFIG = {
|
|
132
|
+
protocolVersion: MQTT_PROTOCOL_V,
|
|
133
|
+
keepalive: ENV_VARS.mqttKeepalive,
|
|
134
|
+
reconnectPeriod: ENV_VARS.mqttReconnectMs,
|
|
135
|
+
connectTimeout: ENV_VARS.mqttConnectTimeoutMs,
|
|
136
|
+
// mqtt.js's option name is `clean` — it maps to the MQTT 5 Clean
|
|
137
|
+
// Start flag. The source's user-facing config key stays
|
|
138
|
+
// `cleanStart` (the MQTT 5 term); client.js maps it onto this.
|
|
139
|
+
// Before 2026-07-09 this was spelled `cleanStart`, which mqtt.js
|
|
140
|
+
// silently ignored — every connection ran clean=true and no
|
|
141
|
+
// session ever survived a reconnect. Caught by the ADR-022 dedup
|
|
142
|
+
// soak's forced-reconnect test.
|
|
143
|
+
clean: false,
|
|
144
|
+
properties: {
|
|
145
|
+
sessionExpiryInterval: ENV_VARS.mqttSessionExpiryS
|
|
146
|
+
}
|
|
147
|
+
};
|