@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,232 @@
|
|
|
1
|
+
// core/source-manager/test-harness/start.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Synthetic source for end-to-end contract checks.
|
|
5
|
+
*
|
|
6
|
+
* testHarness invents messages on demand and feeds them to the
|
|
7
|
+
* pipeline. It does not read from a file or a network — it is a
|
|
8
|
+
* verification adapter, used by integration tests to push messages
|
|
9
|
+
* through every sink and check that all sinks agree.
|
|
10
|
+
*
|
|
11
|
+
* Each generated message carries a running number `_harnessId` so
|
|
12
|
+
* the cross-sink check tool can find the same message in every
|
|
13
|
+
* sink, regardless of arrival order. Field values come from the
|
|
14
|
+
* test author's `messageTemplate`. Tests can opt into a small set
|
|
15
|
+
* of fuzz patterns that inject bad values every Nth message; with
|
|
16
|
+
* fuzz on, the only check is "no adapter crashed".
|
|
17
|
+
*
|
|
18
|
+
* Adapter contract (ADR-018, source role):
|
|
19
|
+
* - Every `onStatus` payload carries the uniform structured shape per
|
|
20
|
+
* ADR-018: `{status: 'green' | 'yellow' | 'red', connected,
|
|
21
|
+
* phase}`; the error path adds `error: {code, message}`. One payload
|
|
22
|
+
* rule, no exceptions (uniformity sweep, 2026-07-09).
|
|
23
|
+
* - Lifecycle phases:
|
|
24
|
+
* `phase: 'starting'` — harness is about to begin
|
|
25
|
+
* `phase: 'generating'` — first message about to be sent
|
|
26
|
+
* `phase: 'complete'` — all messages sent (`connected: false`);
|
|
27
|
+
* carries the uniform `count` of produced
|
|
28
|
+
* messages (per ADR-018 there is no
|
|
29
|
+
* onComplete callback)
|
|
30
|
+
* `phase: 'errored'` — terminal red: the run loop failed and
|
|
31
|
+
* generation stopped (the ADR-018 two-tier
|
|
32
|
+
* rule)
|
|
33
|
+
* `phase: 'stopped'` — yellow status when forced to stop
|
|
34
|
+
*
|
|
35
|
+
* `err.code` vocabulary (per-adapter, listed here per ADR-018):
|
|
36
|
+
* - `INVALID_CONFIG` — setup-time; bad messageTemplate or asset
|
|
37
|
+
* class. Thrown synchronously from start().
|
|
38
|
+
* - `GENERATOR_ERROR` — runtime; a downstream `onMessage` call
|
|
39
|
+
* threw inside the loop. Routed through
|
|
40
|
+
* onStatus, not re-thrown.
|
|
41
|
+
*
|
|
42
|
+
* Design decisions date from 2026-04-29. The `messageTemplate` shape
|
|
43
|
+
* is enforced at startup by `validate.js`; the per-field spec shape
|
|
44
|
+
* is documented in `field-generator.js`.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { createPrng } from './prng.js';
|
|
48
|
+
import { generateField } from './field-generator.js';
|
|
49
|
+
import { applyFuzz, FUZZ_PATTERN_NAMES } from './fuzz.js';
|
|
50
|
+
import { validateMessageTemplate, validateAssetClass } from './validate.js';
|
|
51
|
+
|
|
52
|
+
const DEFAULT_MESSAGE_COUNT = 1000;
|
|
53
|
+
const DEFAULT_INTERVAL_MS = 0;
|
|
54
|
+
const DEFAULT_FUZZ_INTERVAL = 0;
|
|
55
|
+
const DEFAULT_STOP_TIMEOUT = 5000;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Starts the synthetic source.
|
|
59
|
+
*
|
|
60
|
+
* @param {Object} config
|
|
61
|
+
* @param {Object} config.messageTemplate - Required. Shape enforced by validate.js.
|
|
62
|
+
* @param {Object} config.assetClass - Required. Must declare `_harnessId` as int64.
|
|
63
|
+
* @param {Function} config.onMessage - Required. Pipeline-injected handler.
|
|
64
|
+
* @param {Function} [config.onStatus] - Status callback (structured shape per ADR-018);
|
|
65
|
+
* completion arrives as `{phase: 'complete', count}`.
|
|
66
|
+
* @param {Function} [config.onShutdown] - Pipeline shutdown hook (injected by runtime).
|
|
67
|
+
* @param {boolean} [config.shutdownOnComplete=true] - Auto-shutdown pipeline when generation ends.
|
|
68
|
+
* @returns {Function} stopFn — accepts `{ timeout }`, returns Promise resolving when stopped.
|
|
69
|
+
*/
|
|
70
|
+
export const start = function ( config ) {
|
|
71
|
+
const {
|
|
72
|
+
messageTemplate,
|
|
73
|
+
assetClass,
|
|
74
|
+
onMessage,
|
|
75
|
+
onStatus = null,
|
|
76
|
+
onShutdown = null,
|
|
77
|
+
shutdownOnComplete = true
|
|
78
|
+
} = config;
|
|
79
|
+
|
|
80
|
+
// Setup-time validation. Both throw INVALID_CONFIG on any
|
|
81
|
+
// problem, listing every issue found.
|
|
82
|
+
validateMessageTemplate( messageTemplate );
|
|
83
|
+
validateAssetClass( assetClass );
|
|
84
|
+
|
|
85
|
+
if ( typeof onMessage !== 'function' ) {
|
|
86
|
+
const err = new Error( 'testHarness: onMessage must be a function' );
|
|
87
|
+
err.code = 'INVALID_CONFIG';
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Pull out template options with defaults.
|
|
92
|
+
const seed = messageTemplate.seed;
|
|
93
|
+
const messageCount = messageTemplate.messageCount ?? DEFAULT_MESSAGE_COUNT;
|
|
94
|
+
const intervalMs = messageTemplate.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
95
|
+
const fuzzInterval = messageTemplate.fuzzInterval ?? DEFAULT_FUZZ_INTERVAL;
|
|
96
|
+
const fuzzTarget = messageTemplate.fuzzTarget ?? null;
|
|
97
|
+
const fields = messageTemplate.fields;
|
|
98
|
+
const fieldNames = Object.keys( fields );
|
|
99
|
+
|
|
100
|
+
// Stop / finished bookkeeping. `finished` resolves when the run
|
|
101
|
+
// loop fully exits (success or error) — the stop function races
|
|
102
|
+
// it against the timeout, mirroring CSV's shape (ADR-018).
|
|
103
|
+
const prng = createPrng( seed );
|
|
104
|
+
let stopped = false;
|
|
105
|
+
let sentCount = 0;
|
|
106
|
+
let resolveFinished;
|
|
107
|
+
const finished = new Promise( ( resolve ) => {
|
|
108
|
+
resolveFinished = resolve;
|
|
109
|
+
} );
|
|
110
|
+
|
|
111
|
+
if ( onStatus ) onStatus( {
|
|
112
|
+
status: 'green',
|
|
113
|
+
connected: true,
|
|
114
|
+
phase: 'starting',
|
|
115
|
+
messageCount,
|
|
116
|
+
fuzzInterval
|
|
117
|
+
} );
|
|
118
|
+
|
|
119
|
+
const run = async function () {
|
|
120
|
+
if ( onStatus ) onStatus( {
|
|
121
|
+
status: 'green',
|
|
122
|
+
connected: true,
|
|
123
|
+
phase: 'generating'
|
|
124
|
+
} );
|
|
125
|
+
|
|
126
|
+
for ( let i = 1; i <= messageCount; i += 1 ) {
|
|
127
|
+
if ( stopped ) break;
|
|
128
|
+
|
|
129
|
+
const msg = Object.create( null );
|
|
130
|
+
msg._harnessId = i; // eslint-disable-line no-underscore-dangle
|
|
131
|
+
|
|
132
|
+
for ( const name of fieldNames ) {
|
|
133
|
+
msg[ name ] = generateField( fields[ name ], prng, i, intervalMs );
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Fuzz: inject one bad value every Nth message.
|
|
137
|
+
if ( fuzzInterval > 0 && i % fuzzInterval === 0 ) {
|
|
138
|
+
const fuzzNumber = i / fuzzInterval;
|
|
139
|
+
const patternIndex = ( fuzzNumber - 1 ) % FUZZ_PATTERN_NAMES.length;
|
|
140
|
+
applyFuzz( msg, fuzzTarget, fields[ fuzzTarget ], patternIndex );
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// eslint-disable-next-line no-await-in-loop -- Source must wait for the consumer before sending the next message; flooding the consumer would defeat the purpose of an integration check.
|
|
144
|
+
await onMessage( msg );
|
|
145
|
+
sentCount = i;
|
|
146
|
+
|
|
147
|
+
if ( intervalMs > 0 && !stopped ) {
|
|
148
|
+
// eslint-disable-next-line no-await-in-loop -- Paced send between messages is part of the contract for tests that exercise idle-flush timing.
|
|
149
|
+
await new Promise( ( r ) => setTimeout( r, intervalMs ) );
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Completion travels onStatus with the uniform `count` field
|
|
154
|
+
// (per ADR-018 there is no onComplete callback).
|
|
155
|
+
if ( onStatus ) onStatus( {
|
|
156
|
+
status: 'green',
|
|
157
|
+
connected: false,
|
|
158
|
+
phase: 'complete',
|
|
159
|
+
count: sentCount
|
|
160
|
+
} );
|
|
161
|
+
|
|
162
|
+
if ( shutdownOnComplete && onShutdown ) {
|
|
163
|
+
await onShutdown();
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Kick off. Failures inside the loop come from a downstream
|
|
168
|
+
// onMessage handler (the harness itself has no transport that
|
|
169
|
+
// can fail). Route through onStatus so the test sees the error
|
|
170
|
+
// without losing it. Always resolve `finished` so stop() can
|
|
171
|
+
// wait on it.
|
|
172
|
+
run().catch( ( err ) => {
|
|
173
|
+
const message = ( err && err.message ) ? err.message : String( err );
|
|
174
|
+
if ( onStatus ) {
|
|
175
|
+
// Terminal red: generation stopped. Uniform payload with
|
|
176
|
+
// phase 'errored' per the ADR-018 two-tier rule.
|
|
177
|
+
onStatus( {
|
|
178
|
+
status: 'red',
|
|
179
|
+
connected: false,
|
|
180
|
+
phase: 'errored',
|
|
181
|
+
error: { code: 'GENERATOR_ERROR', message }
|
|
182
|
+
} );
|
|
183
|
+
} else {
|
|
184
|
+
console.error( `testHarness error [GENERATOR_ERROR]: ${message}` );
|
|
185
|
+
}
|
|
186
|
+
} ).finally( () => {
|
|
187
|
+
resolveFinished();
|
|
188
|
+
} );
|
|
189
|
+
|
|
190
|
+
// Stop function with `{ timeout }` symmetry per ADR-018.
|
|
191
|
+
//
|
|
192
|
+
// What it does:
|
|
193
|
+
// - Asks the loop to stop at the next message by setting `stopped`.
|
|
194
|
+
// - Waits for the loop to finish. If it finishes within `timeout`
|
|
195
|
+
// ms, the stop returns right away.
|
|
196
|
+
// - If the loop is stuck inside an `await onMessage(msg)` that
|
|
197
|
+
// never returns, the time budget runs out. The stop returns
|
|
198
|
+
// anyway so the rest of the shutdown can proceed.
|
|
199
|
+
// - When the budget runs out, sends a yellow status so callers
|
|
200
|
+
// can see the stop was forced rather than clean.
|
|
201
|
+
//
|
|
202
|
+
// The timer is `unref()`ed so it does not keep Node alive while
|
|
203
|
+
// the loop is finishing in time.
|
|
204
|
+
return function ( { timeout = DEFAULT_STOP_TIMEOUT } = {} ) {
|
|
205
|
+
stopped = true;
|
|
206
|
+
return new Promise( ( resolve ) => {
|
|
207
|
+
let settled = false;
|
|
208
|
+
let forceTimer = null;
|
|
209
|
+
const settle = function () {
|
|
210
|
+
|
|
211
|
+
/* c8 ignore next -- defensive: in normal lifecycle, settle is invoked exactly once (timer fires OR finished resolves; clean path clears the timer before it can fire). The guard protects against a race that requires both paths to fire concurrently, which has no producer in the current design. */
|
|
212
|
+
if ( settled ) return;
|
|
213
|
+
settled = true;
|
|
214
|
+
if ( forceTimer ) clearTimeout( forceTimer );
|
|
215
|
+
resolve();
|
|
216
|
+
};
|
|
217
|
+
forceTimer = setTimeout( () => {
|
|
218
|
+
if ( onStatus ) {
|
|
219
|
+
onStatus( {
|
|
220
|
+
status: 'yellow',
|
|
221
|
+
connected: false,
|
|
222
|
+
phase: 'stopped',
|
|
223
|
+
note: `Stop took longer than ${timeout}ms — forced.`
|
|
224
|
+
} );
|
|
225
|
+
}
|
|
226
|
+
settle();
|
|
227
|
+
}, timeout );
|
|
228
|
+
forceTimer.unref();
|
|
229
|
+
finished.then( settle, settle );
|
|
230
|
+
} );
|
|
231
|
+
};
|
|
232
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// core/source-manager/test-harness/validate.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Startup validation for testHarness.
|
|
5
|
+
*
|
|
6
|
+
* Two things to check before the harness starts running:
|
|
7
|
+
*
|
|
8
|
+
* 1. The `messageTemplate` config — it must declare a seed, at least
|
|
9
|
+
* one field, valid types, and (when fuzz is on) a target column.
|
|
10
|
+
* 2. The asset class — it must declare `_harnessId` as an int64
|
|
11
|
+
* column. The cross-sink check tool uses this number to find the
|
|
12
|
+
* same message in every sink, so every sink must be able to
|
|
13
|
+
* store and report it.
|
|
14
|
+
*
|
|
15
|
+
* Validation runs through the shared `validateWithSchema` helper
|
|
16
|
+
* (see `core/utils/validate`). On any failure we re-throw as a single
|
|
17
|
+
* Error with `code = 'INVALID_CONFIG'` — the classified shape
|
|
18
|
+
* ADR-018 mandates for setup failures. The message lists every
|
|
19
|
+
* problem found so
|
|
20
|
+
* the test author can fix them in one pass.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { validateWithSchema, validators } from '../../utils/validate/index.js';
|
|
24
|
+
|
|
25
|
+
const FIELD_TYPES = [ 'float64', 'int64', 'bool', 'string', 'timestamp' ];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Schema for one entry in messageTemplate.fields.
|
|
29
|
+
*/
|
|
30
|
+
const FIELD_SPEC_SCHEMA = {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
type: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
required: true,
|
|
36
|
+
validator: validators.oneOf( FIELD_TYPES ),
|
|
37
|
+
error: `must be one of ${FIELD_TYPES.join( ', ' )}`
|
|
38
|
+
},
|
|
39
|
+
range: {
|
|
40
|
+
type: 'array',
|
|
41
|
+
minItems: 2,
|
|
42
|
+
maxItems: 2,
|
|
43
|
+
itemSchema: { type: 'number' },
|
|
44
|
+
validator: function ( arr ) {
|
|
45
|
+
return arr[ 0 ] < arr[ 1 ];
|
|
46
|
+
},
|
|
47
|
+
error: 'must be [ min, max ] with min < max'
|
|
48
|
+
},
|
|
49
|
+
resolution: {
|
|
50
|
+
type: 'number',
|
|
51
|
+
validator: validators.positive,
|
|
52
|
+
error: 'must be a positive number'
|
|
53
|
+
},
|
|
54
|
+
values: {
|
|
55
|
+
type: 'array',
|
|
56
|
+
minItems: 1
|
|
57
|
+
},
|
|
58
|
+
mode: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
validator: validators.oneOf( [ 'monotonic-ms', 'static' ] ),
|
|
61
|
+
error: 'must be "monotonic-ms" or "static"'
|
|
62
|
+
}
|
|
63
|
+
// `seedValue` accepts any type, so we omit it from the schema.
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Schema for the messageTemplate config.
|
|
69
|
+
*
|
|
70
|
+
* Cross-field rules (handled by `_crossFieldValidators`):
|
|
71
|
+
* - fuzzTarget is required when fuzzInterval > 0
|
|
72
|
+
* - fuzzTarget must name a declared field
|
|
73
|
+
* - fields must not declare `_harnessId` (the harness adds it)
|
|
74
|
+
*/
|
|
75
|
+
export const MESSAGE_TEMPLATE_SCHEMA = {
|
|
76
|
+
seed: {
|
|
77
|
+
type: 'number',
|
|
78
|
+
required: true,
|
|
79
|
+
validator: validators.isFinite,
|
|
80
|
+
error: 'seed must be a finite number'
|
|
81
|
+
},
|
|
82
|
+
messageCount: {
|
|
83
|
+
type: 'number',
|
|
84
|
+
validator: validators.positiveInteger,
|
|
85
|
+
error: 'messageCount must be a positive integer'
|
|
86
|
+
},
|
|
87
|
+
intervalMs: {
|
|
88
|
+
type: 'number',
|
|
89
|
+
validator: validators.nonNegativeFinite,
|
|
90
|
+
error: 'intervalMs must be a non-negative finite number'
|
|
91
|
+
},
|
|
92
|
+
fuzzInterval: {
|
|
93
|
+
type: 'number',
|
|
94
|
+
validator: function ( v ) {
|
|
95
|
+
return Number.isInteger( v ) && v >= 0;
|
|
96
|
+
},
|
|
97
|
+
error: 'fuzzInterval must be a non-negative integer'
|
|
98
|
+
},
|
|
99
|
+
fuzzTarget: {
|
|
100
|
+
type: 'string'
|
|
101
|
+
},
|
|
102
|
+
fields: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
required: true,
|
|
105
|
+
minProperties: 1,
|
|
106
|
+
propertySchema: FIELD_SPEC_SCHEMA
|
|
107
|
+
},
|
|
108
|
+
_crossFieldValidators: [
|
|
109
|
+
{
|
|
110
|
+
fields: [ 'fuzzInterval', 'fuzzTarget' ],
|
|
111
|
+
validator: function ( obj ) {
|
|
112
|
+
if ( !( obj.fuzzInterval > 0 ) ) return true;
|
|
113
|
+
return typeof obj.fuzzTarget === 'string' && obj.fuzzTarget.length > 0;
|
|
114
|
+
},
|
|
115
|
+
error: 'fuzzTarget is required when fuzzInterval > 0'
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
fields: [ 'fuzzTarget', 'fields' ],
|
|
119
|
+
validator: function ( obj ) {
|
|
120
|
+
if ( !obj.fuzzTarget ) return true;
|
|
121
|
+
return obj.fuzzTarget in obj.fields;
|
|
122
|
+
},
|
|
123
|
+
error: 'fuzzTarget must name a declared field'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
fields: [ 'fields' ],
|
|
127
|
+
validator: function ( obj ) {
|
|
128
|
+
return !( '_harnessId' in obj.fields );
|
|
129
|
+
},
|
|
130
|
+
error: 'fields must not declare "_harnessId" — the harness adds it automatically'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
fields: [ 'fields' ],
|
|
134
|
+
validator: function ( obj ) {
|
|
135
|
+
for ( const spec of Object.values( obj.fields ) ) {
|
|
136
|
+
if ( spec.type === 'string' ) {
|
|
137
|
+
if ( !Array.isArray( spec.values ) || spec.values.length === 0 ) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
},
|
|
144
|
+
error: 'fields with type "string" must declare a non-empty values: [ ... ] list'
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Schema for the asset class. Only checks the parts the harness
|
|
151
|
+
* cares about — `_harnessId` declared as an int64 column. Other
|
|
152
|
+
* columns may exist; we do not look at them here.
|
|
153
|
+
*/
|
|
154
|
+
export const ASSET_CLASS_SCHEMA = {
|
|
155
|
+
columns: {
|
|
156
|
+
type: 'object',
|
|
157
|
+
required: true,
|
|
158
|
+
properties: {
|
|
159
|
+
_harnessId: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
required: true,
|
|
162
|
+
properties: {
|
|
163
|
+
type: {
|
|
164
|
+
type: 'string',
|
|
165
|
+
required: true,
|
|
166
|
+
value: 'int64',
|
|
167
|
+
error: 'must be int64'
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const throwInvalidConfig = function ( errors ) {
|
|
176
|
+
const err = new Error( `testHarness: ${errors.join( '; ' )}` );
|
|
177
|
+
err.code = 'INVALID_CONFIG';
|
|
178
|
+
throw err;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Validates the messageTemplate. Throws Error with
|
|
183
|
+
* `code = 'INVALID_CONFIG'` if anything fails.
|
|
184
|
+
*
|
|
185
|
+
* @param {Object} template - messageTemplate from config
|
|
186
|
+
*/
|
|
187
|
+
export const validateMessageTemplate = function ( template ) {
|
|
188
|
+
if ( !template || typeof template !== 'object' ) {
|
|
189
|
+
throwInvalidConfig( [ 'messageTemplate is required (object)' ] );
|
|
190
|
+
}
|
|
191
|
+
const result = validateWithSchema( MESSAGE_TEMPLATE_SCHEMA, template, 'messageTemplate' );
|
|
192
|
+
if ( !result.valid ) {
|
|
193
|
+
throwInvalidConfig( result.errors );
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Validates the asset class. The harness needs `_harnessId` as an
|
|
199
|
+
* int64 column — the check tool uses this number to find the same
|
|
200
|
+
* message across sinks. Throws Error with
|
|
201
|
+
* `code = 'INVALID_CONFIG'` if anything fails.
|
|
202
|
+
*
|
|
203
|
+
* @param {Object} assetClass - The asset class declared by the test
|
|
204
|
+
*/
|
|
205
|
+
export const validateAssetClass = function ( assetClass ) {
|
|
206
|
+
if ( !assetClass || typeof assetClass !== 'object' ) {
|
|
207
|
+
throwInvalidConfig( [ 'assetClass is required in config' ] );
|
|
208
|
+
}
|
|
209
|
+
const result = validateWithSchema( ASSET_CLASS_SCHEMA, assetClass, 'assetClass' );
|
|
210
|
+
if ( !result.valid ) {
|
|
211
|
+
throwInvalidConfig( result.errors );
|
|
212
|
+
}
|
|
213
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// core/storage-manager/questdb/assert-columns.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Walks every column in an asset class and checks that the
|
|
5
|
+
* column-internal facts QuestDB actually reads are well-formed. Throws
|
|
6
|
+
* with `err.code = 'INVALID_CONFIG'` on the first problem found, naming
|
|
7
|
+
* the offending column and field in the message.
|
|
8
|
+
*
|
|
9
|
+
* Why this file exists separately from the universal semantics schema:
|
|
10
|
+
*
|
|
11
|
+
* The semantics schema (in `core/semantics/schemas/`) validates what a
|
|
12
|
+
* well-formed asset class looks like in the abstract. It says "every
|
|
13
|
+
* column needs a `type`" because that is true for any consumer; it does
|
|
14
|
+
* NOT say "if `type === 'float64'` then `resolution` must be positive"
|
|
15
|
+
* because that is QuestDB-specific knowledge — other adapters may not
|
|
16
|
+
* care about resolution at all. Adapter-specific contracts belong in
|
|
17
|
+
* the adapter, not in the universal schema.
|
|
18
|
+
*
|
|
19
|
+
* Per ADR-018's column-internal facts pattern: each adapter that
|
|
20
|
+
* consumes column-internal facts MUST validate them defensively at
|
|
21
|
+
* startup AND document those assertions in its `@fileoverview`. This
|
|
22
|
+
* file is QuestDB's enforcement point; the documentation lives in the
|
|
23
|
+
* `Column-internal facts consumed` section of `index.js`'s top-of-file
|
|
24
|
+
* comment block.
|
|
25
|
+
*
|
|
26
|
+
* What this layer catches that the universal semantics schema does not:
|
|
27
|
+
*
|
|
28
|
+
* - **Type drift.** The universal schema accepts any type the semantics
|
|
29
|
+
* layer knows about. QuestDB knows how to write a fixed set (the keys
|
|
30
|
+
* of `DDL_TYPES` — float64, int64, bool, string, timestamp). If a new
|
|
31
|
+
* type joins semantics tomorrow but QuestDB's writers haven't caught
|
|
32
|
+
* up, the silent fallback (in `ensure-tables.js` and `persist-plan.js`)
|
|
33
|
+
* would coerce values to VARCHAR. This check makes the gap loud.
|
|
34
|
+
* - **Bypassed schema validation.** Any path that builds an asset class
|
|
35
|
+
* without going through `loadSemantics()` AND without going through
|
|
36
|
+
* `flow.assetClass()` (e.g., a test calling `createStorage` directly)
|
|
37
|
+
* skips the schema. This file is the last line of defense.
|
|
38
|
+
* - **Resolution sanity for float64.** Resolution is optional in the
|
|
39
|
+
* schema (defaults to 1, treated as passthrough by `writers.js:54`).
|
|
40
|
+
* But if the caller declares `resolution: 0` or `resolution: -0.1`,
|
|
41
|
+
* the writer would compute `invResolution = 1 / resolution` as
|
|
42
|
+
* Infinity / negative — silently corrupting every persisted value.
|
|
43
|
+
* The schema does not enforce positivity (since absent resolution is
|
|
44
|
+
* valid); this layer does, when present.
|
|
45
|
+
*
|
|
46
|
+
* Family: same shape as `core/wiring/assert-handle.js`. Both are pure
|
|
47
|
+
* functions that throw diagnostically at startup when an adapter
|
|
48
|
+
* contract is violated.
|
|
49
|
+
*
|
|
50
|
+
* `err.code` thrown:
|
|
51
|
+
* - `INVALID_CONFIG` — column-internal fact missing or malformed.
|
|
52
|
+
* Per ADR-018, setup-time throws carry a classified `err.code`.
|
|
53
|
+
*
|
|
54
|
+
* @see ADR-018 (capability declaration)
|
|
55
|
+
* @see ./writers.js (resolution handling at write time)
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import { DDL_TYPES } from './ensure-tables.js';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Walk every column in `assetClass.columns` and assert the facts QuestDB
|
|
62
|
+
* reads are well-formed. Throws on the first violation.
|
|
63
|
+
*
|
|
64
|
+
* Behaviour:
|
|
65
|
+
* - Each column's `type` must be set and writable by QuestDB
|
|
66
|
+
* (i.e., a key in `DDL_TYPES`).
|
|
67
|
+
* - For columns where `type === 'float64'` and `resolution` is defined,
|
|
68
|
+
* `resolution` must be a positive finite number. (Absent resolution
|
|
69
|
+
* is fine — the writer treats undefined as passthrough.)
|
|
70
|
+
*
|
|
71
|
+
* @param {Object} assetClass - asset class with `columns` map
|
|
72
|
+
* @throws {Error} `err.code === 'INVALID_CONFIG'` on any column-internal
|
|
73
|
+
* fact violation; message names the column and the offending field
|
|
74
|
+
*/
|
|
75
|
+
const assertColumnFacts = function ( assetClass ) {
|
|
76
|
+
const columns = assetClass.columns || Object.create( null );
|
|
77
|
+
const colNames = Object.keys( columns );
|
|
78
|
+
|
|
79
|
+
for ( let i = 0; i < colNames.length; i += 1 ) {
|
|
80
|
+
const colName = colNames[ i ];
|
|
81
|
+
const colSpec = columns[ colName ];
|
|
82
|
+
|
|
83
|
+
// Column type must be set and writable by QuestDB. Closes the
|
|
84
|
+
// silent fallback to VARCHAR / writeAsString for any missing or
|
|
85
|
+
// unsupported type. The `colSpec` truthy check also catches a
|
|
86
|
+
// null / undefined column entry — caller bug, but we surface it
|
|
87
|
+
// clearly rather than crashing on `.type` access.
|
|
88
|
+
if ( !colSpec || !( colSpec.type in DDL_TYPES ) ) {
|
|
89
|
+
const supportedTypes = Object.keys( DDL_TYPES ).join( ', ' );
|
|
90
|
+
const actualType = colSpec ? String( colSpec.type ) : 'undefined';
|
|
91
|
+
const err = new Error(
|
|
92
|
+
`WinkComposer/questdb: column '${colName}' has invalid or missing type '${actualType}'; ` +
|
|
93
|
+
`expected one of: ${supportedTypes}`
|
|
94
|
+
);
|
|
95
|
+
err.code = 'INVALID_CONFIG';
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Float64 columns may declare a resolution for quantization. When
|
|
100
|
+
// present, must be a positive finite number — invResolution at
|
|
101
|
+
// 0 / negative / non-finite would silently corrupt every value.
|
|
102
|
+
// Absent resolution is fine: writers.js:54 treats undefined and
|
|
103
|
+
// 1 as passthrough (no quantization).
|
|
104
|
+
if ( colSpec.type === 'float64' && colSpec.resolution !== undefined ) {
|
|
105
|
+
if ( typeof colSpec.resolution !== 'number' ||
|
|
106
|
+
!Number.isFinite( colSpec.resolution ) ||
|
|
107
|
+
colSpec.resolution <= 0 ) {
|
|
108
|
+
const err = new Error(
|
|
109
|
+
`WinkComposer/questdb: column '${colName}' has type 'float64' ` +
|
|
110
|
+
`but resolution is '${colSpec.resolution}'; ` +
|
|
111
|
+
'expected positive finite number (or omit for passthrough)'
|
|
112
|
+
);
|
|
113
|
+
err.code = 'INVALID_CONFIG';
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export { assertColumnFacts };
|