@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,226 @@
|
|
|
1
|
+
// core/source-manager/test-harness/comparator.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Check tool for testHarness.
|
|
5
|
+
*
|
|
6
|
+
* Takes the messages the harness generated (the ground truth) and
|
|
7
|
+
* one capture per sink (terminal stdout, MQTT subscriber messages,
|
|
8
|
+
* QuestDB rows) and checks each sink's output against the harness
|
|
9
|
+
* input, matched on `_harnessId`.
|
|
10
|
+
*
|
|
11
|
+
* Why ground-truth-driven: the harness deterministically produced a
|
|
12
|
+
* known sequence of messages. The "correct" output for any sink is
|
|
13
|
+
* exactly that sequence. Comparing sinks to each other only catches
|
|
14
|
+
* alignment drift; comparing each sink to the harness input also
|
|
15
|
+
* catches a whole pipeline going wrong in the same way (a uniform
|
|
16
|
+
* error every sink shares).
|
|
17
|
+
*
|
|
18
|
+
* Per-message rules:
|
|
19
|
+
* - Every checked sink must have the message (matched by _harnessId).
|
|
20
|
+
* - For each column declared in the asset class:
|
|
21
|
+
* - float64 with declared resolution: the sink's value must
|
|
22
|
+
* round to the same grid step as the harness input.
|
|
23
|
+
* - other numerics, strings, bools, timestamps: exact match.
|
|
24
|
+
* - Fuzz messages (those carrying `_harnessFuzzPattern`) skip the
|
|
25
|
+
* value check — fuzz inputs legitimately produce different sink
|
|
26
|
+
* outputs (NaN becomes "NaN" in terminal, null in MQTT JSON,
|
|
27
|
+
* dropped in QDB). The check is "every sink saw it" only.
|
|
28
|
+
*
|
|
29
|
+
* The report:
|
|
30
|
+
* {
|
|
31
|
+
* ok: boolean,
|
|
32
|
+
* errors: [ '<diagnostic line>', ... ],
|
|
33
|
+
* summary: {
|
|
34
|
+
* messageCount,
|
|
35
|
+
* sinkCounts: { terminal, mqtt, qdb }
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* Error strings name the harnessId, the column, the harness input,
|
|
40
|
+
* the sink's observed value, and the declared resolution when it
|
|
41
|
+
* applies.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const HARNESS_ID_FIELD = '_harnessId';
|
|
45
|
+
const FUZZ_MARKER_FIELD = '_harnessFuzzPattern';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Walks captured terminal stdout and pulls out each message that
|
|
49
|
+
* was printed in pretty-JSON form (the terminal emitter's
|
|
50
|
+
* `verbose: true` output).
|
|
51
|
+
*
|
|
52
|
+
* Relies on a flat-message layout: each top-level `{` and matching
|
|
53
|
+
* `}` sit at column 0; everything in between is indented. We
|
|
54
|
+
* collect lines between those bookends and JSON.parse the result.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} text - Captured stdout buffer
|
|
57
|
+
* @returns {Object[]} Parsed messages in the order they appeared
|
|
58
|
+
*/
|
|
59
|
+
export const parseTerminalOutput = function ( text ) {
|
|
60
|
+
const messages = [];
|
|
61
|
+
let buffer = null;
|
|
62
|
+
|
|
63
|
+
const lines = text.split( '\n' );
|
|
64
|
+
for ( let i = 0; i < lines.length; i += 1 ) {
|
|
65
|
+
const line = lines[ i ];
|
|
66
|
+
if ( line === '{' ) {
|
|
67
|
+
buffer = `${line}\n`;
|
|
68
|
+
} else if ( line === '}' && buffer !== null ) {
|
|
69
|
+
buffer += line;
|
|
70
|
+
messages.push( JSON.parse( buffer ) );
|
|
71
|
+
buffer = null;
|
|
72
|
+
} else if ( buffer !== null ) {
|
|
73
|
+
buffer += `${line}\n`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return messages;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns an array indexed by message harness id. Each entry is the
|
|
82
|
+
* parsed message; missing ids show up as `undefined`. The id field
|
|
83
|
+
* is fixed (`_harnessId`) — the harness always adds it.
|
|
84
|
+
*
|
|
85
|
+
* @param {Object[]} messages - Parsed messages from one sink
|
|
86
|
+
* @returns {Map<number, Object>}
|
|
87
|
+
*/
|
|
88
|
+
export const indexById = function ( messages ) {
|
|
89
|
+
const map = new Map();
|
|
90
|
+
for ( const msg of messages ) {
|
|
91
|
+
const id = msg[ HARNESS_ID_FIELD ];
|
|
92
|
+
if ( id !== undefined && id !== null ) {
|
|
93
|
+
map.set( id, msg );
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return map;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Tells whether a column should be compared with resolution-aware
|
|
101
|
+
* tolerance. Only float64 columns with a declared resolution use
|
|
102
|
+
* the grid-snap check; everything else uses exact equality.
|
|
103
|
+
*/
|
|
104
|
+
const usesResolutionTolerance = function ( columnSpec ) {
|
|
105
|
+
return columnSpec && columnSpec.type === 'float64' && typeof columnSpec.resolution === 'number';
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* For two float values that should sit on the same resolution grid,
|
|
110
|
+
* tells whether they round to the same grid step. Equivalent to
|
|
111
|
+
* "within half a resolution step of each other".
|
|
112
|
+
*/
|
|
113
|
+
const onSameGrid = function ( a, b, resolution ) {
|
|
114
|
+
if ( typeof a !== 'number' || typeof b !== 'number' ) return false;
|
|
115
|
+
if ( Number.isNaN( a ) || Number.isNaN( b ) ) return Number.isNaN( a ) === Number.isNaN( b );
|
|
116
|
+
return Math.round( a / resolution ) === Math.round( b / resolution );
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Returns true when the sink's value matches the harness's value,
|
|
121
|
+
* applying resolution tolerance for float64 columns that declare it.
|
|
122
|
+
*/
|
|
123
|
+
const valuesAgree = function ( harnessValue, sinkValue, columnSpec ) {
|
|
124
|
+
if ( usesResolutionTolerance( columnSpec ) ) {
|
|
125
|
+
return onSameGrid( harnessValue, sinkValue, columnSpec.resolution );
|
|
126
|
+
}
|
|
127
|
+
if ( Number.isNaN( harnessValue ) || Number.isNaN( sinkValue ) ) {
|
|
128
|
+
return Number.isNaN( harnessValue ) === Number.isNaN( sinkValue );
|
|
129
|
+
}
|
|
130
|
+
return harnessValue === sinkValue;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Builds a diagnostic line for one column where a sink's value
|
|
135
|
+
* disagrees with the harness input.
|
|
136
|
+
*/
|
|
137
|
+
const formatColumnError = function ( harnessId, sinkName, columnName, columnSpec, harnessValue, sinkValue ) {
|
|
138
|
+
const head = `harnessId=${harnessId}, column '${columnName}' — harness sent ${JSON.stringify( harnessValue )}, ${sinkName} saw ${JSON.stringify( sinkValue )}`;
|
|
139
|
+
if ( usesResolutionTolerance( columnSpec ) ) {
|
|
140
|
+
return `${head}, declared resolution: ${columnSpec.resolution} — beyond resolution`;
|
|
141
|
+
}
|
|
142
|
+
return `${head} — values differ`;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Compares one sink's captured messages against the harness inputs,
|
|
147
|
+
* column by column. Returns the error strings for this sink.
|
|
148
|
+
*/
|
|
149
|
+
const checkSinkAgainstInputs = function ( sinkName, sinkIndex, harnessIndex, columnNames, columns ) {
|
|
150
|
+
const errors = [];
|
|
151
|
+
for ( const [ harnessId, harnessMsg ] of harnessIndex.entries() ) {
|
|
152
|
+
const sinkMsg = sinkIndex.get( harnessId );
|
|
153
|
+
if ( !sinkMsg ) {
|
|
154
|
+
errors.push( `harnessId=${harnessId} missing from ${sinkName}` );
|
|
155
|
+
continue; // eslint-disable-line no-continue
|
|
156
|
+
}
|
|
157
|
+
// Fuzz messages skip the column-by-column check — presence
|
|
158
|
+
// alone is the fuzz assertion.
|
|
159
|
+
if ( harnessMsg[ FUZZ_MARKER_FIELD ] !== undefined ) {
|
|
160
|
+
continue; // eslint-disable-line no-continue
|
|
161
|
+
}
|
|
162
|
+
for ( const columnName of columnNames ) {
|
|
163
|
+
if ( columnName === HARNESS_ID_FIELD ) continue; // eslint-disable-line no-continue
|
|
164
|
+
const harnessValue = harnessMsg[ columnName ];
|
|
165
|
+
const sinkValue = sinkMsg[ columnName ];
|
|
166
|
+
if ( !valuesAgree( harnessValue, sinkValue, columns[ columnName ] ) ) {
|
|
167
|
+
errors.push( formatColumnError( harnessId, sinkName, columnName, columns[ columnName ], harnessValue, sinkValue ) );
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return errors;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Checks each captured sink against the harness inputs (the ground
|
|
176
|
+
* truth). Skips per-column value checks for fuzz messages — those
|
|
177
|
+
* legitimately produce different sink outputs.
|
|
178
|
+
*
|
|
179
|
+
* @param {Object[]} harnessInputs - Messages the harness produced (ground truth)
|
|
180
|
+
* @param {Object} captures
|
|
181
|
+
* @param {string} [captures.terminal] - Raw stdout buffer
|
|
182
|
+
* @param {Object[]} [captures.mqtt] - Messages received over MQTT
|
|
183
|
+
* @param {Object[]} [captures.qdb] - Rows queried from QuestDB
|
|
184
|
+
* @param {Object} assetClass - Declares column types and resolutions
|
|
185
|
+
* @returns {{ok: boolean, errors: string[], summary: Object}}
|
|
186
|
+
*/
|
|
187
|
+
export const compareCaptures = function ( harnessInputs, captures, assetClass ) {
|
|
188
|
+
const harnessIndex = indexById( harnessInputs || [] );
|
|
189
|
+
|
|
190
|
+
const sinkIndices = {};
|
|
191
|
+
const sinkCounts = {};
|
|
192
|
+
|
|
193
|
+
if ( captures.terminal !== undefined ) {
|
|
194
|
+
const parsed = parseTerminalOutput( captures.terminal );
|
|
195
|
+
sinkIndices.terminal = indexById( parsed );
|
|
196
|
+
sinkCounts.terminal = parsed.length;
|
|
197
|
+
}
|
|
198
|
+
if ( captures.mqtt !== undefined ) {
|
|
199
|
+
sinkIndices.mqtt = indexById( captures.mqtt );
|
|
200
|
+
sinkCounts.mqtt = captures.mqtt.length;
|
|
201
|
+
}
|
|
202
|
+
if ( captures.qdb !== undefined ) {
|
|
203
|
+
sinkIndices.qdb = indexById( captures.qdb );
|
|
204
|
+
sinkCounts.qdb = captures.qdb.length;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const columnNames = ( assetClass && assetClass.columns ) ? Object.keys( assetClass.columns ) : [];
|
|
208
|
+
const columns = ( assetClass && assetClass.columns ) ? assetClass.columns : {};
|
|
209
|
+
|
|
210
|
+
const errors = [];
|
|
211
|
+
for ( const sinkName of Object.keys( sinkIndices ) ) {
|
|
212
|
+
const sinkErrors = checkSinkAgainstInputs(
|
|
213
|
+
sinkName, sinkIndices[ sinkName ], harnessIndex, columnNames, columns
|
|
214
|
+
);
|
|
215
|
+
errors.push( ...sinkErrors );
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
ok: errors.length === 0,
|
|
220
|
+
errors,
|
|
221
|
+
summary: {
|
|
222
|
+
messageCount: harnessIndex.size,
|
|
223
|
+
sinkCounts
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// core/source-manager/test-harness/field-generator.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Generates one value for one field of a generated message.
|
|
5
|
+
*
|
|
6
|
+
* Each call returns a value of the field's declared type, drawn from
|
|
7
|
+
* the random generator (or built from message index for timestamps).
|
|
8
|
+
* Float values snap to the declared resolution so the harness's output
|
|
9
|
+
* matches what real sensors would produce — and what QuestDB will
|
|
10
|
+
* store after its own quantization.
|
|
11
|
+
*
|
|
12
|
+
* The field spec shape (`type`, `range`, `resolution`, `values`) is
|
|
13
|
+
* enforced at startup by `validate.js` (FIELD_SPEC_SCHEMA).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const generateFloat = function ( spec, prng ) {
|
|
17
|
+
const range = spec.range || [ 0, 1 ];
|
|
18
|
+
const raw = prng.floatInRange( range[ 0 ], range[ 1 ] );
|
|
19
|
+
if ( spec.resolution ) {
|
|
20
|
+
// Snap to grid. Sensors quantize, so generated data should
|
|
21
|
+
// too — otherwise the cross-sink check fails on values that
|
|
22
|
+
// QuestDB rounds and terminal does not.
|
|
23
|
+
return Math.round( raw / spec.resolution ) * spec.resolution;
|
|
24
|
+
}
|
|
25
|
+
return raw;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const generateInt = function ( spec, prng ) {
|
|
29
|
+
const range = spec.range || [ 0, 100 ];
|
|
30
|
+
return prng.intInRange( range[ 0 ], range[ 1 ] );
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const generateBool = function ( spec, prng ) {
|
|
34
|
+
if ( spec.values ) {
|
|
35
|
+
return prng.pickFrom( spec.values );
|
|
36
|
+
}
|
|
37
|
+
return prng.next() < 0.5;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const generateString = function ( spec, prng ) {
|
|
41
|
+
// Strings always need an explicit `values` list — validate.js
|
|
42
|
+
// enforces this at startup.
|
|
43
|
+
return prng.pickFrom( spec.values );
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const generateTimestamp = function ( spec, msgIndex, intervalMs ) {
|
|
47
|
+
if ( spec.mode === 'static' ) {
|
|
48
|
+
return spec.seedValue || 0;
|
|
49
|
+
}
|
|
50
|
+
// monotonic-ms (default): timestamps go up by the message
|
|
51
|
+
// gap, or 1ms per message when the harness is running flat
|
|
52
|
+
// out (intervalMs = 0). First message gets the seed value.
|
|
53
|
+
const start = spec.seedValue || 0;
|
|
54
|
+
const step = ( intervalMs > 0 ) ? intervalMs : 1;
|
|
55
|
+
return start + ( ( msgIndex - 1 ) * step );
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Returns one generated value for the given field spec.
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} fieldSpec - One entry from messageTemplate.fields
|
|
62
|
+
* @param {Object} prng - The random generator (from prng.js)
|
|
63
|
+
* @param {number} msgIndex - 1-based index of the current message
|
|
64
|
+
* @param {number} intervalMs - Gap between messages; used as the
|
|
65
|
+
* timestamp step for monotonic-ms mode
|
|
66
|
+
* @returns {*} A value of the declared type
|
|
67
|
+
*/
|
|
68
|
+
export const generateField = function ( fieldSpec, prng, msgIndex, intervalMs ) {
|
|
69
|
+
switch ( fieldSpec.type ) {
|
|
70
|
+
case 'float64':
|
|
71
|
+
return generateFloat( fieldSpec, prng );
|
|
72
|
+
case 'int64':
|
|
73
|
+
return generateInt( fieldSpec, prng );
|
|
74
|
+
case 'bool':
|
|
75
|
+
return generateBool( fieldSpec, prng );
|
|
76
|
+
case 'string':
|
|
77
|
+
return generateString( fieldSpec, prng );
|
|
78
|
+
case 'timestamp':
|
|
79
|
+
return generateTimestamp( fieldSpec, msgIndex, intervalMs );
|
|
80
|
+
default:
|
|
81
|
+
// Unreachable in practice — validate.js rejects unknown
|
|
82
|
+
// types at startup. Kept for clarity at the boundary.
|
|
83
|
+
throw new Error( `testHarness: unknown field type "${fieldSpec.type}"` );
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// core/source-manager/test-harness/fuzz.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Fuzz patterns for testHarness.
|
|
5
|
+
*
|
|
6
|
+
* When a test enables fuzz, the harness injects one bad value into
|
|
7
|
+
* the target column every Nth message. The patterns below are the
|
|
8
|
+
* minimal set we agreed on — common ways production data goes wrong
|
|
9
|
+
* (null, NaN, JS quirks). For each fuzz message, the harness picks
|
|
10
|
+
* one pattern in rotation, so over the run every pattern gets
|
|
11
|
+
* exercised the same number of times.
|
|
12
|
+
*
|
|
13
|
+
* If the chosen pattern does not make sense for the target field's
|
|
14
|
+
* type (for example, NaN on a string field), we fall back to null —
|
|
15
|
+
* always valid, always a useful "bad value" — so the rotation
|
|
16
|
+
* still progresses.
|
|
17
|
+
*
|
|
18
|
+
* Both the pattern set and the null fallback were settled as design
|
|
19
|
+
* decisions on 2026-04-29.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Names of the six fuzz patterns, in rotation order.
|
|
24
|
+
* @type {string[]}
|
|
25
|
+
*/
|
|
26
|
+
export const FUZZ_PATTERN_NAMES = [
|
|
27
|
+
'null',
|
|
28
|
+
'NaN',
|
|
29
|
+
'string-where-number',
|
|
30
|
+
'undefined',
|
|
31
|
+
'infinity',
|
|
32
|
+
'empty-string'
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const FUZZ_VALUES = {
|
|
36
|
+
'null': null,
|
|
37
|
+
'NaN': NaN,
|
|
38
|
+
'string-where-number': 'not-a-number',
|
|
39
|
+
'undefined': undefined,
|
|
40
|
+
'infinity': Infinity,
|
|
41
|
+
'empty-string': ''
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Tells whether a given fuzz pattern makes sense for a given
|
|
46
|
+
* declared field type. Patterns that do not match the type fall
|
|
47
|
+
* back to null at injection time.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} patternName
|
|
50
|
+
* @param {string} fieldType - 'float64' | 'int64' | 'bool' | 'string' | 'timestamp'
|
|
51
|
+
* @returns {boolean}
|
|
52
|
+
*/
|
|
53
|
+
export const isCompatible = function ( patternName, fieldType ) {
|
|
54
|
+
switch ( patternName ) {
|
|
55
|
+
case 'null':
|
|
56
|
+
case 'undefined':
|
|
57
|
+
return true;
|
|
58
|
+
case 'NaN':
|
|
59
|
+
case 'infinity':
|
|
60
|
+
return fieldType === 'float64';
|
|
61
|
+
case 'string-where-number':
|
|
62
|
+
return fieldType === 'float64' || fieldType === 'int64';
|
|
63
|
+
case 'empty-string':
|
|
64
|
+
return fieldType === 'string';
|
|
65
|
+
default:
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Replaces the target column on the message with a fuzz value, and
|
|
72
|
+
* adds a `_harnessFuzzPattern` field naming the pattern that was
|
|
73
|
+
* applied. Mutates `msg` in place.
|
|
74
|
+
*
|
|
75
|
+
* @param {Object} msg - The message being built
|
|
76
|
+
* @param {string} fuzzTarget - Name of the column to fuzz
|
|
77
|
+
* @param {Object} targetSpec - The target column's spec from messageTemplate.fields
|
|
78
|
+
* @param {number} patternIndex - Index into FUZZ_PATTERN_NAMES (rotates)
|
|
79
|
+
*/
|
|
80
|
+
export const applyFuzz = function ( msg, fuzzTarget, targetSpec, patternIndex ) {
|
|
81
|
+
const patternName = FUZZ_PATTERN_NAMES[ patternIndex ];
|
|
82
|
+
const compatible = isCompatible( patternName, targetSpec.type );
|
|
83
|
+
const value = compatible ? FUZZ_VALUES[ patternName ] : null;
|
|
84
|
+
|
|
85
|
+
msg[ fuzzTarget ] = value;
|
|
86
|
+
msg._harnessFuzzPattern = patternName; // eslint-disable-line no-underscore-dangle
|
|
87
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// core/source-manager/test-harness/index.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview testHarness Source Adapter.
|
|
5
|
+
*
|
|
6
|
+
* A synthetic source used by integration tests to push messages
|
|
7
|
+
* through every sink and check that all sinks agree. The harness
|
|
8
|
+
* does not read from a file or a network — it invents messages on
|
|
9
|
+
* demand from a test-author-supplied `messageTemplate`.
|
|
10
|
+
*
|
|
11
|
+
* Each generated message carries a running number `_harnessId` so
|
|
12
|
+
* the cross-sink check tool can match the same message in every
|
|
13
|
+
* sink, regardless of arrival order.
|
|
14
|
+
*
|
|
15
|
+
* Built native to ADR-018's source role — same
|
|
16
|
+
* `stopFn({ timeout })` shape as CSV and MQTT sources, structured
|
|
17
|
+
* `onStatus` lifecycle phases, classified `err.code` on setup
|
|
18
|
+
* throws (`INVALID_CONFIG`). Module exports follow the contract: `id`,
|
|
19
|
+
* `configSchema`, `start`, `durabilityClass`, and the default
|
|
20
|
+
* aggregate referencing the same constants.
|
|
21
|
+
*
|
|
22
|
+
* Durability (ADR-018): `'best-effort'` — the harness keeps no recovery
|
|
23
|
+
* state; a crashed run is simply re-generated (same seed, same
|
|
24
|
+
* messages).
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* import { flow, testHarness } from '@winkjs/composer';
|
|
28
|
+
*
|
|
29
|
+
* const assetClass = {
|
|
30
|
+
* columns: {
|
|
31
|
+
* _harnessId: { type: 'int64' },
|
|
32
|
+
* temperature: { type: 'float64', resolution: 0.01 }
|
|
33
|
+
* }
|
|
34
|
+
* };
|
|
35
|
+
*
|
|
36
|
+
* flow( 'check' )
|
|
37
|
+
* .assetClass( assetClass )
|
|
38
|
+
* .source( testHarness, {
|
|
39
|
+
* messageTemplate: {
|
|
40
|
+
* seed: 42,
|
|
41
|
+
* messageCount: 100,
|
|
42
|
+
* fields: {
|
|
43
|
+
* temperature: { type: 'float64', range: [ 20, 30 ], resolution: 0.01 }
|
|
44
|
+
* }
|
|
45
|
+
* },
|
|
46
|
+
* assetClass
|
|
47
|
+
* } )
|
|
48
|
+
* .run();
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
import { start } from './start.js';
|
|
52
|
+
import { MESSAGE_TEMPLATE_SCHEMA, ASSET_CLASS_SCHEMA } from './validate.js';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Source identifier — used by the flow runtime for lookup.
|
|
56
|
+
* @type {string}
|
|
57
|
+
*/
|
|
58
|
+
export const id = 'testHarness';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Crash-survival class per ADR-018. For a source the value describes
|
|
62
|
+
* the input it can recover after a disconnect: the harness recovers
|
|
63
|
+
* nothing — a crashed run is re-generated deterministically.
|
|
64
|
+
* @type {string}
|
|
65
|
+
*/
|
|
66
|
+
export const durabilityClass = 'best-effort';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Schema for the harness's public config — the `messageTemplate`
|
|
70
|
+
* and `assetClass` it accepts, plus the caller-suppliable `onStatus`
|
|
71
|
+
* and `shutdownOnComplete`. Exposed for DSL-time validation.
|
|
72
|
+
*
|
|
73
|
+
* `_propertyNames` rejects unknown keys at DSL time (the validator's
|
|
74
|
+
* only unknown-key mechanism, validate.js:68-77) — added by the
|
|
75
|
+
* 2026-07-09 uniformity sweep. The retired `onComplete` key is
|
|
76
|
+
* rejected here: completion travels `onStatus` as
|
|
77
|
+
* `{phase: 'complete', count}` per ADR-018. Runtime-injected
|
|
78
|
+
* callbacks (`onMessage`, `onShutdown`) are deliberately absent.
|
|
79
|
+
*
|
|
80
|
+
* The harness's runtime entry (`start`) re-validates with
|
|
81
|
+
* `validateMessageTemplate` and `validateAssetClass` so any
|
|
82
|
+
* problem fails at startup with classified `err.code`.
|
|
83
|
+
*/
|
|
84
|
+
export const configSchema = {
|
|
85
|
+
_propertyNames: [
|
|
86
|
+
'messageTemplate',
|
|
87
|
+
'assetClass',
|
|
88
|
+
'onStatus',
|
|
89
|
+
'shutdownOnComplete'
|
|
90
|
+
],
|
|
91
|
+
messageTemplate: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
required: true,
|
|
94
|
+
properties: MESSAGE_TEMPLATE_SCHEMA
|
|
95
|
+
},
|
|
96
|
+
assetClass: {
|
|
97
|
+
type: 'object',
|
|
98
|
+
required: true,
|
|
99
|
+
properties: ASSET_CLASS_SCHEMA
|
|
100
|
+
},
|
|
101
|
+
onStatus: {
|
|
102
|
+
type: 'function',
|
|
103
|
+
required: false,
|
|
104
|
+
error: 'onStatus must be a function'
|
|
105
|
+
},
|
|
106
|
+
shutdownOnComplete: {
|
|
107
|
+
type: 'boolean',
|
|
108
|
+
required: false,
|
|
109
|
+
error: 'shutdownOnComplete must be a boolean'
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export { start };
|
|
114
|
+
|
|
115
|
+
export default { id, configSchema, durabilityClass, start };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// core/source-manager/test-harness/prng.js
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-bitwise -- Mulberry32 is fundamentally a bit-shuffling PRNG; bitwise ops are intentional and load-bearing. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @fileoverview Seeded random number generator for testHarness.
|
|
7
|
+
*
|
|
8
|
+
* Tests need to be reproducible: the same seed must give the same
|
|
9
|
+
* sequence of numbers every run. We use Mulberry32 — a small, fast
|
|
10
|
+
* generator with 32-bit state. Good enough for test data; not
|
|
11
|
+
* suitable for anything cryptographic.
|
|
12
|
+
*
|
|
13
|
+
* Reference: https://github.com/bryc/code/blob/master/jshash/PRNGs.md
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Builds a random-number generator from a numeric seed.
|
|
18
|
+
*
|
|
19
|
+
* @param {number} seed - any finite number; same seed → same sequence
|
|
20
|
+
* @returns {{next: function(): number, intInRange: function(number, number): number, floatInRange: function(number, number): number, pickFrom: function(Array): *}}
|
|
21
|
+
*/
|
|
22
|
+
export const createPrng = function ( seed ) {
|
|
23
|
+
// Force the seed into an unsigned 32-bit integer so the maths
|
|
24
|
+
// below behaves the same on every platform.
|
|
25
|
+
let state = seed >>> 0;
|
|
26
|
+
|
|
27
|
+
const next = function () {
|
|
28
|
+
state = ( state + 0x6D2B79F5 ) | 0;
|
|
29
|
+
let t = state;
|
|
30
|
+
t = Math.imul( t ^ ( t >>> 15 ), t | 1 );
|
|
31
|
+
t ^= t + Math.imul( t ^ ( t >>> 7 ), t | 61 );
|
|
32
|
+
return ( ( t ^ ( t >>> 14 ) ) >>> 0 ) / 4294967296;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whole number in the inclusive range [min, max].
|
|
37
|
+
*/
|
|
38
|
+
const intInRange = function ( min, max ) {
|
|
39
|
+
return Math.floor( min + ( next() * ( ( max - min ) + 1 ) ) );
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Decimal number in the half-open range [min, max).
|
|
44
|
+
*/
|
|
45
|
+
const floatInRange = function ( min, max ) {
|
|
46
|
+
return min + ( next() * ( max - min ) );
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Picks one element from a non-empty array.
|
|
51
|
+
*/
|
|
52
|
+
const pickFrom = function ( arr ) {
|
|
53
|
+
return arr[ Math.floor( next() * arr.length ) ];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return { next, intInRange, floatInRange, pickFrom };
|
|
57
|
+
};
|