@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,80 @@
|
|
|
1
|
+
// core/test-utils/sealed-record.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Test harness that proves a sink consumes a record
|
|
5
|
+
* synchronously.
|
|
6
|
+
*
|
|
7
|
+
* ADR-023 obliges every sink to read everything it needs from an incoming
|
|
8
|
+
* message before its hot-path call returns, and never to keep the
|
|
9
|
+
* reference for a later read. With a reused record (the handbook's
|
|
10
|
+
* record-reuse pattern, and the future declarative shaping), a late read
|
|
11
|
+
* would see a LATER message's values — silently corrupted data. This
|
|
12
|
+
* harness makes that rule testable: wrap a record, hand it to the sink,
|
|
13
|
+
* seal it the moment the call returns, and any read arriving after the
|
|
14
|
+
* seal is recorded as a violation.
|
|
15
|
+
*
|
|
16
|
+
* Violations are RECORDED, never thrown. A late read typically happens
|
|
17
|
+
* inside the sink's own async callback; a throw there would surface as an
|
|
18
|
+
* unhandled rejection instead of a clean assertion failure. Tests seal,
|
|
19
|
+
* await a tick, then assert `violations` is empty.
|
|
20
|
+
*
|
|
21
|
+
* Symbol-keyed accesses are ignored on purpose: engines and inspection
|
|
22
|
+
* tools probe symbols (Symbol.toStringTag and friends), while record
|
|
23
|
+
* columns are always string-named. Counting symbol traffic would produce
|
|
24
|
+
* false violations that have nothing to do with data reads.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Wraps a plain record in a Proxy that logs every read and, once sealed,
|
|
29
|
+
* records further reads as violations.
|
|
30
|
+
*
|
|
31
|
+
* @param {Object} fields - Column values the record carries.
|
|
32
|
+
* @returns {{record: Object, seal: Function, violations: Array, reads: Array}}
|
|
33
|
+
* `record` behaves exactly like `fields`; `seal()` marks the moment the
|
|
34
|
+
* sink's call returned (idempotent); `reads` holds `{trap, key}` entries
|
|
35
|
+
* logged before the seal, `violations` the ones logged after it.
|
|
36
|
+
*/
|
|
37
|
+
const makeSealedRecord = function ( fields ) {
|
|
38
|
+
const reads = [];
|
|
39
|
+
const violations = [];
|
|
40
|
+
let sealed = false;
|
|
41
|
+
|
|
42
|
+
const log = function ( trap, key ) {
|
|
43
|
+
if ( typeof key === 'symbol' ) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const entry = { trap, key };
|
|
47
|
+
if ( sealed ) {
|
|
48
|
+
violations.push( entry );
|
|
49
|
+
} else {
|
|
50
|
+
reads.push( entry );
|
|
51
|
+
}
|
|
52
|
+
}; // log()
|
|
53
|
+
|
|
54
|
+
const record = new Proxy( fields, {
|
|
55
|
+
get: function ( target, key, receiver ) {
|
|
56
|
+
log( 'get', key );
|
|
57
|
+
return Reflect.get( target, key, receiver );
|
|
58
|
+
},
|
|
59
|
+
has: function ( target, key ) {
|
|
60
|
+
log( 'has', key );
|
|
61
|
+
return Reflect.has( target, key );
|
|
62
|
+
},
|
|
63
|
+
ownKeys: function ( target ) {
|
|
64
|
+
log( 'ownKeys', null );
|
|
65
|
+
return Reflect.ownKeys( target );
|
|
66
|
+
},
|
|
67
|
+
getOwnPropertyDescriptor: function ( target, key ) {
|
|
68
|
+
log( 'getOwnPropertyDescriptor', key );
|
|
69
|
+
return Reflect.getOwnPropertyDescriptor( target, key );
|
|
70
|
+
}
|
|
71
|
+
} );
|
|
72
|
+
|
|
73
|
+
const seal = function () {
|
|
74
|
+
sealed = true;
|
|
75
|
+
}; // seal()
|
|
76
|
+
|
|
77
|
+
return { record, seal, violations, reads };
|
|
78
|
+
}; // makeSealedRecord()
|
|
79
|
+
|
|
80
|
+
export { makeSealedRecord };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// core/test-utils/soak-signature.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Release-soak signature policy for the MQTT emitter.
|
|
5
|
+
*
|
|
6
|
+
* The release gate runs the emitter soak (`slow-soak-mqtt-emitter.specs.js`)
|
|
7
|
+
* and has to judge a lossy run. One loss mechanism is documented and
|
|
8
|
+
* reported upstream: mqtt.js's reconnect-clear race — on every mid-run
|
|
9
|
+
* reconnect, `_onConnect` wipes the message-id registrations
|
|
10
|
+
* (client.js:1171) before the store re-drive re-registers them
|
|
11
|
+
* asynchronously (client.js:1223); a publish landing in that window
|
|
12
|
+
* overwrites an unacked store entry. The result, measured at
|
|
13
|
+
* ~12–14k msg/s: 1–6 messages lost per reconnect, the store counter
|
|
14
|
+
* left one high per loss (drift), and NO error callback anywhere —
|
|
15
|
+
* so shutdown times out on a phantom count over an empty store.
|
|
16
|
+
*
|
|
17
|
+
* The policy as first ratified (2026-07-07) tolerated a lossy run
|
|
18
|
+
* that carried exactly that signature —
|
|
19
|
+
*
|
|
20
|
+
* 1. at least one mid-run reconnect (the mechanism needs one),
|
|
21
|
+
* 2. loss small and bounded by the reconnect count,
|
|
22
|
+
* 3. zero delivery-failure callbacks (the race produces none), and
|
|
23
|
+
* 4. a shutdown throw, when present, classified `SHUTDOWN_TIMEOUT`
|
|
24
|
+
* with an exact `dropped.count` — never `DELIVERY_FAILED`.
|
|
25
|
+
*
|
|
26
|
+
* **The tolerance was retired by operator ruling on 2026-07-10.**
|
|
27
|
+
* ADR-021 moved the emitter onto mqtt.js's synchronous memory store,
|
|
28
|
+
* which is immune to the erase-then-rebuild race — so the signature's
|
|
29
|
+
* mechanism no longer exists in this build, and a verdict that can
|
|
30
|
+
* excuse a loss is a hole, not a tolerance. Every lossy run now fails.
|
|
31
|
+
* The signature detection is kept as DIAGNOSTICS: when a loss matches
|
|
32
|
+
* the retired signature, the failure reason says so, because "this
|
|
33
|
+
* looks like the foreclosed upstream race" is the first lead an
|
|
34
|
+
* investigator needs.
|
|
35
|
+
*
|
|
36
|
+
* This is test infrastructure (lives beside `tcp-proxy.js`), but the
|
|
37
|
+
* policy itself is tested — see `test/soak-signature.specs.js`.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Per-reconnect loss allowance. Measured losses were 1–6 per reconnect
|
|
42
|
+
* at ~12–14k msg/s; 10 gives headroom without letting an unrelated
|
|
43
|
+
* loss mechanism hide behind a single reconnect.
|
|
44
|
+
*
|
|
45
|
+
* @type {number}
|
|
46
|
+
*/
|
|
47
|
+
const LOSS_ALLOWANCE_PER_RECONNECT = 10;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Loss floor that counts as clean regardless of reconnects: one packet
|
|
51
|
+
* per 100,000 accepted (min 1) may be on the wire at the instant the
|
|
52
|
+
* test process dies. Predates the signature policy (the soak's
|
|
53
|
+
* original coverage assertion).
|
|
54
|
+
*
|
|
55
|
+
* @param {number} accepted - messages accepted by publishNow
|
|
56
|
+
* @returns {number} allowed gap count for a clean verdict
|
|
57
|
+
*/
|
|
58
|
+
const wireRaceFloor = function ( accepted ) {
|
|
59
|
+
return Math.max( 1, Math.floor( accepted / 100_000 ) );
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Judges a completed soak run against the release-gate policy.
|
|
64
|
+
*
|
|
65
|
+
* @param {Object} outcome - What the soak observed
|
|
66
|
+
* @param {Error|null} outcome.shutdownError - throw from emitter.shutdown(),
|
|
67
|
+
* or null when shutdown resolved cleanly
|
|
68
|
+
* @param {Array} outcome.deliveryFailures - every onDeliveryFailure call
|
|
69
|
+
* @param {number} outcome.coverageGaps - accepted IDs never received
|
|
70
|
+
* @param {number} outcome.accepted - messages accepted by publishNow
|
|
71
|
+
* @param {number} outcome.reconnects - emitter getHealth().stats.reconnects
|
|
72
|
+
* @returns {{verdict: 'clean'|'regression', reason: string}}
|
|
73
|
+
*/
|
|
74
|
+
const evaluateSoakOutcome = function ( outcome ) {
|
|
75
|
+
const {
|
|
76
|
+
shutdownError,
|
|
77
|
+
deliveryFailures,
|
|
78
|
+
coverageGaps,
|
|
79
|
+
accepted,
|
|
80
|
+
reconnects
|
|
81
|
+
} = outcome;
|
|
82
|
+
|
|
83
|
+
// Any delivery-failure callback is off-signature: the reconnect-clear
|
|
84
|
+
// race never produces one. Judged first — it blocks even a run that
|
|
85
|
+
// would otherwise look clean.
|
|
86
|
+
if ( deliveryFailures.length > 0 ) {
|
|
87
|
+
return {
|
|
88
|
+
verdict: 'regression',
|
|
89
|
+
reason: `${deliveryFailures.length} delivery failure(s) — first: ` +
|
|
90
|
+
`[${deliveryFailures[ 0 ].code}] ${deliveryFailures[ 0 ].message}`
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Clean: shutdown resolved and gaps are within the wire-race floor.
|
|
95
|
+
if ( !shutdownError && coverageGaps <= wireRaceFloor( accepted ) ) {
|
|
96
|
+
return {
|
|
97
|
+
verdict: 'clean',
|
|
98
|
+
reason: `no shutdown throw, no delivery failures, gaps ${coverageGaps} ` +
|
|
99
|
+
`within the wire-race floor (${wireRaceFloor( accepted )})`
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// From here the run is lossy and WILL fail. The checks below exist
|
|
104
|
+
// for the diagnosis in the reason: they tell an off-signature loss
|
|
105
|
+
// (name the first mismatched fact) apart from one that matches the
|
|
106
|
+
// retired reconnect-clear signature (name the match — see header).
|
|
107
|
+
if ( shutdownError && shutdownError.code !== 'SHUTDOWN_TIMEOUT' ) {
|
|
108
|
+
return {
|
|
109
|
+
verdict: 'regression',
|
|
110
|
+
reason: `shutdown threw [${shutdownError.code}] — the race's only throw is ` +
|
|
111
|
+
'SHUTDOWN_TIMEOUT (a DELIVERY_FAILED means real flush failures)'
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const droppedCount = shutdownError ? shutdownError.dropped?.count : 0;
|
|
115
|
+
if ( shutdownError && typeof droppedCount !== 'number' ) {
|
|
116
|
+
return {
|
|
117
|
+
verdict: 'regression',
|
|
118
|
+
reason: 'shutdown throw carries no dropped.count — cannot verify the drift signature'
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if ( reconnects < 1 ) {
|
|
122
|
+
return {
|
|
123
|
+
verdict: 'regression',
|
|
124
|
+
reason: `loss with zero mid-run reconnects (gaps ${coverageGaps}, ` +
|
|
125
|
+
`dropped ${droppedCount || 0}) — the reconnect-clear race needs a reconnect; ` +
|
|
126
|
+
'this is a different mechanism'
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const totalLoss = Math.max( coverageGaps, droppedCount || 0 );
|
|
130
|
+
const allowance = LOSS_ALLOWANCE_PER_RECONNECT * reconnects;
|
|
131
|
+
if ( totalLoss > allowance ) {
|
|
132
|
+
return {
|
|
133
|
+
verdict: 'regression',
|
|
134
|
+
reason: `loss ${totalLoss} exceeds the per-reconnect allowance ` +
|
|
135
|
+
`(${allowance} for ${reconnects} reconnect(s)) — too large for the race alone`
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
verdict: 'regression',
|
|
141
|
+
reason: `matches the RETIRED mqtt.js reconnect-clear race signature: ${totalLoss} lost ` +
|
|
142
|
+
`across ${reconnects} reconnect(s), zero delivery failures` +
|
|
143
|
+
( shutdownError ? `, SHUTDOWN_TIMEOUT with dropped.count ${droppedCount}` : '' ) +
|
|
144
|
+
' — tolerance retired 2026-07-10 (ADR-021\'s synchronous store forecloses the ' +
|
|
145
|
+
'mechanism), so this match needs investigation, not acceptance'
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export { evaluateSoakOutcome, LOSS_ALLOWANCE_PER_RECONNECT, wireRaceFloor };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// core/test-utils/tcp-proxy.js
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-empty-function */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @fileoverview Tiny TCP proxy for outage-recovery tests. Listens on
|
|
7
|
+
* a port, forwards each connection bidirectionally to a target port.
|
|
8
|
+
* Closing the proxy server tears down all live sockets so existing
|
|
9
|
+
* client sockets see the outage immediately; reopening on the same
|
|
10
|
+
* port restores connectivity.
|
|
11
|
+
*
|
|
12
|
+
* Used by the QuestDB recovery tests and the MQTT emitter
|
|
13
|
+
* recovery tests. Both adapters needed an in-test way to simulate
|
|
14
|
+
* "service unreachable" without coupling tests to docker daemon
|
|
15
|
+
* access. The pattern is identical for any TCP-based service.
|
|
16
|
+
*
|
|
17
|
+
* Cross-cutting test infrastructure mirrors `src/core/source-manager/test-harness/`
|
|
18
|
+
* — both live under `src/core/` because they're shared across adapter
|
|
19
|
+
* modules.
|
|
20
|
+
*
|
|
21
|
+
* Socket errors during proxy toggling are deliberately swallowed:
|
|
22
|
+
* the partner socket may close mid-pipe while the test toggles the
|
|
23
|
+
* proxy, and unhandled-error events would crash the test runner.
|
|
24
|
+
* The test asserts higher-level outcomes (delivery counts, health
|
|
25
|
+
* transitions, pipeline survival), not socket-level cleanliness.
|
|
26
|
+
*
|
|
27
|
+
* @module test-utils/tcp-proxy
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import net from 'node:net';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Start a TCP proxy on `listenPort` that forwards to `targetPort` on
|
|
34
|
+
* 127.0.0.1. Returns the underlying `net.Server` once it's listening;
|
|
35
|
+
* pass it to `stopProxy()` to tear down.
|
|
36
|
+
*
|
|
37
|
+
* **Why we track client sockets ourselves:** `net.Server` doesn't
|
|
38
|
+
* expose `closeAllConnections()` (only `http.Server` does, since
|
|
39
|
+
* Node 18.2). Without tracking, `server.close()` waits for client
|
|
40
|
+
* sockets to close from their end, which never happens for clients
|
|
41
|
+
* holding persistent connections (mqtt.js with keepalive). We track
|
|
42
|
+
* incoming sockets in a Set and destroy them explicitly on
|
|
43
|
+
* `stopProxy()` — that sends RST to the client so it sees the
|
|
44
|
+
* disconnect immediately.
|
|
45
|
+
*
|
|
46
|
+
* @param {number} listenPort
|
|
47
|
+
* @param {number} targetPort
|
|
48
|
+
* @returns {Promise<net.Server>}
|
|
49
|
+
*/
|
|
50
|
+
export const startProxy = function ( listenPort, targetPort ) {
|
|
51
|
+
const sockets = new Set();
|
|
52
|
+
const server = net.createServer( function ( clientSocket ) {
|
|
53
|
+
sockets.add( clientSocket );
|
|
54
|
+
clientSocket.on( 'close', function () {
|
|
55
|
+
sockets.delete( clientSocket );
|
|
56
|
+
} );
|
|
57
|
+
const targetSocket = net.connect( targetPort, '127.0.0.1', function () {
|
|
58
|
+
clientSocket.pipe( targetSocket );
|
|
59
|
+
targetSocket.pipe( clientSocket );
|
|
60
|
+
} );
|
|
61
|
+
// See file-header note on swallowed errors.
|
|
62
|
+
clientSocket.on( 'error', function () {} );
|
|
63
|
+
targetSocket.on( 'error', function () {} );
|
|
64
|
+
} );
|
|
65
|
+
// Attach the tracked-socket Set to the server object so
|
|
66
|
+
// `stopProxy()` can find it. Underscore-prefixed since this is
|
|
67
|
+
// a test-utility internal, not a public API.
|
|
68
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
69
|
+
server._sockets = sockets;
|
|
70
|
+
return new Promise( function ( resolve ) {
|
|
71
|
+
server.listen( listenPort, '127.0.0.1', function () {
|
|
72
|
+
resolve( server );
|
|
73
|
+
} );
|
|
74
|
+
} );
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Stop a proxy started via `startProxy()`. Force-destroys every
|
|
79
|
+
* tracked client socket first (sending RST so clients see the
|
|
80
|
+
* disconnect immediately, not at next keepalive timeout), then
|
|
81
|
+
* waits for the server to close cleanly.
|
|
82
|
+
*
|
|
83
|
+
* Without this, persistent-connection clients (mqtt.js with
|
|
84
|
+
* keepalive) keep their TCP socket alive indefinitely after the
|
|
85
|
+
* proxy "closes". `server.close()` would never fire its callback,
|
|
86
|
+
* and the client would never fire its disconnect events — the
|
|
87
|
+
* connection appears intact even though the proxy is gone. The MQTT
|
|
88
|
+
* emitter recovery tests surfaced this; the fix is to destroy from
|
|
89
|
+
* our side.
|
|
90
|
+
*
|
|
91
|
+
* @param {net.Server} server
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
export const stopProxy = function ( server ) {
|
|
95
|
+
return new Promise( function ( resolve ) {
|
|
96
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
97
|
+
const sockets = server._sockets || new Set();
|
|
98
|
+
for ( const sock of sockets ) {
|
|
99
|
+
sock.destroy();
|
|
100
|
+
}
|
|
101
|
+
sockets.clear();
|
|
102
|
+
server.close( function () {
|
|
103
|
+
resolve();
|
|
104
|
+
} );
|
|
105
|
+
} );
|
|
106
|
+
};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Helper functions for creating tunable parameters.
|
|
3
|
+
*
|
|
4
|
+
* These helpers provide common patterns for dynamic parameters with proper
|
|
5
|
+
* context capture for LLM/dashboard consumption. Each helper creates a
|
|
6
|
+
* function with custom toString() and .semantics for introspection.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // WiFi RSSI thresholds by protocol
|
|
10
|
+
* .threshold('rssiAlert', 'rssi', { alert: 'rssi_low' }, {
|
|
11
|
+
* threshold: lookupByField( 'protocolType', {
|
|
12
|
+
* '802.11ac': -70,
|
|
13
|
+
* '802.11n': -75
|
|
14
|
+
* }, -75 )
|
|
15
|
+
* })
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a tunable parameter that looks up value by a message field.
|
|
20
|
+
*
|
|
21
|
+
* Useful for protocol-specific thresholds, asset-class configs, etc.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} field - Message field to use as lookup key
|
|
24
|
+
* @param {Object} map - Lookup map from field values to parameter values
|
|
25
|
+
* @param {*} defaultVal - Default value if field value not in map
|
|
26
|
+
* @returns {Function} Tunable function with .semantics attached
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const threshold = lookupByField( 'protocolType', {
|
|
30
|
+
* '802.11ac': -70,
|
|
31
|
+
* '802.11n': -75
|
|
32
|
+
* }, -75 );
|
|
33
|
+
*
|
|
34
|
+
* threshold( { protocolType: '802.11ac' } ); // → -70
|
|
35
|
+
* threshold( { protocolType: '802.11g' } ); // → -75 (default)
|
|
36
|
+
*/
|
|
37
|
+
export const lookupByField = function ( field, map, defaultVal ) {
|
|
38
|
+
const fn = ( msg ) => map[ msg[ field ] ] ?? defaultVal;
|
|
39
|
+
|
|
40
|
+
fn.toString = () => `lookupByField("${field}", ${JSON.stringify( map )}, ${JSON.stringify( defaultVal )})`;
|
|
41
|
+
fn.semantics = {
|
|
42
|
+
type: 'lookupByField',
|
|
43
|
+
field,
|
|
44
|
+
map,
|
|
45
|
+
default: defaultVal
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return fn;
|
|
49
|
+
}; // lookupByField()
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a build-time resolver that picks an option value by the current fan field.
|
|
53
|
+
*
|
|
54
|
+
* `pickByField` is `lookupByField`'s build-time sibling, used INSIDE a `forEach`:
|
|
55
|
+
* it resolves to `map[ each.field ]` once, when the fan expands, and leaves no
|
|
56
|
+
* function behind at runtime. Use it for an option whose value differs per channel
|
|
57
|
+
* (a per-string clean range, a per-channel threshold). A field missing from `map`
|
|
58
|
+
* fails at build, not at the first message.
|
|
59
|
+
*
|
|
60
|
+
* It is resolved by the fan, never called per message, so its body throws if it is
|
|
61
|
+
* ever invoked (that would mean it was used outside a `forEach`). `.semantics`
|
|
62
|
+
* carries the map for the fan's resolver; `.toString` serializes it.
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} map - Map from field name to that channel's option value
|
|
65
|
+
* @returns {Function} Resolver marked with .semantics for build-time resolution
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* .forEach( [ 'scb1', 'scb2' ], ( each ) => each
|
|
69
|
+
* .threshold( 'hi', each.field, { active: 'high' },
|
|
70
|
+
* { threshold: pickByField( { scb1: 0.8, scb2: 0.6 } ), mode: 'above' } ) )
|
|
71
|
+
*/
|
|
72
|
+
export const pickByField = function ( map ) {
|
|
73
|
+
const fn = () => {
|
|
74
|
+
throw new Error(
|
|
75
|
+
'WinkComposer/flow: pickByField is resolved at build time inside forEach; ' +
|
|
76
|
+
'it has no runtime value. Use it only for a forEach option.'
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
fn.toString = () => `pickByField(${JSON.stringify( map )})`;
|
|
81
|
+
fn.semantics = {
|
|
82
|
+
type: 'pickByField',
|
|
83
|
+
map
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return fn;
|
|
87
|
+
}; // pickByField()
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a tunable parameter that scales a message field value.
|
|
91
|
+
*
|
|
92
|
+
* Computes: (msg[field] * factor) + offset, optionally snapped to step.
|
|
93
|
+
* Useful for adaptive sensitivity (e.g., delta = stdev * 0.5).
|
|
94
|
+
*
|
|
95
|
+
* @param {string} field - Message field to read
|
|
96
|
+
* @param {number} factor - Multiplication factor
|
|
97
|
+
* @param {number} [offset=0] - Additive offset
|
|
98
|
+
* @param {number} [step=0] - If > 0, snap result to nearest multiple
|
|
99
|
+
* @returns {Function} Tunable function with .semantics attached
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* // Adaptive sensitivity: delta tracks noise level
|
|
103
|
+
* const delta = scaleBy( 'fuel_stdev', 0.5 );
|
|
104
|
+
* delta( { fuel_stdev: 0.046 } ); // → 0.023
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* // With step snapping
|
|
108
|
+
* const rounded = scaleBy( 'value', 1.5, 10, 5 );
|
|
109
|
+
* rounded( { value: 20 } ); // → 40 (30 + 10 = 40, already on step)
|
|
110
|
+
*/
|
|
111
|
+
export const scaleBy = function ( field, factor, offset = 0, step = 0 ) {
|
|
112
|
+
const fn = ( msg ) => {
|
|
113
|
+
const raw = ( msg[ field ] * factor ) + offset;
|
|
114
|
+
return step > 0 ? Math.round( raw / step ) * step : raw;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
fn.toString = () => `scaleBy("${field}", ${factor}, ${offset}, ${step})`;
|
|
118
|
+
fn.semantics = {
|
|
119
|
+
type: 'scaleBy',
|
|
120
|
+
field,
|
|
121
|
+
factor,
|
|
122
|
+
offset,
|
|
123
|
+
step
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return fn;
|
|
127
|
+
}; // scaleBy()
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create a conditional tunable parameter that returns different values based on predicate.
|
|
131
|
+
*
|
|
132
|
+
* Useful for mode-dependent behavior (warmup vs normal, idle vs active).
|
|
133
|
+
*
|
|
134
|
+
* @param {Function} predicate - Function (msg) => boolean
|
|
135
|
+
* @param {*} trueVal - Value or function when predicate is true
|
|
136
|
+
* @param {*} falseVal - Value or function when predicate is false
|
|
137
|
+
* @param {string} predicateDesc - Human-readable description of predicate
|
|
138
|
+
* @returns {Function} Tunable function with .semantics attached
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* // Mode-dependent threshold
|
|
142
|
+
* const threshold = chooseWhen(
|
|
143
|
+
* ( msg ) => msg.isWarmup,
|
|
144
|
+
* 100, // warmup: high threshold
|
|
145
|
+
* 78, // normal: standard threshold
|
|
146
|
+
* 'msg.isWarmup'
|
|
147
|
+
* );
|
|
148
|
+
*
|
|
149
|
+
* threshold( { isWarmup: true } ); // → 100
|
|
150
|
+
* threshold( { isWarmup: false } ); // → 78
|
|
151
|
+
*/
|
|
152
|
+
export const chooseWhen = function ( predicate, trueVal, falseVal, predicateDesc ) {
|
|
153
|
+
const trueFn = typeof trueVal === 'function' ? trueVal : () => trueVal;
|
|
154
|
+
const falseFn = typeof falseVal === 'function' ? falseVal : () => falseVal;
|
|
155
|
+
|
|
156
|
+
const fn = ( msg ) => ( predicate( msg ) ? trueFn( msg ) : falseFn( msg ) );
|
|
157
|
+
|
|
158
|
+
fn.toString = () => `chooseWhen(${predicateDesc}, ${trueVal}, ${falseVal})`;
|
|
159
|
+
fn.semantics = {
|
|
160
|
+
type: 'chooseWhen',
|
|
161
|
+
predicate: predicateDesc,
|
|
162
|
+
trueVal,
|
|
163
|
+
falseVal
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return fn;
|
|
167
|
+
}; // chooseWhen()
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Create a tunable parameter that clamps a message field value to [min, max].
|
|
171
|
+
*
|
|
172
|
+
* Useful for safety limits or bounded parameters.
|
|
173
|
+
*
|
|
174
|
+
* @param {string} field - Message field to read
|
|
175
|
+
* @param {number} min - Minimum value (inclusive)
|
|
176
|
+
* @param {number} max - Maximum value (inclusive)
|
|
177
|
+
* @returns {Function} Tunable function with .semantics attached
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* const safeThreshold = clampTo( 'requestedThreshold', 10, 100 );
|
|
181
|
+
* safeThreshold( { requestedThreshold: 5 } ); // → 10
|
|
182
|
+
* safeThreshold( { requestedThreshold: 50 } ); // → 50
|
|
183
|
+
* safeThreshold( { requestedThreshold: 150 } ); // → 100
|
|
184
|
+
*/
|
|
185
|
+
export const clampTo = function ( field, min, max ) {
|
|
186
|
+
const fn = ( msg ) => {
|
|
187
|
+
const val = msg[ field ];
|
|
188
|
+
return val < min ? min : ( val > max ? max : val );
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
fn.toString = () => `clampTo("${field}", ${min}, ${max})`;
|
|
192
|
+
fn.semantics = {
|
|
193
|
+
type: 'clampTo',
|
|
194
|
+
field,
|
|
195
|
+
min,
|
|
196
|
+
max
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return fn;
|
|
200
|
+
}; // clampTo()
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create a tunable parameter that reads a field directly from the message.
|
|
204
|
+
*
|
|
205
|
+
* Simplest dynamic parameter — just passes through a message field.
|
|
206
|
+
* Useful when the parameter should track another field exactly.
|
|
207
|
+
*
|
|
208
|
+
* @param {string} field - Message field to read
|
|
209
|
+
* @param {*} [defaultVal] - Default value if field is missing/undefined
|
|
210
|
+
* @returns {Function} Tunable function with .semantics attached
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* const dynamicThreshold = fromField( 'learnedBaseline', 50 );
|
|
214
|
+
* dynamicThreshold( { learnedBaseline: 72 } ); // → 72
|
|
215
|
+
* dynamicThreshold( { otherField: 10 } ); // → 50 (default)
|
|
216
|
+
*/
|
|
217
|
+
export const fromField = function ( field, defaultVal ) {
|
|
218
|
+
const fn = ( msg ) => msg[ field ] ?? defaultVal;
|
|
219
|
+
|
|
220
|
+
fn.toString = () => `fromField("${field}", ${JSON.stringify( defaultVal )})`;
|
|
221
|
+
fn.semantics = {
|
|
222
|
+
type: 'fromField',
|
|
223
|
+
field,
|
|
224
|
+
default: defaultVal
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return fn;
|
|
228
|
+
}; // fromField()
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Create a tunable parameter that adds an offset to a message field value.
|
|
232
|
+
*
|
|
233
|
+
* Simpler than scaleBy when factor is 1.
|
|
234
|
+
*
|
|
235
|
+
* @param {string} field - Message field to read
|
|
236
|
+
* @param {number} offset - Value to add
|
|
237
|
+
* @returns {Function} Tunable function with .semantics attached
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* const threshold = offsetBy( 'baseline', 10 );
|
|
241
|
+
* threshold( { baseline: 72 } ); // → 82
|
|
242
|
+
*/
|
|
243
|
+
export const offsetBy = function ( field, offset ) {
|
|
244
|
+
const fn = ( msg ) => msg[ field ] + offset;
|
|
245
|
+
|
|
246
|
+
fn.toString = () => `offsetBy("${field}", ${offset})`;
|
|
247
|
+
fn.semantics = {
|
|
248
|
+
type: 'offsetBy',
|
|
249
|
+
field,
|
|
250
|
+
offset
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return fn;
|
|
254
|
+
}; // offsetBy()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tunable pattern for dynamic parameters.
|
|
3
|
+
*
|
|
4
|
+
* Enables parameters to adapt to message context (operating mode, regime, etc.)
|
|
5
|
+
* by normalizing static values and functions to a uniform callable interface.
|
|
6
|
+
*
|
|
7
|
+
* All node parameters become functions, enabling dynamic behavior
|
|
8
|
+
* without type-checking overhead during message processing.
|
|
9
|
+
*
|
|
10
|
+
* ## Guard Pattern (for node implementors)
|
|
11
|
+
*
|
|
12
|
+
* Dynamic tunables (user-supplied functions) can throw at runtime.
|
|
13
|
+
* Each node's update.js wraps all tunable resolves in a single try/catch.
|
|
14
|
+
* On throw, JS assignment semantics guarantee the state field retains its
|
|
15
|
+
* previous good value (RHS evaluated first; failed RHS ⇒ assignment
|
|
16
|
+
* never executes). Nodes seed state fields in init.js so the first-message
|
|
17
|
+
* edge case has a meaningful fallback.
|
|
18
|
+
*
|
|
19
|
+
* Log suppression: `state.tunableErrorLogged` flag prevents console
|
|
20
|
+
* flooding — one log per error episode, reset on successful recovery
|
|
21
|
+
* or on node reset (reset.js clears the flag).
|
|
22
|
+
*
|
|
23
|
+
* Static tunables (`asTunable(42)` → `() => 42`) cannot throw, so the
|
|
24
|
+
* guard only activates for dynamic tunables.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Convert value to function. Static values become () => value.
|
|
29
|
+
* Functions pass through unchanged.
|
|
30
|
+
*
|
|
31
|
+
* This enables uniform parameter access in node update() functions:
|
|
32
|
+
* `const threshold = state.thresholdFn( msg )` works whether threshold
|
|
33
|
+
* was configured as a static number or a dynamic function.
|
|
34
|
+
*
|
|
35
|
+
* @param {*} v - Static value or function
|
|
36
|
+
* @returns {Function} Always returns a function
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Static value
|
|
40
|
+
* const fn = asTunable( 42 );
|
|
41
|
+
* fn( msg ); // → 42
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // Dynamic function
|
|
45
|
+
* const fn = asTunable( ( msg ) => msg.stdev * 0.5 );
|
|
46
|
+
* fn( { stdev: 10 } ); // → 5
|
|
47
|
+
*/
|
|
48
|
+
export const asTunable = ( v ) => ( typeof v === 'function' ? v : () => v );
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Extract parameter context for LLM/dashboard consumption.
|
|
52
|
+
*
|
|
53
|
+
* Provides introspection metadata about whether a parameter is static
|
|
54
|
+
* or dynamic, including the formula string for dynamic parameters.
|
|
55
|
+
* Helper functions can attach `.semantics` for richer context.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} name - Parameter name
|
|
58
|
+
* @param {*} value - Parameter value (static or function)
|
|
59
|
+
* @returns {Object} Context object with type, value/formula, and semantics
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Static parameter
|
|
63
|
+
* extractParamContext( 'threshold', 78 );
|
|
64
|
+
* // → { name: 'threshold', type: 'static', value: 78 }
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // Dynamic parameter
|
|
68
|
+
* extractParamContext( 'delta', ( msg ) => msg.stdev * 0.5 );
|
|
69
|
+
* // → { name: 'delta', type: 'dynamic', formula: '( msg ) => msg.stdev * 0.5', semantics: null }
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Dynamic parameter with helper semantics
|
|
73
|
+
* const scaledDelta = scaleBy( 'stdev', 0.5 );
|
|
74
|
+
* extractParamContext( 'delta', scaledDelta );
|
|
75
|
+
* // → { name: 'delta', type: 'dynamic', formula: 'scaleBy("stdev", 0.5, 0, 0)', semantics: { type: 'scaleBy', ... } }
|
|
76
|
+
*/
|
|
77
|
+
export const extractParamContext = function ( name, value ) {
|
|
78
|
+
if ( typeof value === 'function' ) {
|
|
79
|
+
return {
|
|
80
|
+
name,
|
|
81
|
+
type: 'dynamic',
|
|
82
|
+
formula: value.toString(),
|
|
83
|
+
semantics: value.semantics || null
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return { name, type: 'static', value };
|
|
87
|
+
}; // extractParamContext()
|