@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,245 @@
|
|
|
1
|
+
// core/env-vars.js
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-process-env */
|
|
4
|
+
|
|
5
|
+
import os from 'os';
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// ENVIRONMENT VARIABLE DEFINITIONS
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
// Yield threshold parses with Number(), not parseInt(): 'Infinity' must map
|
|
12
|
+
// to Infinity — the documented never-yield sentinel (`.yield( { threshold:
|
|
13
|
+
// Infinity } )` uses the same value). An empty string is forced to NaN so
|
|
14
|
+
// validation fails fast; Number( '' ) would silently give 0, which means
|
|
15
|
+
// "breathe after every message" — not what an operator who set the variable
|
|
16
|
+
// to nothing intended.
|
|
17
|
+
const rawYieldMs = ( process.env.YIELD_TIME_THRESHOLD_MS ?? '500' ).trim();
|
|
18
|
+
|
|
19
|
+
const ENV_VARS = {
|
|
20
|
+
// Core Configuration
|
|
21
|
+
edgeDeviceId: ( process.env.EDGE_DEVICE_ID ?? os.hostname() ).trim(),
|
|
22
|
+
nodeEnv: ( process.env.NODE_ENV ?? 'development' ).trim(),
|
|
23
|
+
|
|
24
|
+
// Partition Management (see ADR-016)
|
|
25
|
+
maxPartitionsAllowed: parseInt( process.env.COMPOSER_MAX_PARTITIONS_ALLOWED ?? '10000', 10 ),
|
|
26
|
+
|
|
27
|
+
// STORAGE_DIR retired 2026-07-09 — the emitter's LevelDB store
|
|
28
|
+
// (removed by ADR-021) was the only thing that ever wrote there.
|
|
29
|
+
// A future WAL adapter defines its own disk-path configuration.
|
|
30
|
+
|
|
31
|
+
// Flow lifecycle — top-level forced-shutdown timeout (process layer)
|
|
32
|
+
// Bounds the entire `handle.shutdown()` drain. If `shutdown-manager`
|
|
33
|
+
// doesn't see graceful completion within this many milliseconds, it
|
|
34
|
+
// logs and force-exits with code 1 (vs 0 on graceful). Matches
|
|
35
|
+
// Kubernetes' default `terminationGracePeriodSeconds` of 30. See
|
|
36
|
+
// ADR-018 (flow lifecycle and signal handling).
|
|
37
|
+
shutdownForceTimeoutMs: parseInt( process.env.SHUTDOWN_FORCE_TIMEOUT_MS ?? '30000', 10 ),
|
|
38
|
+
|
|
39
|
+
// Flow yield — default time threshold between deliberate event-loop
|
|
40
|
+
// breaths (ADR-024). Matters only to callers that wait on
|
|
41
|
+
// `processMessage` (file replays, testHarness, the headless driver):
|
|
42
|
+
// for them the yield tick is the only planned chance for sink flush
|
|
43
|
+
// timers and socket I/O to run. 500 ms is half the tightest background
|
|
44
|
+
// timer it must not starve (QUESTDB_IDLE_FLUSH_CHECK_MS, 1000).
|
|
45
|
+
// Per-flow override: `.yield( { threshold } )`. Infinity = never yield.
|
|
46
|
+
yieldTimeThresholdMs: rawYieldMs === '' ? NaN : Number( rawYieldMs ),
|
|
47
|
+
|
|
48
|
+
// MQTT Configuration
|
|
49
|
+
mqttBrokerUrl: ( process.env.MQTT_BROKER_URL ?? 'mqtt://127.0.0.1:1883' ).trim(),
|
|
50
|
+
mqttMsgExpiry: parseInt( process.env.MQTT_MSG_EXPIRY ?? '3600', 10 ),
|
|
51
|
+
mqttKeepalive: parseInt( process.env.MQTT_KEEPALIVE ?? '60', 10 ),
|
|
52
|
+
mqttReconnectMs: parseInt( process.env.MQTT_RECONNECT_MS ?? '5000', 10 ),
|
|
53
|
+
mqttConnectTimeoutMs: parseInt( process.env.MQTT_CONNECT_TIMEOUT_MS ?? '30000', 10 ),
|
|
54
|
+
// How long the MQTT emitter factory waits at startup for the
|
|
55
|
+
// broker's first connection acknowledgment before handing the
|
|
56
|
+
// flow its handle anyway (recovering posture — the wait expiring
|
|
57
|
+
// is not an error). 0 disables the wait. Per-emitter override:
|
|
58
|
+
// the `connectGraceMs` config key.
|
|
59
|
+
mqttConnectGraceMs: parseInt( process.env.MQTT_CONNECT_GRACE_MS ?? '500', 10 ),
|
|
60
|
+
mqttSessionExpiryS: parseInt( process.env.MQTT_SESSION_EXPIRY_S ?? '604800', 10 ),
|
|
61
|
+
mqttMaxQueueSize: parseInt( process.env.MQTT_MAX_QUEUE_SIZE ?? '10000', 10 ),
|
|
62
|
+
// MQTT_MAX_QUEUE_BYTES retired with the emitter's disk store
|
|
63
|
+
// (ADR-021) — the in-memory emitter bounds memory by count alone.
|
|
64
|
+
// MQTT_DEDUP_WINDOW (count-only dedup) retired 2026-07-09 by
|
|
65
|
+
// ADR-022 — the source dedup cache is now time-bounded AND
|
|
66
|
+
// count-capped, with its own two variables below.
|
|
67
|
+
mqttSourceDedupWindowMs: parseInt( process.env.MQTT_SOURCE_DEDUP_WINDOW_MS ?? '120000', 10 ),
|
|
68
|
+
mqttSourceDedupMaxEntries: parseInt( process.env.MQTT_SOURCE_DEDUP_MAX_ENTRIES ?? '65536', 10 ),
|
|
69
|
+
|
|
70
|
+
// QuestDB Configuration
|
|
71
|
+
questdbIlpUrl: ( process.env.QUESTDB_ILP_URL ?? 'localhost:9000' ).trim(),
|
|
72
|
+
questdbPgUrl: ( process.env.QUESTDB_PG_URL ?? 'localhost:8812' ).trim(),
|
|
73
|
+
questdbFlushMode: ( process.env.QUESTDB_FLUSH_MODE ?? 'auto' ).trim(),
|
|
74
|
+
questdbIdleFlushAfterMs: parseInt( process.env.QUESTDB_IDLE_FLUSH_AFTER_MS ?? '5000', 10 ),
|
|
75
|
+
questdbIdleFlushCheckMs: parseInt( process.env.QUESTDB_IDLE_FLUSH_CHECK_MS ?? '1000', 10 ),
|
|
76
|
+
// Optional QuestDB settings (undefined if not set)
|
|
77
|
+
questdbAutoFlushRows: process.env.QUESTDB_AUTO_FLUSH_ROWS ?
|
|
78
|
+
parseInt( process.env.QUESTDB_AUTO_FLUSH_ROWS, 10 ) : undefined,
|
|
79
|
+
questdbAutoFlushIntervalMs: process.env.QUESTDB_AUTO_FLUSH_INTERVAL_MS ?
|
|
80
|
+
parseInt( process.env.QUESTDB_AUTO_FLUSH_INTERVAL_MS, 10 ) : undefined,
|
|
81
|
+
questdbMaxBufSize: process.env.QUESTDB_MAX_BUF_SIZE ?
|
|
82
|
+
parseInt( process.env.QUESTDB_MAX_BUF_SIZE, 10 ) : undefined,
|
|
83
|
+
questdbRetryTimeout: process.env.QUESTDB_RETRY_TIMEOUT ?
|
|
84
|
+
parseInt( process.env.QUESTDB_RETRY_TIMEOUT, 10 ) : undefined,
|
|
85
|
+
|
|
86
|
+
// QuestDB Credentials
|
|
87
|
+
questdbDatabase: ( process.env.QUESTDB_DATABASE ?? 'qdb' ).trim(),
|
|
88
|
+
questdbUser: ( process.env.QUESTDB_USER ?? 'admin' ).trim(),
|
|
89
|
+
questdbPassword: process.env.QUESTDB_PASSWORD ?? 'quest'
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// VALIDATORS
|
|
94
|
+
// ============================================================================
|
|
95
|
+
const validators = {
|
|
96
|
+
edgeDeviceId: function ( value ) {
|
|
97
|
+
if ( !value ) return 'Cannot be empty';
|
|
98
|
+
if ( !( /^[\w\-\/.]+$/ ).test( value ) ) {
|
|
99
|
+
return `Invalid characters for MQTT topic: "${value}"`;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
nodeEnv: function ( value ) {
|
|
105
|
+
const validEnvs = [ 'development', 'production', 'test' ];
|
|
106
|
+
if ( !validEnvs.includes( value ) ) {
|
|
107
|
+
return `Must be one of ${validEnvs.join( ', ' )}, got: "${value}"`;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
mqttMsgExpiry: function ( value, originalEnv ) {
|
|
113
|
+
if ( isNaN( value ) || value <= 0 ) {
|
|
114
|
+
return `Must be positive integer, got: "${originalEnv}"`;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
positiveInt: function ( value, originalEnv ) {
|
|
120
|
+
if ( isNaN( value ) || value <= 0 ) {
|
|
121
|
+
return `Must be positive integer, got: "${originalEnv}"`;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
hostPort: function ( value ) {
|
|
127
|
+
if ( !value ) return 'Cannot be empty';
|
|
128
|
+
// Match host:port pattern (host can be hostname or IP)
|
|
129
|
+
const hostPortRegex = /^[\w.\-]+:\d+$/;
|
|
130
|
+
if ( !hostPortRegex.test( value ) ) {
|
|
131
|
+
return `Must be host:port format, got: "${value}"`;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
questdbFlushMode: function ( value ) {
|
|
137
|
+
const validModes = [ 'auto', 'manual' ];
|
|
138
|
+
if ( !validModes.includes( value ) ) {
|
|
139
|
+
return `Must be one of ${validModes.join( ', ' )}, got: "${value}"`;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
positiveIntOrUndefined: function ( value, originalEnv ) {
|
|
145
|
+
if ( value === undefined ) return null;
|
|
146
|
+
if ( isNaN( value ) || value <= 0 ) {
|
|
147
|
+
return `Must be positive integer, got: "${originalEnv}"`;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
nonNegativeInt: function ( value, originalEnv ) {
|
|
153
|
+
if ( isNaN( value ) || value < 0 ) {
|
|
154
|
+
return `Must be non-negative integer, got: "${originalEnv}"`;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
nonNegativeNumberOrInfinity: function ( value, originalEnv ) {
|
|
160
|
+
if ( isNaN( value ) || value < 0 ) {
|
|
161
|
+
return `Must be a non-negative number or Infinity, got: "${originalEnv}"`;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
nonEmptyString: function ( value ) {
|
|
167
|
+
if ( !value || value.trim() === '' ) {
|
|
168
|
+
return `Cannot be empty, got: "${value}"`;
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
mqttUrl: function ( value ) {
|
|
174
|
+
if ( !value ) return 'Cannot be empty';
|
|
175
|
+
if ( !value.startsWith( 'mqtt://' ) && !value.startsWith( 'mqtts://' ) ) {
|
|
176
|
+
return `Must start with 'mqtt://' or 'mqtts://', got: "${value}"`;
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// VALIDATION CONFIGURATION
|
|
184
|
+
// ============================================================================
|
|
185
|
+
const validationConfig = [
|
|
186
|
+
{ field: 'edgeDeviceId', validator: validators.edgeDeviceId },
|
|
187
|
+
{ field: 'nodeEnv', validator: validators.nodeEnv },
|
|
188
|
+
{ field: 'maxPartitionsAllowed', validator: validators.positiveInt, originalEnv: 'COMPOSER_MAX_PARTITIONS_ALLOWED' },
|
|
189
|
+
{ field: 'yieldTimeThresholdMs', validator: validators.nonNegativeNumberOrInfinity, originalEnv: 'YIELD_TIME_THRESHOLD_MS' },
|
|
190
|
+
{ field: 'mqttBrokerUrl', validator: validators.mqttUrl, label: 'MQTT_BROKER_URL' },
|
|
191
|
+
{ field: 'mqttMsgExpiry', validator: validators.mqttMsgExpiry, originalEnv: 'MQTT_MSG_EXPIRY' },
|
|
192
|
+
{ field: 'mqttKeepalive', validator: validators.positiveInt, originalEnv: 'MQTT_KEEPALIVE' },
|
|
193
|
+
{ field: 'mqttReconnectMs', validator: validators.positiveInt, originalEnv: 'MQTT_RECONNECT_MS' },
|
|
194
|
+
{ field: 'mqttConnectTimeoutMs', validator: validators.positiveInt, originalEnv: 'MQTT_CONNECT_TIMEOUT_MS' },
|
|
195
|
+
{ field: 'mqttConnectGraceMs', validator: validators.nonNegativeInt, originalEnv: 'MQTT_CONNECT_GRACE_MS' },
|
|
196
|
+
{ field: 'mqttSessionExpiryS', validator: validators.positiveInt, originalEnv: 'MQTT_SESSION_EXPIRY_S' },
|
|
197
|
+
{ field: 'mqttMaxQueueSize', validator: validators.positiveInt, originalEnv: 'MQTT_MAX_QUEUE_SIZE' },
|
|
198
|
+
{ field: 'mqttSourceDedupWindowMs', validator: validators.positiveInt, originalEnv: 'MQTT_SOURCE_DEDUP_WINDOW_MS' },
|
|
199
|
+
{ field: 'mqttSourceDedupMaxEntries', validator: validators.positiveInt, originalEnv: 'MQTT_SOURCE_DEDUP_MAX_ENTRIES' },
|
|
200
|
+
// QuestDB Configuration
|
|
201
|
+
{ field: 'questdbIlpUrl', validator: validators.hostPort, label: 'QUESTDB_ILP_URL' },
|
|
202
|
+
{ field: 'questdbPgUrl', validator: validators.hostPort, label: 'QUESTDB_PG_URL' },
|
|
203
|
+
{ field: 'questdbFlushMode', validator: validators.questdbFlushMode, label: 'QUESTDB_FLUSH_MODE' },
|
|
204
|
+
{ field: 'questdbIdleFlushAfterMs', validator: validators.nonNegativeInt, originalEnv: 'QUESTDB_IDLE_FLUSH_AFTER_MS' },
|
|
205
|
+
{ field: 'questdbIdleFlushCheckMs', validator: validators.positiveInt, originalEnv: 'QUESTDB_IDLE_FLUSH_CHECK_MS' },
|
|
206
|
+
{ field: 'questdbAutoFlushRows', validator: validators.positiveIntOrUndefined, originalEnv: 'QUESTDB_AUTO_FLUSH_ROWS' },
|
|
207
|
+
{ field: 'questdbAutoFlushIntervalMs', validator: validators.positiveIntOrUndefined, originalEnv: 'QUESTDB_AUTO_FLUSH_INTERVAL_MS' },
|
|
208
|
+
{ field: 'questdbMaxBufSize', validator: validators.positiveIntOrUndefined, originalEnv: 'QUESTDB_MAX_BUF_SIZE' },
|
|
209
|
+
{ field: 'questdbRetryTimeout', validator: validators.positiveIntOrUndefined, originalEnv: 'QUESTDB_RETRY_TIMEOUT' },
|
|
210
|
+
// QuestDB Credentials
|
|
211
|
+
{ field: 'questdbDatabase', validator: validators.nonEmptyString, label: 'QUESTDB_DATABASE' },
|
|
212
|
+
{ field: 'questdbUser', validator: validators.nonEmptyString, label: 'QUESTDB_USER' }
|
|
213
|
+
// questdbPassword: no validation — allows empty for passwordless auth
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
// ============================================================================
|
|
217
|
+
// VALIDATION RUNNER
|
|
218
|
+
// ============================================================================
|
|
219
|
+
const validate = function () {
|
|
220
|
+
const errors = [];
|
|
221
|
+
|
|
222
|
+
// Run each validator
|
|
223
|
+
for ( const config of validationConfig ) {
|
|
224
|
+
const value = ENV_VARS[ config.field ];
|
|
225
|
+
const originalEnv = config.originalEnv ? process.env[ config.originalEnv ] : value;
|
|
226
|
+
const label = config.label || config.field.toUpperCase().replace( /([a-z])([A-Z])/g, '$1_$2' );
|
|
227
|
+
|
|
228
|
+
const error = config.validator( value, originalEnv || value, label );
|
|
229
|
+
if ( error ) {
|
|
230
|
+
errors.push( `${label}: ${error}` );
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Fail fast with clear error message
|
|
235
|
+
if ( errors.length > 0 ) {
|
|
236
|
+
console.error( 'composer: Environment variable validation failed:' );
|
|
237
|
+
errors.forEach( ( err ) => console.error( ` - ${err}` ) );
|
|
238
|
+
process.exit( 1 ); // eslint-disable-line no-process-exit
|
|
239
|
+
}
|
|
240
|
+
}; // validate()
|
|
241
|
+
|
|
242
|
+
// Run validation immediately on import
|
|
243
|
+
validate();
|
|
244
|
+
|
|
245
|
+
export { ENV_VARS, validators };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// core/mqtt-protocol.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Shared MQTT protocol invariants for WinkComposer.
|
|
5
|
+
*
|
|
6
|
+
* These are design decisions, NOT deployment-configurable values.
|
|
7
|
+
* Both source and emitter depend on identical values for interoperability:
|
|
8
|
+
* - QoS 1 (at-least-once) is coupled to the dedup strategy
|
|
9
|
+
* - Protocol v5 is required for user properties and message expiry
|
|
10
|
+
* - WINK_NAMESPACE keys must match between publisher and subscriber
|
|
11
|
+
*
|
|
12
|
+
* Deployment-configurable MQTT settings (keepalive, reconnect, etc.)
|
|
13
|
+
* live in ENV_VARS and are consumed by each manager's constants.js.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// PROTOCOL INVARIANTS
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* MQTT v5 user property keys for WinkComposer metadata.
|
|
22
|
+
* Publisher (emitter) writes these; subscriber (source) reads them.
|
|
23
|
+
*
|
|
24
|
+
* @type {Object}
|
|
25
|
+
*/
|
|
26
|
+
export const WINK_NAMESPACE = {
|
|
27
|
+
dedupId: 'winkDedupId',
|
|
28
|
+
timestamp: 'winkTimestamp',
|
|
29
|
+
version: 'winkVersion'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* QoS level — fixed at 1 (at-least-once delivery).
|
|
34
|
+
* Duplicates handled by backend deduplication via winkDedupId UUID.
|
|
35
|
+
* Must match between emitter and source for session resumption.
|
|
36
|
+
*
|
|
37
|
+
* @type {number}
|
|
38
|
+
*/
|
|
39
|
+
export const MQTT_QOS = 1;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* MQTT protocol version — fixed at 5.
|
|
43
|
+
* Required for user properties (dedup metadata) and message expiry.
|
|
44
|
+
*
|
|
45
|
+
* @type {number}
|
|
46
|
+
*/
|
|
47
|
+
export const MQTT_PROTOCOL_V = 5;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as init } from './init.js';
|
|
2
|
+
export { default as update } from './update.js';
|
|
3
|
+
export { default as reset } from './reset.js';
|
|
4
|
+
export { default as recompute } from './recompute.js';
|
|
5
|
+
export { default as publishTo } from './publish-to.js';
|
|
6
|
+
// Re-export everything from introspection.js
|
|
7
|
+
export * from './introspect.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import validateFlow from './validate-flow.js';
|
|
2
|
+
|
|
3
|
+
const init = function ( flow ) {
|
|
4
|
+
// Fail-fast: reject malformed flow config at flow start rather than
|
|
5
|
+
// at first-message time. All three live callers (src/flow/run.js,
|
|
6
|
+
// composer-website demo-runner, benchmark-runner) pass through here.
|
|
7
|
+
validateFlow( flow );
|
|
8
|
+
|
|
9
|
+
const composerState = Object.create( null );
|
|
10
|
+
|
|
11
|
+
// Two-level structure: partitionId → specializationType → graph
|
|
12
|
+
composerState.partitionSpecializations = new Map();
|
|
13
|
+
|
|
14
|
+
composerState.flow = flow;
|
|
15
|
+
|
|
16
|
+
// Cumulative counter of partition-creation attempts (accepted or rejected).
|
|
17
|
+
// Pre-cap: equals partitionSpecializations.size. Post-cap: diverges by the
|
|
18
|
+
// rejection count. See ADR-016.
|
|
19
|
+
composerState.totalPartitionsCreated = 0;
|
|
20
|
+
|
|
21
|
+
// Yield state (ADR-024): update() sets `yieldPending` when the time
|
|
22
|
+
// threshold is crossed; the flow runtime clears it and takes the
|
|
23
|
+
// event-loop breath after running the message's pipeline.
|
|
24
|
+
composerState.partitionState = Object.create( null );
|
|
25
|
+
composerState.partitionState.lastYield = Date.now();
|
|
26
|
+
composerState.partitionState.yieldPending = false;
|
|
27
|
+
|
|
28
|
+
// Registry of sinks that expose getPressure(). Populated by `flow/run.js`
|
|
29
|
+
// immediately after this init returns, by querying the wire-emitters and
|
|
30
|
+
// wire-storages registries. The pressure-aware yield decision (ADR-020,
|
|
31
|
+
// Draft) will read this registry as one of its yield conditions when it
|
|
32
|
+
// lands; until then the registry is forward-compat plumbing.
|
|
33
|
+
//
|
|
34
|
+
// Object.create(null) keyed by 'emitter:<target>' / 'storage:<storageName>'
|
|
35
|
+
// — a runtime-string-keyed registry per the codebase convention. Keys are
|
|
36
|
+
// self-describing in debug logs ("which sink crossed the threshold?"
|
|
37
|
+
// becomes a one-glance answer); for-in iteration over Object.create(null)
|
|
38
|
+
// is allocation-free on the amortized check path.
|
|
39
|
+
composerState.partitionState.backpressureAwareSinks = Object.create( null );
|
|
40
|
+
|
|
41
|
+
return composerState;
|
|
42
|
+
}; // init()
|
|
43
|
+
|
|
44
|
+
export default init;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Single source of truth for what this plugin can do */
|
|
2
|
+
const SUPPORTED_STATS = [
|
|
3
|
+
];
|
|
4
|
+
|
|
5
|
+
/** Human-readable descriptions (for introspection) */
|
|
6
|
+
const STAT_DESCRIPTIONS = {
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/** The type of this node */
|
|
10
|
+
const NODE_TYPE = 'Partition Manager';
|
|
11
|
+
|
|
12
|
+
// Attach static metadata once, from our constants by making a shallow copy.
|
|
13
|
+
export const getSupportedStats = () => SUPPORTED_STATS.slice();
|
|
14
|
+
export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
|
|
15
|
+
export const getNodeType = () => NODE_TYPE.slice();
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import nodeTypeToModule from '../wiring/node-type-to-module.js';
|
|
2
|
+
|
|
3
|
+
import { ENV_VARS } from '../env-vars.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This function transforms trigger specifications that reference nodes by name
|
|
7
|
+
* into executable triggers with actual function references and state objects.
|
|
8
|
+
*
|
|
9
|
+
* Key Design Drivers:
|
|
10
|
+
* - All targets in a single trigger must be homogeneous (same node type)
|
|
11
|
+
* - Control methods are resolved from the target node type
|
|
12
|
+
* - Target resolution uses spec names for maintainable transpilation
|
|
13
|
+
* - Comprehensive validation prevents runtime errors
|
|
14
|
+
*
|
|
15
|
+
* @param {Array} stateStore - Initialized state store for target state resolution
|
|
16
|
+
* @param {Array<Object>} triggers - Trigger specifications with named targets
|
|
17
|
+
* @param {Object} scopedFlow - Specialization-scoped flow data containing specs and nodeModules
|
|
18
|
+
* @returns {Array<Object>} Resolved triggers with function and state references
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const triggers = [
|
|
22
|
+
* { control: 'reset', targets: [ 'fastEWMA', 'slowEWMA' ] }
|
|
23
|
+
* ];
|
|
24
|
+
* const resolved = resolveTriggers( stateStore, triggers, scopedFlow );
|
|
25
|
+
*/
|
|
26
|
+
const resolveTriggers = function ( stateStore, triggers, scopedFlow ) {
|
|
27
|
+
// Handle empty or undefined triggers gracefully
|
|
28
|
+
if ( triggers === undefined || triggers.length === 0 ) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Pre-allocate for performance
|
|
33
|
+
const resolvedTriggers = new Array( triggers.length );
|
|
34
|
+
|
|
35
|
+
for ( let i = 0; i < triggers.length; i += 1 ) {
|
|
36
|
+
const trigger = triggers[ i ];
|
|
37
|
+
const targets = trigger.targets;
|
|
38
|
+
const control = trigger.control;
|
|
39
|
+
|
|
40
|
+
// Initialize resolved trigger object
|
|
41
|
+
const resolvedTrigger = Object.create( null );
|
|
42
|
+
const resolvedTargets = new Array( targets.length );
|
|
43
|
+
|
|
44
|
+
// Resolve first target to establish reference node type
|
|
45
|
+
const firstTargetName = targets[ 0 ];
|
|
46
|
+
if ( typeof firstTargetName !== 'string' || firstTargetName.length === 0 ) {
|
|
47
|
+
throw new TypeError( `Target name at trigger ${i}, target 0 must be a non-empty string; found '${firstTargetName}'` );
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const firstTargetIndex = scopedFlow.specs.findIndex( ( spec ) => spec.name === firstTargetName );
|
|
51
|
+
if ( firstTargetIndex === -1 ) {
|
|
52
|
+
throw new Error( `Target node '${firstTargetName}' not found in flow specs for trigger ${i}` );
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Get reference node module and validate control method exists
|
|
56
|
+
const firstSpec = scopedFlow.specs[ firstTargetIndex ];
|
|
57
|
+
const moduleName = nodeTypeToModule( firstSpec.nodeType );
|
|
58
|
+
const referenceNode = scopedFlow.nodeModules[ moduleName ];
|
|
59
|
+
|
|
60
|
+
if ( !referenceNode ) {
|
|
61
|
+
throw new Error( `Node module '${moduleName}' not found for nodeType '${firstSpec.nodeType}' in trigger ${i}` );
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ( typeof referenceNode[ control ] !== 'function' ) {
|
|
65
|
+
throw new Error( `Control method '${control}' not found on node type '${referenceNode.getNodeType?.() || firstSpec.nodeType}' for trigger ${i}` );
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Store first resolved target
|
|
69
|
+
resolvedTargets[ 0 ] = stateStore[ firstTargetIndex ];
|
|
70
|
+
|
|
71
|
+
// Resolve remaining targets and validate homogeneity
|
|
72
|
+
for ( let j = 1; j < targets.length; j += 1 ) {
|
|
73
|
+
const targetName = targets[ j ];
|
|
74
|
+
|
|
75
|
+
if ( typeof targetName !== 'string' || targetName.length === 0 ) {
|
|
76
|
+
throw new TypeError( `Target name at trigger ${i}, target ${j} must be a non-empty string; found '${targetName}'` );
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const targetIndex = scopedFlow.specs.findIndex( ( spec ) => spec.name === targetName );
|
|
80
|
+
if ( targetIndex === -1 ) {
|
|
81
|
+
throw new Error( `Target node '${targetName}' not found in flow specs for trigger ${i}, target ${j}` );
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Ensure all targets are homogeneous (same node type)
|
|
85
|
+
const targetSpec = scopedFlow.specs[ targetIndex ];
|
|
86
|
+
const targetModuleName = nodeTypeToModule( targetSpec.nodeType );
|
|
87
|
+
const targetNode = scopedFlow.nodeModules[ targetModuleName ];
|
|
88
|
+
|
|
89
|
+
if ( targetNode !== referenceNode ) {
|
|
90
|
+
const refNodeType = referenceNode.getNodeType?.() || firstSpec.nodeType;
|
|
91
|
+
const targetNodeType = targetNode.getNodeType?.() || targetSpec.nodeType;
|
|
92
|
+
throw new Error( `Target node '${targetName}' (type: ${targetNodeType}) is incompatible with reference node type '${refNodeType}' for trigger ${i}` );
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
resolvedTargets[ j ] = stateStore[ targetIndex ];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Store resolved trigger with function reference and state objects
|
|
99
|
+
resolvedTrigger.control = referenceNode[ control ];
|
|
100
|
+
resolvedTrigger.targets = resolvedTargets;
|
|
101
|
+
resolvedTriggers[ i ] = resolvedTrigger;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return resolvedTriggers;
|
|
105
|
+
}; // resolveTriggers()
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Partition state manager's update with integrated trigger resolution and specialization support.
|
|
109
|
+
* Resolved triggers are co-located with each node's state for zero hot-path overhead.
|
|
110
|
+
*
|
|
111
|
+
* Uses two-level lookup: partitionId → specializationType → graph
|
|
112
|
+
* Zero allocation in hot path: direct field access with default to 0.
|
|
113
|
+
*
|
|
114
|
+
* @param {Object} composerState - Contains partitionSpecializations and flow specification
|
|
115
|
+
* @param {Object} message - Input message for partition and specialization extraction
|
|
116
|
+
* @returns {Array|null} Graph (node state array) with resolved triggers, or null if specialization unknown
|
|
117
|
+
*/
|
|
118
|
+
const update = function ( composerState, msg ) {
|
|
119
|
+
const { partitionSpecializations, flow, partitionState } = composerState;
|
|
120
|
+
|
|
121
|
+
// Zero allocation: direct field access, default to 0
|
|
122
|
+
const partitionId = flow.partitionField ?
|
|
123
|
+
msg[ flow.partitionField ] :
|
|
124
|
+
0;
|
|
125
|
+
|
|
126
|
+
const specializationType = flow.specializationField ?
|
|
127
|
+
msg[ flow.specializationField ] :
|
|
128
|
+
0;
|
|
129
|
+
|
|
130
|
+
// Level 1: Partition lookup
|
|
131
|
+
let specializedGraphs = partitionSpecializations.get( partitionId );
|
|
132
|
+
|
|
133
|
+
// Level 2: Specialization lookup (graph may exist or need creation)
|
|
134
|
+
let graph = specializedGraphs ? specializedGraphs[ specializationType ] : undefined;
|
|
135
|
+
|
|
136
|
+
if ( !graph ) {
|
|
137
|
+
const specs = flow.specsBySpecialization[ specializationType ];
|
|
138
|
+
|
|
139
|
+
if ( !specs ) {
|
|
140
|
+
// Log error and skip message - no default fallback
|
|
141
|
+
// Note: Do NOT create partition entry for unknown specialization
|
|
142
|
+
console.error( `[PartitionManager] Unknown specialization '${specializationType}' for partition '${partitionId}'. Message dropped.` );
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Create partition entry only after validating specialization exists.
|
|
147
|
+
// Cap check (ADR-016): enforced here — the sole site where
|
|
148
|
+
// partitionSpecializations.size increases. Increment-first,
|
|
149
|
+
// strict-`>` comparison: the Nth partition succeeds, the (N+1)th is
|
|
150
|
+
// rejected with a console.error mirroring the unknown-specialization
|
|
151
|
+
// drop pattern above.
|
|
152
|
+
if ( !specializedGraphs ) {
|
|
153
|
+
composerState.totalPartitionsCreated += 1;
|
|
154
|
+
if ( composerState.totalPartitionsCreated > ENV_VARS.maxPartitionsAllowed ) {
|
|
155
|
+
console.error(
|
|
156
|
+
`[PartitionManager] Partition creation for assetId '${partitionId}' ` +
|
|
157
|
+
`failed — maxPartitionsAllowed (${ENV_VARS.maxPartitionsAllowed}) reached. ` +
|
|
158
|
+
'Message dropped.'
|
|
159
|
+
);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
specializedGraphs = Object.create( null );
|
|
163
|
+
partitionSpecializations.set( partitionId, specializedGraphs );
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Create state array sized for this specialization
|
|
167
|
+
graph = new Array( specs.length );
|
|
168
|
+
|
|
169
|
+
for ( let k = 0; k < specs.length; k += 1 ) {
|
|
170
|
+
const spec = specs[ k ];
|
|
171
|
+
|
|
172
|
+
// Look up node module using nodeTypeToModule
|
|
173
|
+
const moduleName = nodeTypeToModule( spec.nodeType );
|
|
174
|
+
const node = flow.nodeModules[ moduleName ];
|
|
175
|
+
|
|
176
|
+
if ( !node ) {
|
|
177
|
+
throw new Error( `Node module '${moduleName}' not found for nodeType '${spec.nodeType}' at index ${k}` );
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Initialize node state
|
|
181
|
+
graph[ k ] = node.init( spec );
|
|
182
|
+
|
|
183
|
+
// ====================================================================
|
|
184
|
+
// MQTT TOPIC INJECTION IN EMIT IF NODE
|
|
185
|
+
// ====================================================================
|
|
186
|
+
// Precompute partition-specific MQTT topic at initialization to avoid
|
|
187
|
+
// reconstruction on every emission (performance optimization).
|
|
188
|
+
//
|
|
189
|
+
// Topic structure: edgeDeviceId/partitionId/specialization/insightType
|
|
190
|
+
// Example: "edge-001/sensor-42/temperature/changeDetected"
|
|
191
|
+
//
|
|
192
|
+
// The edgeDeviceId can encode ISA-95 hierarchy for Unified Namespace:
|
|
193
|
+
// - Simple: "edge-001"
|
|
194
|
+
// - UNS: "northwind/riverton/plant2/line3" (enterprise/site/area/cell)
|
|
195
|
+
//
|
|
196
|
+
// Enables powerful MQTT subscription patterns:
|
|
197
|
+
// - All from edge: "edge-001/#" or "northwind/riverton/#"
|
|
198
|
+
// - Specific sensor: "+/sensor-42/#"
|
|
199
|
+
if ( spec.nodeType === 'Emit If' && ( graph[ k ].target === 'mqtt' || graph[ k ].target === 'terminal' ) ) {
|
|
200
|
+
graph[ k ].topic = `${ENV_VARS.edgeDeviceId}/${partitionId}/${specializationType}/${graph[ k ].insightType}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ====================================================================
|
|
204
|
+
// STORAGE AND PARTITION INJECTION IN PERSIST IF NODE
|
|
205
|
+
// ====================================================================
|
|
206
|
+
// Inject the storage singleton and partition ID at initialization.
|
|
207
|
+
// Storage reference is set during wiring (spec.storage), but partition
|
|
208
|
+
// ID is partition-specific and must be injected here.
|
|
209
|
+
//
|
|
210
|
+
// Zero hot-path overhead: All references are pre-resolved.
|
|
211
|
+
if ( spec.nodeType === 'Persist If' && spec.storage ) {
|
|
212
|
+
graph[ k ].storage = spec.storage;
|
|
213
|
+
graph[ k ].partitionId = partitionId;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Resolve triggers with specialization-scoped data
|
|
217
|
+
graph[ k ].resolvedTriggers = resolveTriggers( graph, spec.triggers, {
|
|
218
|
+
specs: specs,
|
|
219
|
+
nodeModules: flow.nodeModules
|
|
220
|
+
});
|
|
221
|
+
} // for ( let k = 0; k < specs.length; k += 1 )
|
|
222
|
+
|
|
223
|
+
// Special handling for Controller nodes with nested triggers
|
|
224
|
+
// Only controller node allows FORWARD node control.
|
|
225
|
+
// That is why it has to be performed only after all nodes have
|
|
226
|
+
// been initialized.
|
|
227
|
+
for ( let k = 0; k < specs.length; k += 1 ) {
|
|
228
|
+
const spec = specs[ k ];
|
|
229
|
+
if ( spec.nodeType === 'Controller' && graph[ k ].logic ) {
|
|
230
|
+
// Resolve triggers for each condition in the logic array
|
|
231
|
+
for ( let j = 0; j < graph[ k ].logic.length; j += 1 ) {
|
|
232
|
+
graph[ k ].logic[ j ].resolvedTriggers = resolveTriggers(
|
|
233
|
+
graph,
|
|
234
|
+
graph[ k ].logic[ j ].triggers,
|
|
235
|
+
{
|
|
236
|
+
specs: specs,
|
|
237
|
+
nodeModules: flow.nodeModules
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
// No top-level resolvedTriggers for controller
|
|
242
|
+
graph[ k ].resolvedTriggers = null;
|
|
243
|
+
}
|
|
244
|
+
} // for ( let k = 0; k < specs.length; k += 1 )
|
|
245
|
+
|
|
246
|
+
specializedGraphs[ specializationType ] = graph;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Yield decision (ADR-024, process-then-breathe): when the threshold is
|
|
250
|
+
// crossed, ask the caller to give the event loop a turn AFTER it has run
|
|
251
|
+
// this message's pipeline. The graph always goes back synchronously —
|
|
252
|
+
// returning a Promise here (the pre-ADR-024 shape) deferred this
|
|
253
|
+
// message's processing past later ones, reordering updates within a
|
|
254
|
+
// partition for callers that do not await. flow/run.js owns clearing
|
|
255
|
+
// the flag and taking the breath.
|
|
256
|
+
if ( Date.now() - partitionState.lastYield > flow.yieldThreshold ) {
|
|
257
|
+
partitionState.lastYield = Date.now();
|
|
258
|
+
partitionState.yieldPending = true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return graph;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export default update;
|