@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,102 @@
|
|
|
1
|
+
// Helper: Check value against range
|
|
2
|
+
const checkRange = function ( state, value, rangeSpec ) {
|
|
3
|
+
if ( !state.hasRange || !rangeSpec ) {
|
|
4
|
+
return true; // No range configured, pass through
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
return value >= rangeSpec.min && value <= rangeSpec.max;
|
|
8
|
+
}; // checkRange()
|
|
9
|
+
|
|
10
|
+
// Helper: Check value against list
|
|
11
|
+
const checkValueList = function ( state, value ) {
|
|
12
|
+
if ( !state.valueSet ) {
|
|
13
|
+
return true; // No list configured, pass through
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const isInList = state.valueSet.has( value );
|
|
17
|
+
// If containsValidValues is true, value must be in list (allow list)
|
|
18
|
+
// If containsValidValues is false, value must NOT be in list (deny list)
|
|
19
|
+
return state.containsValidValues ? isInList : !isInList;
|
|
20
|
+
}; // checkValueList()
|
|
21
|
+
|
|
22
|
+
// Helper: Check custom predicate
|
|
23
|
+
const checkPredicate = function ( state, value, msg ) {
|
|
24
|
+
if ( !state.predicate ) {
|
|
25
|
+
return true; // No predicate configured, pass through
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const result = state.predicate( value, msg ) === true;
|
|
30
|
+
// Recovery: clear log-suppression flag on success
|
|
31
|
+
if ( state.predicateErrorLogged ) state.predicateErrorLogged = false;
|
|
32
|
+
return result;
|
|
33
|
+
} catch ( error ) {
|
|
34
|
+
// Log first error per episode; suppress subsequent until recovery
|
|
35
|
+
if ( !state.predicateErrorLogged ) {
|
|
36
|
+
state.predicateErrorLogged = true;
|
|
37
|
+
console.error( `WinkComposer/sanitize: predicate threw exception: ${error.message}` );
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}; // checkPredicate()
|
|
42
|
+
|
|
43
|
+
// Main update function
|
|
44
|
+
const update = function ( state, msg ) {
|
|
45
|
+
if ( state.disable || state.pause ) return state;
|
|
46
|
+
|
|
47
|
+
const key = state.x;
|
|
48
|
+
const value = msg[ key ];
|
|
49
|
+
|
|
50
|
+
// Use self compare NaN NaN check first and not the Number.isFinite(),
|
|
51
|
+
// as incoming value may not be number type
|
|
52
|
+
if ( value !== value ) { // eslint-disable-line no-self-compare
|
|
53
|
+
// Assume it to be a value related failure!
|
|
54
|
+
state.failureReason = state.REASON_VALUE_LIST;
|
|
55
|
+
state.failedValue = value;
|
|
56
|
+
return state;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Resolve range tunable for this message (if configured)
|
|
60
|
+
if ( state.rangesFn ) {
|
|
61
|
+
try {
|
|
62
|
+
state.resolvedRangeSpec = state.rangesFn( msg );
|
|
63
|
+
if ( state.tunableErrorLogged ) state.tunableErrorLogged = false;
|
|
64
|
+
} catch ( error ) {
|
|
65
|
+
if ( !state.tunableErrorLogged ) {
|
|
66
|
+
state.tunableErrorLogged = true;
|
|
67
|
+
console.error( `WinkComposer/${state.nodeType}: tunable threw: ${error.message}` );
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check range first (most common validation)
|
|
73
|
+
if ( !checkRange( state, value, state.resolvedRangeSpec ) ) {
|
|
74
|
+
state.failureReason = state.REASON_RANGE;
|
|
75
|
+
state.failedValue = value;
|
|
76
|
+
msg[ key ] = NaN;
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check value list
|
|
81
|
+
if ( !checkValueList( state, value ) ) {
|
|
82
|
+
state.failureReason = state.REASON_VALUE_LIST;
|
|
83
|
+
state.failedValue = value;
|
|
84
|
+
msg[ key ] = NaN;
|
|
85
|
+
return state;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check predicate
|
|
89
|
+
if ( !checkPredicate( state, value, msg ) ) {
|
|
90
|
+
state.failureReason = state.REASON_PREDICATE;
|
|
91
|
+
state.failedValue = value;
|
|
92
|
+
msg[ key ] = NaN;
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Value is valid, pass through unchanged
|
|
97
|
+
state.failureReason = null;
|
|
98
|
+
state.failedValue = null;
|
|
99
|
+
return state;
|
|
100
|
+
}; // update()
|
|
101
|
+
|
|
102
|
+
export default update;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// nodes/spike-guard/index.js
|
|
2
|
+
|
|
3
|
+
export { default as init } from './init.js';
|
|
4
|
+
export { default as update } from './update.js';
|
|
5
|
+
export { default as reset } from './reset.js';
|
|
6
|
+
export { default as recompute } from './recompute.js';
|
|
7
|
+
export { default as publishTo } from './publish-to.js';
|
|
8
|
+
// Direct imports for common control functions
|
|
9
|
+
export { default as disable } from '../../core/utils/node/disable.js';
|
|
10
|
+
export { default as enable } from '../../core/utils/node/enable.js';
|
|
11
|
+
export { default as pause } from '../../core/utils/node/pause.js';
|
|
12
|
+
export { default as unpause } from '../../core/utils/node/unpause.js';
|
|
13
|
+
// Re-export everything from introspection.js
|
|
14
|
+
export * from './introspect.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// nodes/spike-guard/init.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Initialize spikeGuard node state.
|
|
5
|
+
*
|
|
6
|
+
* Uses count-sliding ring buffer for 3-sample window.
|
|
7
|
+
* Unlike failed glitchGuard, stores VALUES not message references.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { validateSpec } from '../../core/utils/node/index.js';
|
|
11
|
+
import * as introspect from './introspect.js';
|
|
12
|
+
import { create } from '../../windowing/count-sliding/index.js';
|
|
13
|
+
|
|
14
|
+
const init = function ( spec ) {
|
|
15
|
+
validateSpec( spec, introspect );
|
|
16
|
+
|
|
17
|
+
// Create state after validation passes
|
|
18
|
+
const state = Object.create( null );
|
|
19
|
+
|
|
20
|
+
// Standard input parameter health flag during update: false means healthy
|
|
21
|
+
state.inputValidationFailed = false;
|
|
22
|
+
|
|
23
|
+
// Ensure node is enabled by default
|
|
24
|
+
state.disable = false;
|
|
25
|
+
state.pause = false;
|
|
26
|
+
|
|
27
|
+
// Extract field name and threshold
|
|
28
|
+
state.x = spec.from.x;
|
|
29
|
+
state.threshold = spec.threshold;
|
|
30
|
+
|
|
31
|
+
// Ring buffer for 3 values (reuses count-sliding)
|
|
32
|
+
state.ring = create( 3 );
|
|
33
|
+
|
|
34
|
+
// Config copy for publishTo
|
|
35
|
+
state.stats = spec.stats;
|
|
36
|
+
|
|
37
|
+
// Output state variables
|
|
38
|
+
state.clean = 0;
|
|
39
|
+
state.detected = false;
|
|
40
|
+
state.magnitude = 0;
|
|
41
|
+
|
|
42
|
+
state.nodeType = introspect.getNodeType();
|
|
43
|
+
|
|
44
|
+
return state;
|
|
45
|
+
}; // init()
|
|
46
|
+
|
|
47
|
+
export default init;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// nodes/spike-guard/introspect.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Introspection metadata for spikeGuard node.
|
|
5
|
+
*
|
|
6
|
+
* spikeGuard detects single-sample spikes using a 3-sample sliding window.
|
|
7
|
+
* Core insight: In window [left, middle, right]:
|
|
8
|
+
* - Spike: middle differs from BOTH left and right by > threshold
|
|
9
|
+
* - Transition: middle differs from only ONE neighbor
|
|
10
|
+
*
|
|
11
|
+
* This elegantly discriminates spikes from transitions without lookahead.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { validators } from '../../core/utils/validate/index.js';
|
|
15
|
+
|
|
16
|
+
/** Single source of truth for what this plugin can do */
|
|
17
|
+
const SUPPORTED_STATS = [
|
|
18
|
+
'clean', // Cleaned value (median of window)
|
|
19
|
+
'detected', // Boolean: true when spike detected
|
|
20
|
+
'magnitude' // Signed spike magnitude (negative=dip, positive=surge), 0 if no spike
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/** Human-readable descriptions (for introspection) */
|
|
24
|
+
const STAT_DESCRIPTIONS = {
|
|
25
|
+
clean: 'Cleaned value (median of 3-sample window)',
|
|
26
|
+
detected: 'Boolean: true when spike detected',
|
|
27
|
+
magnitude: 'Signed spike magnitude: negative=dip (dropout), positive=surge (noise), 0 if no spike'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Control methods available for triggers */
|
|
31
|
+
const SUPPORTED_CONTROL_METHODS = {
|
|
32
|
+
reset: 'Clears accumulated state and restarts computation',
|
|
33
|
+
enable: 'Enables node processing',
|
|
34
|
+
disable: 'Disables node processing',
|
|
35
|
+
pause: 'Pauses node processing while keeping state visible',
|
|
36
|
+
unpause: 'Resumes node processing after pause'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** The type of this node */
|
|
40
|
+
const NODE_TYPE = 'Spike Guard';
|
|
41
|
+
|
|
42
|
+
/** Node capabilities for documentation */
|
|
43
|
+
const CAPABILITIES = {
|
|
44
|
+
description: 'Detects single-sample spikes and outputs cleaned (median) value',
|
|
45
|
+
features: [
|
|
46
|
+
'Rolling window of exactly 3 values',
|
|
47
|
+
'Spike = middle differs from BOTH neighbors by > threshold',
|
|
48
|
+
'Outputs cleaned value (median) and detection flag',
|
|
49
|
+
'Zero false positives at state transitions'
|
|
50
|
+
]
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Export functions for accessing metadata
|
|
54
|
+
export const getSupportedStats = () => SUPPORTED_STATS.slice();
|
|
55
|
+
export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
|
|
56
|
+
export const getSupportedControlMethods = () => ( { ...SUPPORTED_CONTROL_METHODS } );
|
|
57
|
+
export const getNodeType = () => NODE_TYPE;
|
|
58
|
+
export const getCapabilities = () => ( { ...CAPABILITIES, features: CAPABILITIES.features.slice() } );
|
|
59
|
+
|
|
60
|
+
// DSL Metadata for transpilation
|
|
61
|
+
// Pattern: NAME_X_OUTPUTS_OPTIONS -> .spikeGuard( name, x, stats, options )
|
|
62
|
+
const DSL_METADATA = {
|
|
63
|
+
// Spec schema for validation
|
|
64
|
+
specSchema: {
|
|
65
|
+
nodeType: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
required: true,
|
|
68
|
+
value: NODE_TYPE
|
|
69
|
+
},
|
|
70
|
+
name: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
required: true,
|
|
73
|
+
validator: validators.identifier,
|
|
74
|
+
error: 'Name must be a valid identifier'
|
|
75
|
+
},
|
|
76
|
+
from: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
required: true,
|
|
79
|
+
properties: {
|
|
80
|
+
x: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
required: true,
|
|
83
|
+
validator: validators.noSpaces,
|
|
84
|
+
error: 'Field name cannot contain spaces'
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
threshold: {
|
|
89
|
+
type: 'number',
|
|
90
|
+
required: true,
|
|
91
|
+
validator: validators.positive,
|
|
92
|
+
error: 'Threshold must be a positive number'
|
|
93
|
+
},
|
|
94
|
+
stats: {
|
|
95
|
+
type: 'object',
|
|
96
|
+
required: true,
|
|
97
|
+
minProperties: 1,
|
|
98
|
+
propertyNames: SUPPORTED_STATS,
|
|
99
|
+
propertySchema: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
required: true,
|
|
102
|
+
properties: {
|
|
103
|
+
storeAs: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
required: true,
|
|
106
|
+
minLength: 1,
|
|
107
|
+
validator: validators.identifier,
|
|
108
|
+
error: 'storeAs must be a valid identifier'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Cross-field validators (none needed for spikeGuard)
|
|
116
|
+
crossFieldValidators: [],
|
|
117
|
+
|
|
118
|
+
// How to build the spec from DSL params
|
|
119
|
+
// Pattern: NAME_X_OUTPUTS_OPTIONS -> (name, x, stats, options)
|
|
120
|
+
buildSpec: ( name, x, stats, options ) => ( {
|
|
121
|
+
nodeType: NODE_TYPE,
|
|
122
|
+
name,
|
|
123
|
+
from: { x },
|
|
124
|
+
stats,
|
|
125
|
+
...options
|
|
126
|
+
} )
|
|
127
|
+
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const getDSLMetadata = () => ( { ...DSL_METADATA } );
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// nodes/spike-guard/publish-to.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Publish spikeGuard outputs to message.
|
|
5
|
+
*
|
|
6
|
+
* Supports three stats:
|
|
7
|
+
* - clean: Cleaned value (median of window)
|
|
8
|
+
* - detected: Boolean indicating spike detected
|
|
9
|
+
* - magnitude: Spike magnitude (0 if no spike)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { publishNaN } from '../../core/utils/node/index.js';
|
|
13
|
+
|
|
14
|
+
const publishTo = function ( state, msg ) {
|
|
15
|
+
if ( state.disable ) return;
|
|
16
|
+
|
|
17
|
+
// Propagate NaN if unhealthy
|
|
18
|
+
if ( state.inputValidationFailed ) {
|
|
19
|
+
publishNaN( state, msg );
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Publish configured stats (only those requested in spec)
|
|
24
|
+
if ( state.stats.clean ) {
|
|
25
|
+
msg[ state.stats.clean.storeAs ] = state.clean;
|
|
26
|
+
}
|
|
27
|
+
if ( state.stats.detected ) {
|
|
28
|
+
msg[ state.stats.detected.storeAs ] = state.detected;
|
|
29
|
+
}
|
|
30
|
+
if ( state.stats.magnitude ) {
|
|
31
|
+
msg[ state.stats.magnitude.storeAs ] = state.magnitude;
|
|
32
|
+
}
|
|
33
|
+
}; // publishTo()
|
|
34
|
+
|
|
35
|
+
export default publishTo;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// nodes/spike-guard/recompute.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Recompute for numerical stability.
|
|
5
|
+
*
|
|
6
|
+
* spikeGuard has no numerical drift - it only computes medians
|
|
7
|
+
* and absolute differences. No accumulation that could drift.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const recompute = function () {
|
|
11
|
+
return true;
|
|
12
|
+
}; // recompute()
|
|
13
|
+
|
|
14
|
+
export default recompute;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// nodes/spike-guard/reset.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Reset spikeGuard node state.
|
|
5
|
+
*
|
|
6
|
+
* Clears the ring buffer and resets output values.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { reset as resetRing } from '../../windowing/count-sliding/index.js';
|
|
10
|
+
|
|
11
|
+
const reset = function ( state ) {
|
|
12
|
+
resetRing( state.ring );
|
|
13
|
+
state.clean = 0;
|
|
14
|
+
state.detected = false;
|
|
15
|
+
state.magnitude = 0;
|
|
16
|
+
return true;
|
|
17
|
+
}; // reset()
|
|
18
|
+
|
|
19
|
+
export default reset;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// nodes/spike-guard/update.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Core spike detection algorithm.
|
|
5
|
+
*
|
|
6
|
+
* Window: [left, middle, right] (3-sample sliding window of VALUES)
|
|
7
|
+
*
|
|
8
|
+
* On each sample:
|
|
9
|
+
* 1. Push new value into window
|
|
10
|
+
* 2. If window full:
|
|
11
|
+
* - leftDiff = |middle - left|
|
|
12
|
+
* - rightDiff = |middle - right|
|
|
13
|
+
* - detected = (leftDiff > threshold) AND (rightDiff > threshold)
|
|
14
|
+
* - clean = median(left, middle, right)
|
|
15
|
+
* - magnitude = (middle - avg(left, right)) if detected, else 0
|
|
16
|
+
* 3. Output: clean, detected, magnitude (signed: negative=dip, positive=surge)
|
|
17
|
+
*
|
|
18
|
+
* Why This Works (No False Positives at Transitions):
|
|
19
|
+
*
|
|
20
|
+
* | Scenario | Window | leftDiff | rightDiff | Both > T? | Result |
|
|
21
|
+
* |---------------|------------------|----------|-----------|-----------|------------|
|
|
22
|
+
* | Spike | [90, 0.7, 90] | 89.3 | 89.3 | YES | DETECTED |
|
|
23
|
+
* | Falling edge | [90, 90, 0.7] | 0 | 89.3 | NO | not spike |
|
|
24
|
+
* | Falling edge | [90, 0.7, 0.7] | 89.3 | 0 | NO | not spike |
|
|
25
|
+
* | Rising edge | [0.7, 0.7, 90] | 0 | 89.3 | NO | not spike |
|
|
26
|
+
* | Rising edge | [0.7, 90, 90] | 89.3 | 0 | NO | not spike |
|
|
27
|
+
* | Normal | [89, 91, 90] | 2 | 1 | NO | not spike |
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { push } from '../../windowing/count-sliding/index.js';
|
|
31
|
+
|
|
32
|
+
const update = function ( state, msg ) {
|
|
33
|
+
if ( state.disable || state.pause ) return state;
|
|
34
|
+
|
|
35
|
+
const xVal = msg[ state.x ];
|
|
36
|
+
|
|
37
|
+
// Reset on each update
|
|
38
|
+
state.inputValidationFailed = false;
|
|
39
|
+
|
|
40
|
+
// Handle faults gracefully: ensure their isolation
|
|
41
|
+
if ( !Number.isFinite( xVal ) ) {
|
|
42
|
+
// Signals publishing NaN for all demanded `stats` in publishTo.
|
|
43
|
+
state.inputValidationFailed = true;
|
|
44
|
+
return state;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
push( state.ring, xVal );
|
|
48
|
+
|
|
49
|
+
// Need exactly 3 values for spike detection
|
|
50
|
+
if ( state.ring.used < 3 ) {
|
|
51
|
+
// Partial window: output current value, no detection
|
|
52
|
+
state.clean = xVal;
|
|
53
|
+
state.detected = false;
|
|
54
|
+
state.magnitude = 0;
|
|
55
|
+
return state;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Extract window values in chronological order: [oldest, middle, newest]
|
|
59
|
+
// The ring buffer's head points to the NEXT slot to write (= oldest value)
|
|
60
|
+
const head = state.ring.head;
|
|
61
|
+
const left = state.ring.buffer[ head ]; // oldest
|
|
62
|
+
const middle = state.ring.buffer[ ( head + 1 ) % 3 ]; // middle (potential spike)
|
|
63
|
+
const right = state.ring.buffer[ ( head + 2 ) % 3 ]; // newest (just pushed)
|
|
64
|
+
|
|
65
|
+
// Compute differences from middle to neighbors
|
|
66
|
+
const leftDiff = Math.abs( middle - left );
|
|
67
|
+
const rightDiff = Math.abs( middle - right );
|
|
68
|
+
|
|
69
|
+
// Spike = middle differs from BOTH neighbors by > threshold
|
|
70
|
+
state.detected = ( leftDiff > state.threshold ) &&
|
|
71
|
+
( rightDiff > state.threshold );
|
|
72
|
+
|
|
73
|
+
// Compute median for clean value (same algorithm as median3)
|
|
74
|
+
let a = left;
|
|
75
|
+
let b = middle;
|
|
76
|
+
let c = right;
|
|
77
|
+
if ( a > b ) [ a, b ] = [ b, a ];
|
|
78
|
+
if ( b > c ) [ b, c ] = [ c, b ];
|
|
79
|
+
state.clean = ( a > b ) ? a : b;
|
|
80
|
+
|
|
81
|
+
// Signed magnitude: deviation from expected (interpolated) value
|
|
82
|
+
// Negative = dip (dropout), Positive = surge (noise)
|
|
83
|
+
const expectedValue = ( left + right ) / 2;
|
|
84
|
+
state.magnitude = state.detected ? ( middle - expectedValue ) : 0;
|
|
85
|
+
|
|
86
|
+
return state;
|
|
87
|
+
}; // update()
|
|
88
|
+
|
|
89
|
+
export default update;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as init } from './init.js';
|
|
2
|
+
export { default as update } from './update.js';
|
|
3
|
+
export { default as reset } from './reset.js';
|
|
4
|
+
export { default as recompute } from './recompute.js';
|
|
5
|
+
export { default as publishTo } from './publish-to.js';
|
|
6
|
+
// Re-export everything from introspection.js
|
|
7
|
+
export * from './introspect.js';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { validateSpec } from '../../core/utils/node/index.js';
|
|
2
|
+
import * as introspect from './introspect.js';
|
|
3
|
+
import { DATA_PREFIX, MESSAGE_SUFFIX } from './utils/build-message.js';
|
|
4
|
+
import { createSSEHeaders, createConnection, setupConnectionHandlers } from './utils/connection-manager.js';
|
|
5
|
+
import { initializeEndpoints } from './utils/endpoint-initializer.js';
|
|
6
|
+
import { handleHealthCheck } from './utils/health-monitor.js';
|
|
7
|
+
import { broadcastHeartbeat } from './utils/broadcast-heartbeat.js';
|
|
8
|
+
import { createServer } from 'node:http';
|
|
9
|
+
|
|
10
|
+
// Simple module-level resources
|
|
11
|
+
let server = null;
|
|
12
|
+
let heartbeatInterval = null; // eslint-disable-line no-unused-vars
|
|
13
|
+
|
|
14
|
+
const handleSSEConnection = function ( state, req, res, endpointName ) {
|
|
15
|
+
// Check connection limit
|
|
16
|
+
if ( state.totalConnections >= state.maxConnections ) {
|
|
17
|
+
res.writeHead( 503, { 'Content-Type': 'text/plain' } );
|
|
18
|
+
res.end( `Max connections reached (${state.maxConnections})` );
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Set SSE headers
|
|
23
|
+
res.writeHead( 200, createSSEHeaders() );
|
|
24
|
+
|
|
25
|
+
// Create and register connection
|
|
26
|
+
const connection = createConnection( state, res, endpointName );
|
|
27
|
+
|
|
28
|
+
console.log( `SSE ${endpointName} connected (${state.totalConnections}/${state.maxConnections})` );
|
|
29
|
+
|
|
30
|
+
// Send initial message using same format as broadcasts
|
|
31
|
+
const initData = `{"connected":true,"endpoint":"${endpointName}"}`;
|
|
32
|
+
const initMessage = `data: ${initData}\n\n`;
|
|
33
|
+
res.write( initMessage );
|
|
34
|
+
|
|
35
|
+
// Setup disconnect handlers
|
|
36
|
+
setupConnectionHandlers( state, req, connection, endpointName );
|
|
37
|
+
}; // handleSSEConnection()
|
|
38
|
+
|
|
39
|
+
const routeRequest = function ( state, req, res ) {
|
|
40
|
+
const url = req.url;
|
|
41
|
+
|
|
42
|
+
if ( url === '/health' ) {
|
|
43
|
+
handleHealthCheck( state, res );
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if ( url.startsWith( '/events/' ) ) {
|
|
48
|
+
const endpointName = url.slice( 8 ); // Remove '/events/' prefix
|
|
49
|
+
|
|
50
|
+
if ( state.connections[ endpointName ] ) {
|
|
51
|
+
handleSSEConnection( state, req, res, endpointName );
|
|
52
|
+
} else {
|
|
53
|
+
res.writeHead( 404, { 'Content-Type': 'text/plain' } );
|
|
54
|
+
res.end( `Endpoint not configured. Available: ${state.endpointNames.join( ', ' )}` );
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
res.writeHead( 404 );
|
|
60
|
+
res.end( 'Not found' );
|
|
61
|
+
}; // routeRequest()
|
|
62
|
+
|
|
63
|
+
const init = function ( spec ) {
|
|
64
|
+
validateSpec( spec, introspect );
|
|
65
|
+
|
|
66
|
+
const state = Object.create( null );
|
|
67
|
+
|
|
68
|
+
// 2. Extract and store configuration
|
|
69
|
+
state.port = spec.port || 3000;
|
|
70
|
+
state.maxConnections = spec.maxConnections || 4;
|
|
71
|
+
state.stats = spec.stats;
|
|
72
|
+
state.nodeType = introspect.getNodeType();
|
|
73
|
+
|
|
74
|
+
// 3. Initialize endpoints and connections
|
|
75
|
+
initializeEndpoints( state );
|
|
76
|
+
|
|
77
|
+
// 4. Performance optimization: Pre-allocated buffers and constants
|
|
78
|
+
state.messageBuffer = Buffer.alloc( 4096 ); // 4KB reusable buffer
|
|
79
|
+
state.deadConnectionsPool = []; // Reusable arrays for cleanup
|
|
80
|
+
state.dataPrefix = DATA_PREFIX;
|
|
81
|
+
state.messageSuffix = MESSAGE_SUFFIX;
|
|
82
|
+
|
|
83
|
+
// 5. Initialize statistics tracking
|
|
84
|
+
state.totalConnections = 0;
|
|
85
|
+
state.totalSent = 0;
|
|
86
|
+
state.totalErrors = 0;
|
|
87
|
+
|
|
88
|
+
// 6. Create and start HTTP server
|
|
89
|
+
server = createServer( ( req, res ) => {
|
|
90
|
+
routeRequest( state, req, res );
|
|
91
|
+
} );
|
|
92
|
+
|
|
93
|
+
server.listen( state.port, () => {
|
|
94
|
+
console.log( `SSE Emitter listening on port ${state.port}` );
|
|
95
|
+
console.log( `Configured endpoints: ${state.endpointNames.join( ', ' )}` );
|
|
96
|
+
} );
|
|
97
|
+
|
|
98
|
+
heartbeatInterval = setInterval(() => {
|
|
99
|
+
broadcastHeartbeat(state);
|
|
100
|
+
}, 30000);
|
|
101
|
+
|
|
102
|
+
return state;
|
|
103
|
+
}; // init()
|
|
104
|
+
|
|
105
|
+
export default init;
|