@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,59 @@
|
|
|
1
|
+
// nodes/dwell-time-tracker/init.js
|
|
2
|
+
|
|
3
|
+
import { validateSpec } from '../../core/utils/node/index.js';
|
|
4
|
+
import * as introspect from './introspect.js';
|
|
5
|
+
|
|
6
|
+
const init = function ( spec ) {
|
|
7
|
+
validateSpec( spec, introspect );
|
|
8
|
+
|
|
9
|
+
// Create state after validation passes
|
|
10
|
+
const state = Object.create( null );
|
|
11
|
+
// Standard input parameter health flag during update: false means healthy
|
|
12
|
+
state.inputValidationFailed = false;
|
|
13
|
+
// Ensure node is enabled by default
|
|
14
|
+
state.disable = false;
|
|
15
|
+
state.pause = false;
|
|
16
|
+
|
|
17
|
+
// Extract configuration
|
|
18
|
+
state.predicate = spec.predicate;
|
|
19
|
+
state.timestampField = spec.timestampField || null;
|
|
20
|
+
|
|
21
|
+
// Config copy
|
|
22
|
+
state.stats = spec.stats;
|
|
23
|
+
|
|
24
|
+
// Track if we need duration tracking
|
|
25
|
+
state.needsDurationTracking = Boolean(
|
|
26
|
+
spec.stats.dwellTime ||
|
|
27
|
+
spec.stats.dwellSamples ||
|
|
28
|
+
spec.stats.dutyCycle
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// State variables - clear boolean semantics
|
|
32
|
+
state.active = false; // Current state
|
|
33
|
+
state.wasActive = false; // Previous state (for edge detection)
|
|
34
|
+
|
|
35
|
+
// Duration tracking state
|
|
36
|
+
state.stateEnteredAt = null; // When current state was entered (milliseconds)
|
|
37
|
+
state.dwellTime = null; // Duration of previous state (milliseconds)
|
|
38
|
+
state.dwellSamples = null; // Samples in the previous state
|
|
39
|
+
state.sampleCount = 0;
|
|
40
|
+
// Duty cycle tracking mechanism
|
|
41
|
+
state.dutyCycleTracker = Object.create( null );
|
|
42
|
+
// It is indexed by `wasActive` state as dwellTime is always for wasActive state (see update.js)
|
|
43
|
+
state.dutyCycleTracker.true = null;
|
|
44
|
+
state.dutyCycleTracker.false = null;
|
|
45
|
+
state.dutyCycle = null; // Used for publish duty cycle
|
|
46
|
+
|
|
47
|
+
// First message flag
|
|
48
|
+
state.hasSeenFirstValue = false;
|
|
49
|
+
|
|
50
|
+
state.nodeType = introspect.getNodeType();
|
|
51
|
+
state.inControlPhase = false;
|
|
52
|
+
|
|
53
|
+
// Log suppression: one log per error episode; reset on recovery.
|
|
54
|
+
state.predicateErrorLogged = false;
|
|
55
|
+
|
|
56
|
+
return state;
|
|
57
|
+
}; // init()
|
|
58
|
+
|
|
59
|
+
export default init;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// nodes/dwell-time-tracker/introspect.js
|
|
2
|
+
|
|
3
|
+
import { validators, composerValidators } from '../../core/utils/validate/index.js';
|
|
4
|
+
|
|
5
|
+
/** Single source of truth for what this plugin can do */
|
|
6
|
+
const SUPPORTED_STATS = [
|
|
7
|
+
'active', // Current boolean state (passthrough)
|
|
8
|
+
'dwellTime', // Duration of previous state (milliseconds)
|
|
9
|
+
'dwellSamples', // Number of samples in previous state
|
|
10
|
+
'dutyCycle' // Duty cycle ( active / ( active + inactive ) )
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
/** Human-readable descriptions (for introspection) */
|
|
14
|
+
const STAT_DESCRIPTIONS = {
|
|
15
|
+
active: 'Current boolean state of the tracked predicate condition.',
|
|
16
|
+
dwellTime: 'Duration in milliseconds for which the previous state was maintained; populated only at the moment of state transition, otherwise null.',
|
|
17
|
+
dwellSamples: 'Number of samples processed while in the previous state; populated only at the moment of state transition, otherwise null.',
|
|
18
|
+
dutyCycle: 'Fraction of time spent in true state over one complete cycle (true time / total time); populated after completing a full cycle (two consecutive transitions), otherwise null.'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/** Control methods available for triggers */
|
|
23
|
+
const SUPPORTED_CONTROL_METHODS = {
|
|
24
|
+
reset: 'Clears accumulated state and restarts computation',
|
|
25
|
+
enable: 'Enables node processing',
|
|
26
|
+
disable: 'Disables node processing',
|
|
27
|
+
pause: 'Pauses node processing while keeping state visible',
|
|
28
|
+
unpause: 'Resumes node processing after pause'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** The type of this node */
|
|
32
|
+
const NODE_TYPE = 'Dwell Time Tracker';
|
|
33
|
+
|
|
34
|
+
/** Node capabilities for documentation */
|
|
35
|
+
const CAPABILITIES = {
|
|
36
|
+
description: 'Tracks boolean state transitions with edge detection and dwell time measurement.',
|
|
37
|
+
features: [
|
|
38
|
+
'Evaluates a predicate function to detect state changes based on user-defined logic.',
|
|
39
|
+
'Measures dwell time in the previous state (in milliseconds).',
|
|
40
|
+
'Counts dwell samples in the previous state.',
|
|
41
|
+
'Identifies rising and falling edges using `dwellTime !== null` or `dwellSamples !== null` checks.',
|
|
42
|
+
'Enables easy computation of the previous toggle time via `timestamp - dwellTime`.',
|
|
43
|
+
'Supports both message timestamps and system time for flexible timing sources.'
|
|
44
|
+
]
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
/** Spec default values */
|
|
49
|
+
export const DEFAULT_OPTIONS = {
|
|
50
|
+
// No defaults needed for this node
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Export functions for accessing metadata
|
|
54
|
+
export const getSupportedStats = () => SUPPORTED_STATS.slice();
|
|
55
|
+
export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
|
|
56
|
+
export const getSupportedControlMethods = () => ( { ...SUPPORTED_CONTROL_METHODS } );
|
|
57
|
+
export const getNodeType = () => NODE_TYPE;
|
|
58
|
+
export const getCapabilities = () => ( {
|
|
59
|
+
...CAPABILITIES,
|
|
60
|
+
features: CAPABILITIES.features.slice()
|
|
61
|
+
} );
|
|
62
|
+
|
|
63
|
+
// DSL Metadata for transpilation
|
|
64
|
+
const DSL_METADATA = {
|
|
65
|
+
// Spec schema for validation
|
|
66
|
+
specSchema: {
|
|
67
|
+
nodeType: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
required: true,
|
|
70
|
+
value: NODE_TYPE
|
|
71
|
+
},
|
|
72
|
+
name: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
required: true,
|
|
75
|
+
validator: validators.identifier,
|
|
76
|
+
error: 'Name must be a valid identifier'
|
|
77
|
+
},
|
|
78
|
+
predicate: {
|
|
79
|
+
type: 'function',
|
|
80
|
+
required: true,
|
|
81
|
+
arity: 1,
|
|
82
|
+
validator: composerValidators.predicate,
|
|
83
|
+
error: 'Predicate must be a function accepting one parameter (msg)'
|
|
84
|
+
},
|
|
85
|
+
timestampField: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
required: false,
|
|
88
|
+
validator: validators.noSpaces,
|
|
89
|
+
error: 'Timestamp field name cannot contain spaces'
|
|
90
|
+
},
|
|
91
|
+
stats: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
required: true,
|
|
94
|
+
minProperties: 1,
|
|
95
|
+
propertyNames: SUPPORTED_STATS,
|
|
96
|
+
propertySchema: {
|
|
97
|
+
type: 'object',
|
|
98
|
+
required: true,
|
|
99
|
+
properties: {
|
|
100
|
+
storeAs: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
required: true,
|
|
103
|
+
minLength: 1,
|
|
104
|
+
validator: validators.identifier,
|
|
105
|
+
error: 'storeAs must be a valid identifier'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
triggers: {
|
|
111
|
+
type: 'array',
|
|
112
|
+
required: false,
|
|
113
|
+
itemSchema: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
validator: composerValidators.trigger,
|
|
116
|
+
error: 'Invalid trigger specification'
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// Cross-field validators
|
|
122
|
+
crossFieldValidators: [
|
|
123
|
+
composerValidators.noSelfTriggers
|
|
124
|
+
],
|
|
125
|
+
|
|
126
|
+
// How to build the spec from DSL params
|
|
127
|
+
buildSpec: ( name, predicate, stats, options ) => ( {
|
|
128
|
+
nodeType: NODE_TYPE,
|
|
129
|
+
name,
|
|
130
|
+
predicate,
|
|
131
|
+
stats,
|
|
132
|
+
...options
|
|
133
|
+
} ),
|
|
134
|
+
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const getDSLMetadata = () => ( { ...DSL_METADATA } );
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// nodes/dwell-time-tracker/publish-to.js
|
|
2
|
+
|
|
3
|
+
import { publishNaN } from '../../core/utils/node/index.js';
|
|
4
|
+
|
|
5
|
+
const publishTo = function ( state, msg ) {
|
|
6
|
+
if ( state.disable ) return;
|
|
7
|
+
// Propagate NaN if unhealthy
|
|
8
|
+
if ( state.inputValidationFailed ) {
|
|
9
|
+
publishNaN( state, msg );
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Iterate only over configured stats
|
|
14
|
+
const stats = state.stats;
|
|
15
|
+
for ( const statName in stats ) {
|
|
16
|
+
if ( Object.prototype.hasOwnProperty.call(stats, statName ) ) {
|
|
17
|
+
msg[ stats[ statName ].storeAs ] = state[ statName ];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}; // publishTo()
|
|
21
|
+
|
|
22
|
+
export default publishTo;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// nodes/dwell-time-tracker/reset.js
|
|
2
|
+
|
|
3
|
+
const reset = function ( state ) {
|
|
4
|
+
// Reset to initial conditions
|
|
5
|
+
state.active = false;
|
|
6
|
+
state.wasActive = false;
|
|
7
|
+
state.hasSeenFirstValue = false;
|
|
8
|
+
state.stateEnteredAt = null;
|
|
9
|
+
state.dwellTime = null;
|
|
10
|
+
state.dwellSamples = null;
|
|
11
|
+
state.sampleCount = 0;
|
|
12
|
+
|
|
13
|
+
state.dutyCycleTracker.true = null;
|
|
14
|
+
state.dutyCycleTracker.false = null;
|
|
15
|
+
state.dutyCycle = null;
|
|
16
|
+
|
|
17
|
+
// Clear error suppression so next error episode is logged
|
|
18
|
+
state.predicateErrorLogged = false;
|
|
19
|
+
|
|
20
|
+
return true;
|
|
21
|
+
}; // reset()
|
|
22
|
+
|
|
23
|
+
export default reset;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// nodes/dwell-time-tracker/update.js
|
|
2
|
+
|
|
3
|
+
import { executeTriggers } from '../../core/utils/node/index.js';
|
|
4
|
+
|
|
5
|
+
const update = function ( state, msg ) {
|
|
6
|
+
if ( state.disable || state.pause ) return state;
|
|
7
|
+
// Reset on each update
|
|
8
|
+
state.inputValidationFailed = false;
|
|
9
|
+
|
|
10
|
+
// Evaluate the predicate to compute the value
|
|
11
|
+
let value;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
value = state.predicate( msg );
|
|
15
|
+
// Recovery: clear log-suppression flag on success
|
|
16
|
+
if ( state.predicateErrorLogged ) state.predicateErrorLogged = false;
|
|
17
|
+
} catch ( error ) {
|
|
18
|
+
// Signals publishing NaN for all demanded `stats` in publishTo.
|
|
19
|
+
state.inputValidationFailed = true;
|
|
20
|
+
// Log first error per episode; suppress subsequent until recovery
|
|
21
|
+
if ( !state.predicateErrorLogged ) {
|
|
22
|
+
state.predicateErrorLogged = true;
|
|
23
|
+
console.error( `WinkComposer/dwellTimeTracker: predicate threw exception: ${error.message}` );
|
|
24
|
+
}
|
|
25
|
+
return state;
|
|
26
|
+
}
|
|
27
|
+
// Skip if not a valid boolean
|
|
28
|
+
if ( typeof value !== 'boolean' ) {
|
|
29
|
+
// Signals publishing NaN for all demanded `stats` in publishTo.
|
|
30
|
+
state.inputValidationFailed = true;
|
|
31
|
+
return state;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Get timestamp (from message or current time)
|
|
35
|
+
const now = state.timestampField ? msg[ state.timestampField ] : Date.now();
|
|
36
|
+
// Handle time related faults gracefully: ensure their isolation
|
|
37
|
+
if ( !Number.isFinite( now ) ) {
|
|
38
|
+
// Signals publishing NaN for all demanded `stats` in publishTo.
|
|
39
|
+
state.inputValidationFailed = true;
|
|
40
|
+
return state;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Remember previous state for edge detection
|
|
44
|
+
state.wasActive = state.active;
|
|
45
|
+
|
|
46
|
+
// Clear edge flags (they're momentary - true for one cycle only)
|
|
47
|
+
state.dwellTime = null; // Clear duration (only set on state change)
|
|
48
|
+
state.dwellSamples = null; // Clear samples.
|
|
49
|
+
state.dutyCycle = null; // Clear duty cycle
|
|
50
|
+
|
|
51
|
+
// Update active state (passthrough)
|
|
52
|
+
if ( state.hasSeenFirstValue ) {
|
|
53
|
+
state.active = value;
|
|
54
|
+
} else {
|
|
55
|
+
// First value - set initial state
|
|
56
|
+
state.active = value;
|
|
57
|
+
state.hasSeenFirstValue = true;
|
|
58
|
+
state.sampleCount = 1; // Seeing the very first sample
|
|
59
|
+
// Initialize state entry time ONLY for first value so that
|
|
60
|
+
// on change the first known value of time can be published
|
|
61
|
+
if ( state.needsDurationTracking ) {
|
|
62
|
+
state.stateEnteredAt = now;
|
|
63
|
+
}
|
|
64
|
+
// Since this is the first message, no need to check transition: simply return.
|
|
65
|
+
return state;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Detect edges and calculate duration
|
|
69
|
+
if ( state.active !== state.wasActive ) { // eslint-disable-line no-negated-condition
|
|
70
|
+
// Calculate duration and save entry timestamp
|
|
71
|
+
if ( state.needsDurationTracking ) {
|
|
72
|
+
if ( state.stateEnteredAt !== null ) {
|
|
73
|
+
state.dwellTime = now - state.stateEnteredAt;
|
|
74
|
+
if ( state.dwellTime < 0 ) state.dwellTime = 0;
|
|
75
|
+
// Save for publishing
|
|
76
|
+
state.dwellSamples = state.sampleCount;
|
|
77
|
+
|
|
78
|
+
// Store current dwell time FIRST: Update the tracker by `wasActive` index.
|
|
79
|
+
state.dutyCycleTracker[ state.wasActive ] = state.dwellTime;
|
|
80
|
+
|
|
81
|
+
if ( state.dutyCycleTracker.true !== null && state.dutyCycleTracker.false !== null ) {
|
|
82
|
+
// Both on and off times accumulated - compute duty cycle for complete cycle
|
|
83
|
+
const totalDuration = state.dutyCycleTracker.true + state.dutyCycleTracker.false;
|
|
84
|
+
// Guard: zero total duration (identical timestamps) — NaN signals invalid computation
|
|
85
|
+
state.dutyCycle = ( totalDuration > 0 ) ? ( state.dutyCycleTracker.true / totalDuration ) : NaN;
|
|
86
|
+
// Reset tracker
|
|
87
|
+
state.dutyCycleTracker.true = null;
|
|
88
|
+
state.dutyCycleTracker.false = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
state.stateEnteredAt = now; // Update for new state
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// First sample in the changed state
|
|
95
|
+
state.sampleCount = 1;
|
|
96
|
+
// Trigger controls on state change
|
|
97
|
+
executeTriggers( state );
|
|
98
|
+
} else state.sampleCount += 1; // Count samples as long as state is unchanged
|
|
99
|
+
|
|
100
|
+
return state;
|
|
101
|
+
}; // update()
|
|
102
|
+
|
|
103
|
+
export default update;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Helper functions for emitIf node
|
|
3
|
+
*
|
|
4
|
+
* Status signal emission for predicate error state transitions, and the
|
|
5
|
+
* shared publish-failure recording used by both the data path (update.js)
|
|
6
|
+
* and the status-signal path below.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Static fallback error the framework substitutes when an adapter breaks
|
|
11
|
+
* the return contract — no result object at all, or `{ ok: false }` with
|
|
12
|
+
* no `error` (ADR-018 error vocabulary). A module-level singleton, so the
|
|
13
|
+
* hot path allocates nothing. Mirrors persist-if.
|
|
14
|
+
*/
|
|
15
|
+
const MALFORMED_RESULT_ERROR = {
|
|
16
|
+
code: 'MALFORMED_RESULT',
|
|
17
|
+
message: 'emitter returned a malformed publish result (expected { ok, error? })'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Throws when an annotate return is not a plain object — arrays pass a
|
|
22
|
+
* bare typeof check but are just as wrong. A flow-authoring bug surfaces
|
|
23
|
+
* in the node's own error episode (the predicate/annotate try in
|
|
24
|
+
* update.js), never downstream where it would masquerade as an adapter
|
|
25
|
+
* failure. Mirrors persist-if.
|
|
26
|
+
*
|
|
27
|
+
* @param {*} record - The annotate return value
|
|
28
|
+
* @throws {Error} Naming the offending type
|
|
29
|
+
*/
|
|
30
|
+
const assertAnnotateReturn = function ( record ) {
|
|
31
|
+
const got = record === null ? 'null' :
|
|
32
|
+
( Array.isArray( record ) ? 'array' : typeof record );
|
|
33
|
+
if ( got === 'object' ) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
throw new Error( `annotate must return an object, got ${got}` );
|
|
37
|
+
}; // assertAnnotateReturn()
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Records a failed publish into the node's failure episode: updates the
|
|
41
|
+
* last* fields, and on the first failure of an episode captures the first*
|
|
42
|
+
* fields and logs one console.error (repeats stay quiet until a successful
|
|
43
|
+
* publish closes the episode — see update.js).
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} state - Node state
|
|
46
|
+
* @param {Object} error - The `{ code, message }` from the emitter's
|
|
47
|
+
* `{ ok: false }` return (ADR-018 sink return contract)
|
|
48
|
+
*/
|
|
49
|
+
const recordEmissionFailure = function ( state, error ) {
|
|
50
|
+
state.emissionErrors += 1;
|
|
51
|
+
state.lastEmissionError = error.message;
|
|
52
|
+
state.lastEmissionErrorCode = error.code;
|
|
53
|
+
if ( !state.emitErrorLogged ) {
|
|
54
|
+
state.emitErrorLogged = true;
|
|
55
|
+
state.firstEmissionError = error.message;
|
|
56
|
+
state.firstEmissionErrorCode = error.code;
|
|
57
|
+
console.error( state.emitErrorLogPrefix + error.code + '): ' + error.message );
|
|
58
|
+
}
|
|
59
|
+
}; // recordEmissionFailure()
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Emits disable/enable status signal to the wired emitter. Published
|
|
63
|
+
* unconditionally (no connectivity pre-check per ADR-018 — during a
|
|
64
|
+
* disconnect the signal is still accepted into the emitter's in-process
|
|
65
|
+
* buffer); a failed publish rides the same failure episode as data
|
|
66
|
+
* emissions.
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} state - Node state containing emitter and topic
|
|
69
|
+
* @param {boolean} disable - Whether to disable (true) or enable (false)
|
|
70
|
+
* @param {string} reason - Reason for disable
|
|
71
|
+
*/
|
|
72
|
+
const emitStatusSignal = function ( state, disable, reason ) {
|
|
73
|
+
if ( !state.emitter ) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const signal = {
|
|
78
|
+
$disable: disable,
|
|
79
|
+
$reason: reason,
|
|
80
|
+
$timestamp: Date.now()
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Guarded read: this path runs inside update()'s catch block, so a
|
|
84
|
+
// TypeError here would escape update() and kill the pipeline. A
|
|
85
|
+
// malformed result counts as a failure with the static fallback
|
|
86
|
+
// error instead.
|
|
87
|
+
const result = state.emitter.publishNow( state.topic, signal );
|
|
88
|
+
if ( result && result.ok ) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
recordEmissionFailure( state, ( result && result.error ) || MALFORMED_RESULT_ERROR );
|
|
92
|
+
}; // emitStatusSignal()
|
|
93
|
+
|
|
94
|
+
export { assertAnnotateReturn, emitStatusSignal, recordEmissionFailure, MALFORMED_RESULT_ERROR };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public API for emitIf node
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { default as init } from './init.js';
|
|
6
|
+
export { default as update } from './update.js';
|
|
7
|
+
export { default as reset } from './reset.js';
|
|
8
|
+
export { default as recompute } from './recompute.js';
|
|
9
|
+
export { default as publishTo } from './publish-to.js';
|
|
10
|
+
// Re-export everything from introspection
|
|
11
|
+
export * from './introspect.js';
|
|
12
|
+
|
|
13
|
+
// Note: Control signals (disable/enable/pause/unpause) are intentionally
|
|
14
|
+
// not exported. emitIf is a side-effect terminal node — it should always
|
|
15
|
+
// process when the pipeline runs. Use predicate logic for conditional
|
|
16
|
+
// emission. Consistent with persist-if (peer terminal node).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Initialization for emitIf node
|
|
3
|
+
*
|
|
4
|
+
* Validates specification and creates initial state for conditional
|
|
5
|
+
* emission to external systems (MQTT, GPIO, terminal).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { validateSpec } from '../../core/utils/node/index.js';
|
|
9
|
+
import * as introspect from './introspect.js';
|
|
10
|
+
|
|
11
|
+
const init = function ( spec ) {
|
|
12
|
+
validateSpec( spec, introspect );
|
|
13
|
+
|
|
14
|
+
// Create state after validation passes
|
|
15
|
+
const state = Object.create( null );
|
|
16
|
+
|
|
17
|
+
// Core configuration
|
|
18
|
+
state.nodeType = introspect.getNodeType();
|
|
19
|
+
state.name = spec.name;
|
|
20
|
+
state.requires = spec.requires;
|
|
21
|
+
state.predicate = spec.predicate;
|
|
22
|
+
state.target = spec.target;
|
|
23
|
+
state.emitter = spec.emitter;
|
|
24
|
+
state.annotate = spec.annotate || null;
|
|
25
|
+
state.insightType = spec.insightType;
|
|
26
|
+
// state.topic is injected by the partition manager during partition
|
|
27
|
+
// creation (not per-message). The topic encodes partition-scoped
|
|
28
|
+
// identity: edgeDeviceId/partitionId/specialization/insightType.
|
|
29
|
+
// This is an acceptable exception to "state shape fixed at init"
|
|
30
|
+
// because the partition ID is unknown until the first message arrives.
|
|
31
|
+
// See: partition-manager/update.js — MQTT TOPIC INJECTION block.
|
|
32
|
+
// Used by: update.js (emitter.publishNow) and helpers.js (status signals).
|
|
33
|
+
|
|
34
|
+
// State transition tracking (semantic — controls status signals)
|
|
35
|
+
state.inErrorState = false;
|
|
36
|
+
// Log suppression: one log per error episode; reset on recovery.
|
|
37
|
+
state.predicateErrorLogged = false;
|
|
38
|
+
|
|
39
|
+
// Statistics for observability
|
|
40
|
+
state.emissionCount = 0;
|
|
41
|
+
state.passCount = 0;
|
|
42
|
+
state.lastEmissionTime = null;
|
|
43
|
+
|
|
44
|
+
// Error tracking
|
|
45
|
+
state.emissionErrors = 0;
|
|
46
|
+
state.lastEmissionError = null;
|
|
47
|
+
// Mirrors persistIf: the code from the emitter's { ok: false, error:
|
|
48
|
+
// { code, message } } return; null when the error came from a predicate
|
|
49
|
+
// exception (predicate failures are not classified by the contract).
|
|
50
|
+
state.lastEmissionErrorCode = null;
|
|
51
|
+
// Publish-failure episode (mirrors persistIf's write-failure episode):
|
|
52
|
+
// one console.error per episode; a successful publish closes it. The
|
|
53
|
+
// first* fields keep the episode-opening error — in a cascade the LAST
|
|
54
|
+
// error is the symptom and the FIRST is the cause. They survive
|
|
55
|
+
// recovery for post-mortem reads and are overwritten only when the
|
|
56
|
+
// next episode opens.
|
|
57
|
+
state.emitErrorLogged = false;
|
|
58
|
+
state.firstEmissionError = null;
|
|
59
|
+
state.firstEmissionErrorCode = null;
|
|
60
|
+
// Static log prefix pre-built here per ADR-004 (no string building in
|
|
61
|
+
// update beyond the two runtime fields, and only once per episode).
|
|
62
|
+
state.emitErrorLogPrefix = `WinkComposer/emitIf: publish failed (node=${state.name}, insightType=${state.insightType}, code=`;
|
|
63
|
+
|
|
64
|
+
return state;
|
|
65
|
+
}; // init()
|
|
66
|
+
|
|
67
|
+
export default init;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Introspection metadata for emitIf node
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for DSL metadata, validation schema,
|
|
5
|
+
* and node capabilities. Used by flow transpiler and documentation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { validators } from '../../core/utils/validate/index.js';
|
|
9
|
+
const SUPPORTED_STATS = []; // Pass-through node, no stats to publish
|
|
10
|
+
|
|
11
|
+
/** Human-readable descriptions (for introspection) */
|
|
12
|
+
const STAT_DESCRIPTIONS = {}; // No stats, no descriptions
|
|
13
|
+
|
|
14
|
+
/** Control methods available for triggers (none - side-effect only node) */
|
|
15
|
+
const SUPPORTED_CONTROL_METHODS = {};
|
|
16
|
+
|
|
17
|
+
/** The type of this node */
|
|
18
|
+
const NODE_TYPE = 'Emit If';
|
|
19
|
+
|
|
20
|
+
/** Node capabilities for documentation (kept accurate to shipping code) */
|
|
21
|
+
const CAPABILITIES = {
|
|
22
|
+
description: 'Conditionally emits messages to external targets (MQTT, terminal) without disrupting pipeline flow',
|
|
23
|
+
features: [
|
|
24
|
+
'Pure pass-through behavior - messages continue unchanged',
|
|
25
|
+
'Conditional emission based on predicate function',
|
|
26
|
+
'Publishes through the emitter handle\'s publishNow and reads its classified { ok, error } result',
|
|
27
|
+
'Optional annotate hook shapes the emitted payload; a non-object return is rejected into the node\'s error episode',
|
|
28
|
+
'Status signals ($disable / $reason) on predicate error episodes and on recovery',
|
|
29
|
+
'Publish failures open a loud error episode (one console.error per episode); the pipeline never stops',
|
|
30
|
+
'Zero overhead when predicate evaluates to false'
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Export functions for accessing metadata
|
|
35
|
+
export const getSupportedStats = () => SUPPORTED_STATS.slice();
|
|
36
|
+
export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
|
|
37
|
+
export const getSupportedControlMethods = () => ( { ...SUPPORTED_CONTROL_METHODS } );
|
|
38
|
+
export const getNodeType = () => NODE_TYPE;
|
|
39
|
+
export const getCapabilities = () => ( { ...CAPABILITIES, features: CAPABILITIES.features.slice() } );
|
|
40
|
+
export const DEFAULT_OPTIONS = {};
|
|
41
|
+
|
|
42
|
+
// DSL Metadata for transpilation
|
|
43
|
+
const DSL_METADATA = {
|
|
44
|
+
// Validation schema
|
|
45
|
+
specSchema: {
|
|
46
|
+
nodeType: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
required: true,
|
|
49
|
+
value: NODE_TYPE
|
|
50
|
+
},
|
|
51
|
+
name: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
required: true,
|
|
54
|
+
validator: validators.identifier,
|
|
55
|
+
error: 'Name must be a valid JavaScript identifier'
|
|
56
|
+
},
|
|
57
|
+
predicate: {
|
|
58
|
+
type: 'function',
|
|
59
|
+
required: true,
|
|
60
|
+
arity: 1,
|
|
61
|
+
error: 'Predicate must be a function with exactly one parameter (inputs)'
|
|
62
|
+
},
|
|
63
|
+
target: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
required: true,
|
|
66
|
+
validator: validators.oneOf( [ 'mqtt', 'gpio', 'terminal' ] ),
|
|
67
|
+
error: 'Target type must be "mqtt", "gpio", or "terminal"'
|
|
68
|
+
},
|
|
69
|
+
insightType: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
required: true,
|
|
72
|
+
error: 'Insight type must be specified for topic construction'
|
|
73
|
+
},
|
|
74
|
+
annotate: {
|
|
75
|
+
type: 'function',
|
|
76
|
+
required: false,
|
|
77
|
+
arity: 1,
|
|
78
|
+
error: 'Annotate must be a function with exactly one parameter (msg)'
|
|
79
|
+
},
|
|
80
|
+
stats: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
required: false,
|
|
83
|
+
propertyNames: [], // No stats for pass-through node
|
|
84
|
+
error: 'EmitIf is a pass-through node and does not support stats'
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// Cross-field validators
|
|
89
|
+
crossFieldValidators: [
|
|
90
|
+
],
|
|
91
|
+
|
|
92
|
+
// Build spec from DSL
|
|
93
|
+
buildSpec: ( name, predicate, options ) => ( {
|
|
94
|
+
nodeType: NODE_TYPE,
|
|
95
|
+
name,
|
|
96
|
+
predicate,
|
|
97
|
+
...options
|
|
98
|
+
} ),
|
|
99
|
+
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const getDSLMetadata = () => ( { ...DSL_METADATA } );
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview PublishTo function for emitIf node
|
|
3
|
+
*
|
|
4
|
+
* Pass-through node — message continues unchanged.
|
|
5
|
+
* No fields are added to the outgoing message.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const publishTo = function ( _state, _msg ) {
|
|
9
|
+
// Pure pass-through node - message continues unchanged
|
|
10
|
+
// No fields are added to the message
|
|
11
|
+
}; // publishTo()
|
|
12
|
+
|
|
13
|
+
export default publishTo;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Recompute function for emitIf node
|
|
3
|
+
*
|
|
4
|
+
* No numerical computation to stabilize.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const recompute = function ( _state ) {
|
|
8
|
+
// No numerical computation to stabilize
|
|
9
|
+
return true;
|
|
10
|
+
}; // recompute()
|
|
11
|
+
|
|
12
|
+
export default recompute;
|