@winkjs/composer 0.0.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +106 -80
- package/package.json +56 -28
- 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,63 @@
|
|
|
1
|
+
// nodes/csv-source/update.js
|
|
2
|
+
const update = function ( state, msg ) {
|
|
3
|
+
// Source nodes are special - they generate messages
|
|
4
|
+
// The msg parameter here is typically a trigger/timer signal
|
|
5
|
+
|
|
6
|
+
if ( !state.isLoaded || state.isPaused ) {
|
|
7
|
+
return null; // Nothing to emit
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if ( state.currentIndex >= state.data.length ) {
|
|
11
|
+
if ( state.loop ) {
|
|
12
|
+
state.currentIndex = 0; // Reset to beginning
|
|
13
|
+
} else {
|
|
14
|
+
state.isPaused = true; // Stop emitting
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Rate limiting for stream mode
|
|
20
|
+
if ( state.mode === 'stream' ) {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const timeSinceLastEmit = now - state.lastEmitTime;
|
|
23
|
+
const minInterval = 1000 / state.rate;
|
|
24
|
+
|
|
25
|
+
if ( timeSinceLastEmit < minInterval ) {
|
|
26
|
+
return null; // Too soon, skip this update
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
state.lastEmitTime = now;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Get next row
|
|
33
|
+
const row = state.data[ state.currentIndex ];
|
|
34
|
+
state.currentIndex += 1;
|
|
35
|
+
|
|
36
|
+
// Create output message
|
|
37
|
+
const output = Object.create( null );
|
|
38
|
+
|
|
39
|
+
// Add metadata
|
|
40
|
+
output._source = {
|
|
41
|
+
type: 'csv',
|
|
42
|
+
index: state.currentIndex - 1,
|
|
43
|
+
total: state.data.length,
|
|
44
|
+
timestamp: Date.now()
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Map columns
|
|
48
|
+
if ( state.columns === 'auto' ) {
|
|
49
|
+
// Use all columns as-is
|
|
50
|
+
Object.assign( output, row );
|
|
51
|
+
} else {
|
|
52
|
+
// Custom column mapping
|
|
53
|
+
for ( const [ csvCol, msgField ] of Object.entries( state.columns ) ) {
|
|
54
|
+
if ( row.hasOwnProperty( csvCol ) ) {
|
|
55
|
+
output[ msgField ] = row[ csvCol ];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return output; // This will be the new message
|
|
61
|
+
}; // update()
|
|
62
|
+
|
|
63
|
+
export default update;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public API barrel for butterworth-filter node.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all standard node interface functions and introspection metadata.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { default as init } from './init.js';
|
|
8
|
+
export { default as update } from './update.js';
|
|
9
|
+
export { default as reset } from './reset.js';
|
|
10
|
+
export { default as recompute } from './recompute.js';
|
|
11
|
+
export { default as publishTo } from './publish-to.js';
|
|
12
|
+
// Direct imports for common control functions
|
|
13
|
+
export { default as disable } from '../../core/utils/node/disable.js';
|
|
14
|
+
export { default as enable } from '../../core/utils/node/enable.js';
|
|
15
|
+
export { default as pause } from '../../core/utils/node/pause.js';
|
|
16
|
+
export { default as unpause } from '../../core/utils/node/unpause.js';
|
|
17
|
+
// Re-export everything from introspection.js
|
|
18
|
+
export * from './introspect.js';
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Initialization for butterworth-filter node.
|
|
3
|
+
*
|
|
4
|
+
* Validates the spec, computes 2nd-order Butterworth coefficients via the
|
|
5
|
+
* bilinear transform with frequency pre-warping, and returns fully
|
|
6
|
+
* initialized state. Supports lowpass/highpass, three cutoff specification
|
|
7
|
+
* methods, cascade adjustment, and DC initialization for transient reduction.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { validateSpec } from '../../core/utils/node/index.js';
|
|
11
|
+
import * as introspect from './introspect.js';
|
|
12
|
+
import { resolveScalar } from '../../core/utils/options/resolve-field-keyed.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
|
+
// Standard input parameter health flag during update: false means healthy
|
|
20
|
+
state.inputValidationFailed = false;
|
|
21
|
+
// Ensure node is enabled by default
|
|
22
|
+
state.disable = false;
|
|
23
|
+
state.pause = false;
|
|
24
|
+
|
|
25
|
+
// Extract field name
|
|
26
|
+
state.x = spec.from.x;
|
|
27
|
+
|
|
28
|
+
// Apply defaults from introspect (validation doesn't enforce them)
|
|
29
|
+
// Supports both direct and field-keyed specification for tunable params
|
|
30
|
+
const filterTypeSpec = resolveScalar( spec.filterType, state.x );
|
|
31
|
+
state.filterType = filterTypeSpec ?? introspect.DEFAULT_OPTIONS.filterType;
|
|
32
|
+
state.acceptNumericalRisk = spec.acceptNumericalRisk ?? introspect.DEFAULT_OPTIONS.acceptNumericalRisk;
|
|
33
|
+
|
|
34
|
+
// Extract sample rate (required field, validated by schema)
|
|
35
|
+
const sampleRateHz = spec.sampleRateHz;
|
|
36
|
+
const nyquistHz = sampleRateHz / 2;
|
|
37
|
+
|
|
38
|
+
// Resolve tunable params (supports field-keying)
|
|
39
|
+
const cutoffHzSpec = resolveScalar( spec.cutoffHz, state.x );
|
|
40
|
+
const settlingTimeMsSpec = resolveScalar( spec.settlingTimeMs, state.x );
|
|
41
|
+
const cutoffRatioSpec = resolveScalar( spec.cutoffRatio, state.x );
|
|
42
|
+
|
|
43
|
+
// Determine cutoff frequency from various possible inputs
|
|
44
|
+
let cutoffHz;
|
|
45
|
+
let configIntent = 'direct';
|
|
46
|
+
|
|
47
|
+
if ( cutoffHzSpec ) {
|
|
48
|
+
// Direct specification
|
|
49
|
+
cutoffHz = cutoffHzSpec;
|
|
50
|
+
|
|
51
|
+
} else if ( settlingTimeMsSpec && spec.sampleRateHz ) {
|
|
52
|
+
// Calculate from settling time requirement
|
|
53
|
+
// For 2nd order Butterworth: settling time ≈ 4 / (2π * fc) for 98% settling
|
|
54
|
+
const settlingTimeSec = settlingTimeMsSpec / 1000;
|
|
55
|
+
cutoffHz = 4 / ( 2 * Math.PI * settlingTimeSec );
|
|
56
|
+
configIntent = 'settling-time';
|
|
57
|
+
|
|
58
|
+
} else if ( cutoffRatioSpec ) {
|
|
59
|
+
// Relative to Nyquist
|
|
60
|
+
cutoffHz = cutoffRatioSpec * nyquistHz;
|
|
61
|
+
configIntent = 'ratio';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Adjust for cascading if specified
|
|
65
|
+
let cascadeAdjustment = 1;
|
|
66
|
+
if ( spec.adjustForCascade && spec.adjustForCascade > 1 ) {
|
|
67
|
+
// For N cascaded stages to have -3dB at fc:
|
|
68
|
+
// Each stage needs fc * 2^(1/N - 1)
|
|
69
|
+
cascadeAdjustment = Math.pow( 2, ( 1 / spec.adjustForCascade ) - 1 );
|
|
70
|
+
cutoffHz /= cascadeAdjustment;
|
|
71
|
+
configIntent += '-cascade-adjusted';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Validate the computed cutoff
|
|
75
|
+
if ( cutoffHz <= 0 || cutoffHz >= nyquistHz ) {
|
|
76
|
+
throw new RangeError(
|
|
77
|
+
`Computed cutoff ${cutoffHz.toFixed(1)}Hz is outside valid range (0, ${nyquistHz}Hz)`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Warn about potential numerical issues
|
|
82
|
+
const normalizedCutoff = cutoffHz / nyquistHz;
|
|
83
|
+
if ( normalizedCutoff < 0.001 && !state.acceptNumericalRisk ) {
|
|
84
|
+
console.warn( `Very low cutoff (${cutoffHz.toFixed(3)}Hz) may cause numerical instability` );
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Store configuration for reference
|
|
88
|
+
state.config = {
|
|
89
|
+
cutoffHz,
|
|
90
|
+
sampleRateHz,
|
|
91
|
+
normalizedCutoff,
|
|
92
|
+
intent: configIntent,
|
|
93
|
+
cascadeAdjustment,
|
|
94
|
+
dcEstimate: spec.dcEstimate // Store for reset functionality
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Calculate filter coefficients using bilinear transform
|
|
98
|
+
// Pre-warp the frequency to compensate for bilinear transform warping
|
|
99
|
+
const wc = ( Math.PI * normalizedCutoff ) / 2; // Bilinear pre-warp: tan(π·fc/fs)
|
|
100
|
+
const warpedWc = Math.tan( wc ); // Pre-warped cutoff frequency
|
|
101
|
+
|
|
102
|
+
// Butterworth prototype has Q = 1/√2 for maximally flat response
|
|
103
|
+
const Q = 1 / Math.sqrt( 2 );
|
|
104
|
+
|
|
105
|
+
// Bilinear transform coefficients
|
|
106
|
+
const K = warpedWc;
|
|
107
|
+
const K2 = K * K;
|
|
108
|
+
const norm = 1 / ( K2 + ( K / Q ) + 1 );
|
|
109
|
+
|
|
110
|
+
if ( state.filterType === 'lowpass' ) {
|
|
111
|
+
// H(s) = 1 / (s² + s/Q + 1) -> H(z) via bilinear transform
|
|
112
|
+
state.b0 = K2 * norm;
|
|
113
|
+
state.b1 = 2 * K2 * norm;
|
|
114
|
+
state.b2 = K2 * norm;
|
|
115
|
+
state.a1 = 2 * ( K2 - 1 ) * norm;
|
|
116
|
+
state.a2 = ( K2 - ( K / Q ) + 1 ) * norm;
|
|
117
|
+
} else { // highpass
|
|
118
|
+
// H(s) = s² / (s² + s/Q + 1) -> H(z) via bilinear transform
|
|
119
|
+
state.b0 = norm;
|
|
120
|
+
state.b1 = -2 * norm;
|
|
121
|
+
state.b2 = norm;
|
|
122
|
+
state.a1 = 2 * ( K2 - 1 ) * norm;
|
|
123
|
+
state.a2 = ( K2 - ( K / Q ) + 1 ) * norm;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Validate filter stability (poles inside unit circle)
|
|
127
|
+
/* c8 ignore next 5 -- defensive: bilinear transform produces stable coefficients for valid Nyquist-bounded cutoff */
|
|
128
|
+
if ( Math.abs( state.a2 ) >= 1 ) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Filter unstable: coefficient a2 (${state.a2.toFixed(4)}) is outside stability bounds`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* c8 ignore next 5 -- defensive: same Nyquist guard prevents a1 stability violation */
|
|
135
|
+
if ( Math.abs( state.a1 ) >= 1 + state.a2 ) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Filter unstable: coefficient a1 (${state.a1.toFixed(4)}) violates stability condition |a1| < 1 + a2`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// State variables for Direct Form II
|
|
142
|
+
state.z1 = 0;
|
|
143
|
+
state.z2 = 0;
|
|
144
|
+
|
|
145
|
+
// CRITICAL: Initialize output storage for publishTo()
|
|
146
|
+
// This ensures edge cases (NaN/Inf) work correctly on first call
|
|
147
|
+
state.output = 0;
|
|
148
|
+
|
|
149
|
+
// Initialization strategy
|
|
150
|
+
if ( spec.initStrategy === 'dc' && spec.dcEstimate !== undefined ) {
|
|
151
|
+
// Initialize DF2T state to steady-state for known DC level.
|
|
152
|
+
// At steady state with constant input D: output = D·G, z1 = G·D - b0·D,
|
|
153
|
+
// z2 = b2·D - a2·G·D, where G = H(1) = Σb / (1 + a1 + a2).
|
|
154
|
+
const dcGain = ( state.b0 + state.b1 + state.b2 ) / ( 1 + state.a1 + state.a2 );
|
|
155
|
+
if ( Math.abs( dcGain ) > 1e-10 ) {
|
|
156
|
+
const steadyOutput = spec.dcEstimate * dcGain;
|
|
157
|
+
state.z1 = steadyOutput - ( state.b0 * spec.dcEstimate );
|
|
158
|
+
state.z2 = ( state.b2 * spec.dcEstimate ) - ( state.a2 * steadyOutput );
|
|
159
|
+
state.output = steadyOutput;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Auto-compute performance characteristics
|
|
164
|
+
state.performance = {
|
|
165
|
+
multipliesPerSample: 5,
|
|
166
|
+
addsPerSample: 4,
|
|
167
|
+
memoryBytes: 56, // 7 Float64s (6 coeffs + 2 state + 1 output)
|
|
168
|
+
// Group delay approximation at cutoff (in samples)
|
|
169
|
+
groupDelaySamples: Math.round( 0.5 / ( Math.PI * normalizedCutoff ) ),
|
|
170
|
+
// Settling time to 98% (in samples)
|
|
171
|
+
settlingTimeSamples: Math.round( 4 / ( 2 * Math.PI * normalizedCutoff ) ),
|
|
172
|
+
// Useful for debugging
|
|
173
|
+
actualCutoffHz: cutoffHz,
|
|
174
|
+
normalizedCutoff: normalizedCutoff
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Output configuration
|
|
178
|
+
state.stats = spec.stats;
|
|
179
|
+
state.nodeType = introspect.getNodeType();
|
|
180
|
+
|
|
181
|
+
return state;
|
|
182
|
+
}; // init()
|
|
183
|
+
|
|
184
|
+
export default init;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Introspection metadata for butterworth-filter node.
|
|
3
|
+
*
|
|
4
|
+
* Defines supported stats, control methods, capabilities, DSL schema,
|
|
5
|
+
* and default options. All getters return defensive copies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { validators } from '../../core/utils/validate/index.js';
|
|
9
|
+
|
|
10
|
+
/** Single source of truth for capabilities */
|
|
11
|
+
const SUPPORTED_STATS = [ 'filtered' ];
|
|
12
|
+
|
|
13
|
+
const STAT_DESCRIPTIONS = {
|
|
14
|
+
filtered: 'Butterworth filtered signal'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Control methods available for triggers */
|
|
18
|
+
const SUPPORTED_CONTROL_METHODS = {
|
|
19
|
+
reset: 'Clears accumulated state and restarts computation',
|
|
20
|
+
enable: 'Enables node processing',
|
|
21
|
+
disable: 'Disables node processing',
|
|
22
|
+
pause: 'Pauses node processing while keeping state visible',
|
|
23
|
+
unpause: 'Resumes node processing after pause'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const NODE_TYPE = 'Butterworth Filter';
|
|
27
|
+
|
|
28
|
+
/** Node capabilities for documentation */
|
|
29
|
+
const CAPABILITIES = {
|
|
30
|
+
description: 'High-performance 2nd-order Butterworth filter for real-time streaming',
|
|
31
|
+
features: [
|
|
32
|
+
'Lowpass and highpass filtering',
|
|
33
|
+
'Multiple cutoff specification methods',
|
|
34
|
+
'DC initialization for transient reduction',
|
|
35
|
+
'Cascade adjustment for multi-stage filtering'
|
|
36
|
+
]
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Static metadata exports
|
|
40
|
+
export const getSupportedStats = () => SUPPORTED_STATS.slice();
|
|
41
|
+
export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
|
|
42
|
+
export const getSupportedControlMethods = () => ( { ...SUPPORTED_CONTROL_METHODS } );
|
|
43
|
+
export const getNodeType = () => NODE_TYPE;
|
|
44
|
+
export const getCapabilities = () => ( { ...CAPABILITIES, features: CAPABILITIES.features.slice() } );
|
|
45
|
+
|
|
46
|
+
export const DEFAULT_OPTIONS = {
|
|
47
|
+
filterType: 'lowpass',
|
|
48
|
+
acceptNumericalRisk: false
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// DSL Metadata for transpilation
|
|
52
|
+
const DSL_METADATA = {
|
|
53
|
+
// Spec schema for validation
|
|
54
|
+
specSchema: {
|
|
55
|
+
nodeType: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
required: true,
|
|
58
|
+
value: NODE_TYPE
|
|
59
|
+
},
|
|
60
|
+
name: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
required: true,
|
|
63
|
+
validator: validators.identifier,
|
|
64
|
+
error: 'Name must be a valid identifier'
|
|
65
|
+
},
|
|
66
|
+
from: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
required: true,
|
|
69
|
+
properties: {
|
|
70
|
+
x: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
required: true,
|
|
73
|
+
validator: validators.noSpaces,
|
|
74
|
+
error: 'Field name cannot contain spaces'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
filterType: {
|
|
79
|
+
type: 'stringOrFieldKeyed',
|
|
80
|
+
required: false,
|
|
81
|
+
default: 'lowpass',
|
|
82
|
+
validator: validators.oneOf([ 'lowpass', 'highpass' ]),
|
|
83
|
+
error: 'filterType must be "lowpass" or "highpass"'
|
|
84
|
+
},
|
|
85
|
+
sampleRateHz: {
|
|
86
|
+
type: 'number',
|
|
87
|
+
required: true,
|
|
88
|
+
validator: validators.positive,
|
|
89
|
+
error: 'sampleRateHz must be positive'
|
|
90
|
+
},
|
|
91
|
+
// One of these cutoff specifications is required
|
|
92
|
+
cutoffHz: {
|
|
93
|
+
type: 'numberOrFieldKeyed',
|
|
94
|
+
required: false,
|
|
95
|
+
validator: validators.positive,
|
|
96
|
+
error: 'cutoffHz must be positive'
|
|
97
|
+
},
|
|
98
|
+
settlingTimeMs: {
|
|
99
|
+
type: 'numberOrFieldKeyed',
|
|
100
|
+
required: false,
|
|
101
|
+
validator: validators.positive,
|
|
102
|
+
error: 'settlingTimeMs must be positive'
|
|
103
|
+
},
|
|
104
|
+
cutoffRatio: {
|
|
105
|
+
type: 'numberOrFieldKeyed',
|
|
106
|
+
required: false,
|
|
107
|
+
validator: validators.inRange(0, 1),
|
|
108
|
+
error: 'cutoffRatio must be between 0 and 1'
|
|
109
|
+
},
|
|
110
|
+
adjustForCascade: {
|
|
111
|
+
type: 'number',
|
|
112
|
+
required: false,
|
|
113
|
+
integer: true,
|
|
114
|
+
min: 2,
|
|
115
|
+
validator: validators.positiveInteger,
|
|
116
|
+
error: 'adjustForCascade must be an integer >= 2'
|
|
117
|
+
},
|
|
118
|
+
initStrategy: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
required: false,
|
|
121
|
+
validator: validators.oneOf([ 'dc' ]),
|
|
122
|
+
error: 'initStrategy must be "dc" if specified'
|
|
123
|
+
},
|
|
124
|
+
dcEstimate: {
|
|
125
|
+
type: 'number',
|
|
126
|
+
required: false
|
|
127
|
+
},
|
|
128
|
+
acceptNumericalRisk: {
|
|
129
|
+
type: 'boolean',
|
|
130
|
+
required: false,
|
|
131
|
+
default: false
|
|
132
|
+
},
|
|
133
|
+
stats: {
|
|
134
|
+
type: 'object',
|
|
135
|
+
required: true,
|
|
136
|
+
propertyNames: SUPPORTED_STATS,
|
|
137
|
+
propertySchema: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
required: true,
|
|
140
|
+
properties: {
|
|
141
|
+
storeAs: {
|
|
142
|
+
type: 'string',
|
|
143
|
+
required: true,
|
|
144
|
+
validator: validators.identifier,
|
|
145
|
+
error: 'storeAs must be a valid identifier'
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// Cross-field validators
|
|
153
|
+
crossFieldValidators: [
|
|
154
|
+
{
|
|
155
|
+
fields: [ 'cutoffHz', 'settlingTimeMs', 'cutoffRatio' ],
|
|
156
|
+
// At least one cutoff specification method must be provided
|
|
157
|
+
validator: ( spec ) => ( spec.cutoffHz || spec.settlingTimeMs || spec.cutoffRatio ),
|
|
158
|
+
error: 'Must specify cutoffHz, settlingTimeMs, or cutoffRatio'
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
fields: [ 'cutoffHz', 'settlingTimeMs', 'cutoffRatio' ],
|
|
162
|
+
validator: ( spec ) => {
|
|
163
|
+
// Only one cutoff specification method should be provided
|
|
164
|
+
const methods = [ spec.cutoffHz, spec.settlingTimeMs, spec.cutoffRatio ].filter( Boolean );
|
|
165
|
+
return methods.length === 1;
|
|
166
|
+
},
|
|
167
|
+
error: 'Specify only one of: cutoffHz, settlingTimeMs, or cutoffRatio'
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
fields: [ 'initStrategy', 'dcEstimate' ],
|
|
171
|
+
validator: ( spec ) => {
|
|
172
|
+
// If initStrategy is 'dc', dcEstimate must be provided
|
|
173
|
+
if ( spec.initStrategy === 'dc' ) {
|
|
174
|
+
return spec.dcEstimate !== undefined;
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
},
|
|
178
|
+
error: 'dcEstimate is required when initStrategy is "dc"'
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
fields: [ 'initStrategy', 'dcEstimate' ],
|
|
182
|
+
validator: ( spec ) => {
|
|
183
|
+
// If dcEstimate is provided, initStrategy must be 'dc'
|
|
184
|
+
if ( spec.dcEstimate !== undefined ) {
|
|
185
|
+
return spec.initStrategy === 'dc';
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
},
|
|
189
|
+
error: 'initStrategy must be "dc" when dcEstimate is provided'
|
|
190
|
+
}
|
|
191
|
+
],
|
|
192
|
+
|
|
193
|
+
// How to build the spec from DSL params
|
|
194
|
+
// Pattern: NAME_X_OUTPUTS_OPTIONS -> (name, x, stats, options)
|
|
195
|
+
buildSpec: ( name, x, stats, options ) => ( {
|
|
196
|
+
nodeType: NODE_TYPE,
|
|
197
|
+
name,
|
|
198
|
+
from: { x },
|
|
199
|
+
stats,
|
|
200
|
+
...options
|
|
201
|
+
} ),
|
|
202
|
+
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export const getDSLMetadata = () => ( { ...DSL_METADATA } );
|
|
206
|
+
|
|
207
|
+
// Performance and design info exports
|
|
208
|
+
export const getPerformance = ( state ) => ( { ...state.performance } );
|
|
209
|
+
|
|
210
|
+
export const getDesignInfo = ( state ) => ( {
|
|
211
|
+
filterType: state.filterType,
|
|
212
|
+
actualCutoffHz: state.config.cutoffHz,
|
|
213
|
+
sampleRateHz: state.config.sampleRateHz,
|
|
214
|
+
configMethod: state.config.intent,
|
|
215
|
+
normalized3dBFreq: state.config.normalizedCutoff,
|
|
216
|
+
cascadeAdjustment: state.config.cascadeAdjustment,
|
|
217
|
+
// Phase delay at cutoff in ms
|
|
218
|
+
phaseDelayMs: ( state.performance.groupDelaySamples / state.config.sampleRateHz ) * 1000,
|
|
219
|
+
// Settling time in ms
|
|
220
|
+
settlingTimeMs: ( state.performance.settlingTimeSamples / state.config.sampleRateHz ) * 1000
|
|
221
|
+
} );
|
|
222
|
+
|
|
223
|
+
// Human-readable description
|
|
224
|
+
export const describe = ( state ) => {
|
|
225
|
+
const info = getDesignInfo( state );
|
|
226
|
+
return `${state.filterType} filter: ${info.actualCutoffHz.toFixed(1)}Hz @ ${info.sampleRateHz}Hz ` +
|
|
227
|
+
`(settles in ~${info.settlingTimeMs.toFixed(0)}ms, delay ~${info.phaseDelayMs.toFixed(1)}ms)`;
|
|
228
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Publish function for butterworth-filter node.
|
|
3
|
+
*
|
|
4
|
+
* Copies the filtered output to the message, or propagates NaN if the last
|
|
5
|
+
* input was invalid. Skipped entirely when disabled.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { publishNaN } from '../../core/utils/node/index.js';
|
|
9
|
+
|
|
10
|
+
const publishTo = function ( state, msg ) {
|
|
11
|
+
if ( state.disable ) return;
|
|
12
|
+
// Propagate NaN if unhealthy
|
|
13
|
+
if ( state.inputValidationFailed ) {
|
|
14
|
+
publishNaN( state, msg );
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
msg[ state.stats.filtered.storeAs ] = state.output;
|
|
19
|
+
}; // publishTo()
|
|
20
|
+
|
|
21
|
+
export default publishTo;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Recompute for butterworth-filter node.
|
|
3
|
+
*
|
|
4
|
+
* No-op: stable 2nd-order IIR — state variables z1/z2 are bounded by
|
|
5
|
+
* exponential decay. No accumulated statistics susceptible to drift.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const recompute = function ( _state ) {
|
|
9
|
+
// Stable 2nd-order IIR: z1/z2 bounded by exponential decay; no drift.
|
|
10
|
+
return true;
|
|
11
|
+
}; // recompute()
|
|
12
|
+
|
|
13
|
+
export default recompute;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Reset for butterworth-filter node.
|
|
3
|
+
*
|
|
4
|
+
* Clears delay elements and output. When a DC estimate is configured,
|
|
5
|
+
* restores steady-state z1/z2 and output to minimize transient on restart.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const reset = function ( state ) {
|
|
9
|
+
// Clear delay elements
|
|
10
|
+
state.z1 = 0;
|
|
11
|
+
state.z2 = 0;
|
|
12
|
+
|
|
13
|
+
// Clear output
|
|
14
|
+
state.output = 0;
|
|
15
|
+
|
|
16
|
+
// Re-initialize to steady-state if DC estimate is known
|
|
17
|
+
if ( state.config.dcEstimate !== undefined ) {
|
|
18
|
+
const dcGain = ( state.b0 + state.b1 + state.b2 ) / ( 1 + state.a1 + state.a2 );
|
|
19
|
+
if ( Math.abs( dcGain ) > 1e-10 ) {
|
|
20
|
+
const steadyOutput = state.config.dcEstimate * dcGain;
|
|
21
|
+
state.z1 = steadyOutput - ( state.b0 * state.config.dcEstimate );
|
|
22
|
+
state.z2 = ( state.b2 * state.config.dcEstimate ) - ( state.a2 * steadyOutput );
|
|
23
|
+
state.output = steadyOutput;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return true;
|
|
28
|
+
}; // reset()
|
|
29
|
+
|
|
30
|
+
export default reset;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Update function for butterworth-filter node.
|
|
3
|
+
*
|
|
4
|
+
* Implements Direct Form II Transposed structure for a 2nd-order Butterworth
|
|
5
|
+
* filter. Denormals are flushed to zero to avoid performance degradation on
|
|
6
|
+
* x86 hardware.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const DENORMAL_THRESHOLD = 1e-30;
|
|
10
|
+
|
|
11
|
+
const update = function ( state, msg ) {
|
|
12
|
+
if ( state.disable || state.pause ) return state;
|
|
13
|
+
const xVal = msg[ state.x ];
|
|
14
|
+
// Reset on each update
|
|
15
|
+
state.inputValidationFailed = false;
|
|
16
|
+
// Handle faults gracefully: ensure their isolation
|
|
17
|
+
if ( !Number.isFinite( xVal ) ) {
|
|
18
|
+
// Signals publishing NaN for all demanded `stats` in publishTo.
|
|
19
|
+
state.inputValidationFailed = true;
|
|
20
|
+
return state;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Direct Form II implementation
|
|
24
|
+
const output = ( state.b0 * xVal ) + state.z1;
|
|
25
|
+
state.z1 = ( state.b1 * xVal ) - ( state.a1 * output ) + state.z2;
|
|
26
|
+
state.z2 = ( state.b2 * xVal ) - ( state.a2 * output );
|
|
27
|
+
|
|
28
|
+
// Flush denormals to zero (performance optimization)
|
|
29
|
+
if ( Math.abs( state.z1 ) < DENORMAL_THRESHOLD ) state.z1 = 0;
|
|
30
|
+
if ( Math.abs( state.z2 ) < DENORMAL_THRESHOLD ) state.z2 = 0;
|
|
31
|
+
|
|
32
|
+
// Store output in state for publishTo() to access
|
|
33
|
+
state.output = output;
|
|
34
|
+
|
|
35
|
+
return state;
|
|
36
|
+
}; // update()
|
|
37
|
+
|
|
38
|
+
export default update;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Entry point for categorize node.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all lifecycle functions (init, update, publishTo, reset, recompute),
|
|
5
|
+
* shared control signals (disable, enable, pause, unpause), and introspection metadata.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { default as init } from './init.js';
|
|
9
|
+
export { default as update } from './update.js';
|
|
10
|
+
export { default as reset } from './reset.js';
|
|
11
|
+
export { default as recompute } from './recompute.js';
|
|
12
|
+
export { default as publishTo } from './publish-to.js';
|
|
13
|
+
// Direct imports for common control functions
|
|
14
|
+
export { default as disable } from '../../core/utils/node/disable.js';
|
|
15
|
+
export { default as enable } from '../../core/utils/node/enable.js';
|
|
16
|
+
export { default as pause } from '../../core/utils/node/pause.js';
|
|
17
|
+
export { default as unpause } from '../../core/utils/node/unpause.js';
|
|
18
|
+
// Re-export everything from introspection.js
|
|
19
|
+
export * from './introspect.js';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Initialization for categorize node.
|
|
3
|
+
*
|
|
4
|
+
* Validates the spec, resolves thresholds (static or tunable via asTunable),
|
|
5
|
+
* resolves field-keyed categories, and builds the initial state with the
|
|
6
|
+
* default category (first element).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { validateSpec } from '../../core/utils/node/index.js';
|
|
10
|
+
import * as introspect from './introspect.js';
|
|
11
|
+
import { resolveArray } from '../../core/utils/options/resolve-field-keyed.js';
|
|
12
|
+
import { asTunable } from '../../core/tunable/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
|
+
// Standard input parameter health flag during update: false means healthy
|
|
20
|
+
state.inputValidationFailed = false;
|
|
21
|
+
// Ensure node is enabled by default
|
|
22
|
+
state.disable = false;
|
|
23
|
+
state.pause = false;
|
|
24
|
+
|
|
25
|
+
// Extract configuration
|
|
26
|
+
state.x = spec.from.x;
|
|
27
|
+
state.stats = spec.stats;
|
|
28
|
+
|
|
29
|
+
// Supports direct: { thresholds: [15, 25] }
|
|
30
|
+
// field-keyed: { thresholds: { temp: [15, 25], pressure: [30, 60] } }
|
|
31
|
+
// and tunable: { thresholds: lookupByField('shift', { day: [...], night: [...] }, [...]) }
|
|
32
|
+
if ( typeof spec.thresholds === 'function' ) {
|
|
33
|
+
// Dynamic thresholds (tunable) - resolve at runtime
|
|
34
|
+
state.thresholdsFn = spec.thresholds;
|
|
35
|
+
state.resolvedThresholds = null; // Not yet resolved — first message must succeed
|
|
36
|
+
} else {
|
|
37
|
+
// Static thresholds - resolve field-keying at init time
|
|
38
|
+
const thresholdsResolved = resolveArray( spec.thresholds, state.x );
|
|
39
|
+
state.thresholdsFn = asTunable( thresholdsResolved );
|
|
40
|
+
state.resolvedThresholds = thresholdsResolved; // Known at init
|
|
41
|
+
}
|
|
42
|
+
state.tunableErrorLogged = false;
|
|
43
|
+
|
|
44
|
+
// Categories remain static (structural)
|
|
45
|
+
state.categories = resolveArray( spec.categories, state.x );
|
|
46
|
+
|
|
47
|
+
// Current categorization state
|
|
48
|
+
state.categoryIndex = 0;
|
|
49
|
+
state.category = state.categories[ 0 ]; // Default to first category
|
|
50
|
+
|
|
51
|
+
// Node metadata
|
|
52
|
+
state.nodeType = introspect.getNodeType();
|
|
53
|
+
state.inControlPhase = false;
|
|
54
|
+
|
|
55
|
+
return state;
|
|
56
|
+
}; // init()
|
|
57
|
+
|
|
58
|
+
export default init;
|