@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,171 @@
|
|
|
1
|
+
// core/source-manager/csv/index.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview CSV Source Adapter.
|
|
5
|
+
*
|
|
6
|
+
* Streams a CSV file row by row to the pipeline. Each row becomes one
|
|
7
|
+
* message. Useful for replaying recorded data through a flow under test
|
|
8
|
+
* conditions, or for one-shot batch jobs that consume a file and stop.
|
|
9
|
+
*
|
|
10
|
+
* Adapter contract (ADR-018):
|
|
11
|
+
* - Module-level exports: `id`, `configSchema`, `start`,
|
|
12
|
+
* `durabilityClass`, and the default aggregate referencing the same
|
|
13
|
+
* constants.
|
|
14
|
+
* - `start( config )` returns a `stopFn({ timeout })`.
|
|
15
|
+
* - Lifecycle and error contract details: see `start.js` file header.
|
|
16
|
+
*
|
|
17
|
+
* Durability (ADR-018): `'best-effort'` — the source keeps no recovery state
|
|
18
|
+
* of its own. The file itself is replayable, so a caller can re-run the
|
|
19
|
+
* stream after a crash, but the adapter does not resume mid-file.
|
|
20
|
+
*
|
|
21
|
+
* `configSchema` declares the user-supplied config fields. The flow
|
|
22
|
+
* runtime calls `validateWithSchema( csv.configSchema, sourceConfig )`
|
|
23
|
+
* at DSL time (in `flow.source()`), so typos and bad types are caught
|
|
24
|
+
* before the flow ever runs. Per ADR-018 the schema is authoritative
|
|
25
|
+
* for the fields it covers; `start.js` does not re-enforce them.
|
|
26
|
+
*
|
|
27
|
+
* Runtime-injected callbacks (`onMessage`, `onShutdown`) are not part
|
|
28
|
+
* of `configSchema` — they are added to the config by the wiring layer
|
|
29
|
+
* at start time, not by the user.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* import { flow, csv } from '@winkjs/composer';
|
|
33
|
+
*
|
|
34
|
+
* flow( 'demo' )
|
|
35
|
+
* .source( csv, {
|
|
36
|
+
* path: './sensor-data.csv',
|
|
37
|
+
* delayMs: 100
|
|
38
|
+
* } )
|
|
39
|
+
* .run();
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { validators } from '../../utils/validate/index.js';
|
|
43
|
+
import { start } from './start.js';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Source identifier — used by the flow registry and error messages.
|
|
47
|
+
* @type {string}
|
|
48
|
+
*/
|
|
49
|
+
export const id = 'csv';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Crash-survival class per ADR-018. For a source the value describes
|
|
53
|
+
* the input it can recover after a disconnect: CSV recovers nothing
|
|
54
|
+
* itself — the caller replays the file.
|
|
55
|
+
* @type {string}
|
|
56
|
+
*/
|
|
57
|
+
export const durabilityClass = 'best-effort';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Validates that a value is either a number or a non-empty string.
|
|
61
|
+
* Used by `startMsgId` and `endMsgId`, which accept either an index
|
|
62
|
+
* (number) or a row-id field value (typically a string).
|
|
63
|
+
*
|
|
64
|
+
* @param {*} value
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
const isNumberOrNonEmptyString = function ( value ) {
|
|
68
|
+
if ( typeof value === 'number' && Number.isFinite( value ) ) return true;
|
|
69
|
+
if ( typeof value === 'string' && value.length > 0 ) return true;
|
|
70
|
+
return false;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Schema for the CSV source's user-supplied config. Consumed by the
|
|
75
|
+
* flow runtime (`flow.source()` → `validateWithSchema`) at DSL time.
|
|
76
|
+
*
|
|
77
|
+
* Required:
|
|
78
|
+
* - `path` — file system path to the CSV file.
|
|
79
|
+
*
|
|
80
|
+
* Optional:
|
|
81
|
+
* - `delayMs` — pause between messages, in milliseconds. Default 0.
|
|
82
|
+
* - `dynamicTyping` — auto-cast values to numbers / booleans / null.
|
|
83
|
+
* Default true.
|
|
84
|
+
* - `transform` — function( row ) → row, applied before `onMessage`;
|
|
85
|
+
* returning null/undefined drops the row (counted in `skipped`).
|
|
86
|
+
* A throw skips that one row with a per-record CALLBACK_FAILED
|
|
87
|
+
* report and the stream continues. Uniform with the MQTT source.
|
|
88
|
+
* - `onStatus` — observability callback (the ADR-018 status shape).
|
|
89
|
+
* Completion arrives here as `{phase: 'complete', count, skipped}` —
|
|
90
|
+
* the retired `onComplete` key is rejected as unknown (completion
|
|
91
|
+
* travels onStatus per ADR-018).
|
|
92
|
+
* - `shutdownOnComplete` — whether the source signals end-of-stream by
|
|
93
|
+
* triggering pipeline shutdown. Default true. (Inside a flow, the
|
|
94
|
+
* runtime overrides this to false and orchestrates the ordered drain
|
|
95
|
+
* itself; the option serves direct callers — ADR-018's flow-layer split.)
|
|
96
|
+
* - `idField` — column name to use for `startMsgId` / `endMsgId`
|
|
97
|
+
* matching. When omitted, the row index (0-based) is used.
|
|
98
|
+
* - `startMsgId`, `endMsgId` — inclusive range bounds. Number when
|
|
99
|
+
* matching against the row index; string when matching against an
|
|
100
|
+
* `idField` value.
|
|
101
|
+
*
|
|
102
|
+
* @type {Object}
|
|
103
|
+
*/
|
|
104
|
+
export const configSchema = {
|
|
105
|
+
// Unknown keys throw at DSL time — the only unknown-key rejection
|
|
106
|
+
// mechanism the validator has (validate.js:68-77). onMessage and
|
|
107
|
+
// onShutdown are runtime-injected, so they are deliberately absent.
|
|
108
|
+
_propertyNames: [
|
|
109
|
+
'path',
|
|
110
|
+
'delayMs',
|
|
111
|
+
'dynamicTyping',
|
|
112
|
+
'transform',
|
|
113
|
+
'onStatus',
|
|
114
|
+
'shutdownOnComplete',
|
|
115
|
+
'idField',
|
|
116
|
+
'startMsgId',
|
|
117
|
+
'endMsgId'
|
|
118
|
+
],
|
|
119
|
+
path: {
|
|
120
|
+
type: 'string',
|
|
121
|
+
required: true,
|
|
122
|
+
validator: validators.nonEmptyString,
|
|
123
|
+
error: 'path must be a non-empty string (file system path to the CSV file)'
|
|
124
|
+
},
|
|
125
|
+
delayMs: {
|
|
126
|
+
type: 'number',
|
|
127
|
+
required: false,
|
|
128
|
+
validator: validators.nonNegativeFinite,
|
|
129
|
+
error: 'delayMs must be a non-negative finite number'
|
|
130
|
+
},
|
|
131
|
+
dynamicTyping: {
|
|
132
|
+
type: 'boolean',
|
|
133
|
+
required: false,
|
|
134
|
+
error: 'dynamicTyping must be a boolean'
|
|
135
|
+
},
|
|
136
|
+
transform: {
|
|
137
|
+
type: 'function',
|
|
138
|
+
required: false,
|
|
139
|
+
error: 'transform must be a function( row ) returning a row (or null/undefined to drop)'
|
|
140
|
+
},
|
|
141
|
+
onStatus: {
|
|
142
|
+
type: 'function',
|
|
143
|
+
required: false,
|
|
144
|
+
error: 'onStatus must be a function'
|
|
145
|
+
},
|
|
146
|
+
shutdownOnComplete: {
|
|
147
|
+
type: 'boolean',
|
|
148
|
+
required: false,
|
|
149
|
+
error: 'shutdownOnComplete must be a boolean'
|
|
150
|
+
},
|
|
151
|
+
idField: {
|
|
152
|
+
type: 'string',
|
|
153
|
+
required: false,
|
|
154
|
+
validator: validators.nonEmptyString,
|
|
155
|
+
error: 'idField must be a non-empty string (CSV column name)'
|
|
156
|
+
},
|
|
157
|
+
startMsgId: {
|
|
158
|
+
required: false,
|
|
159
|
+
validator: isNumberOrNonEmptyString,
|
|
160
|
+
error: 'startMsgId must be a finite number or a non-empty string'
|
|
161
|
+
},
|
|
162
|
+
endMsgId: {
|
|
163
|
+
required: false,
|
|
164
|
+
validator: isNumberOrNonEmptyString,
|
|
165
|
+
error: 'endMsgId must be a finite number or a non-empty string'
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export { start };
|
|
170
|
+
|
|
171
|
+
export default { id, configSchema, durabilityClass, start };
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
// core/source-manager/csv/start.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview CSV streaming source implementation.
|
|
5
|
+
*
|
|
6
|
+
* Reads CSV file line-by-line using Node.js streams for memory efficiency.
|
|
7
|
+
* Each row is parsed and emitted as a message to the pipeline.
|
|
8
|
+
*
|
|
9
|
+
* Adapter contract (ADR-018, source role):
|
|
10
|
+
* - Every `onStatus` payload carries the uniform structured shape per
|
|
11
|
+
* ADR-018: `{status: 'green' | 'yellow' | 'red', connected, phase}`;
|
|
12
|
+
* the error path adds an `error: {code, message}` field. One payload
|
|
13
|
+
* rule, no exceptions (uniformity sweep, 2026-07-09). Callers without
|
|
14
|
+
* an `onStatus` handler still see error-path failures via a classified
|
|
15
|
+
* `console.error` line (fallback).
|
|
16
|
+
* - Lifecycle phases emitted (`status: 'green'` for all, since the file
|
|
17
|
+
* stream is healthy until it errors):
|
|
18
|
+
* `phase: 'starting'` — file open, includes `path`.
|
|
19
|
+
* `phase: 'headers'` — header line parsed, includes `headers` array.
|
|
20
|
+
* `phase: 'complete'` — file fully consumed; `connected: false` (stream
|
|
21
|
+
* closed). Includes the uniform `count` of
|
|
22
|
+
* produced messages (per ADR-018 there is no
|
|
23
|
+
* onComplete callback) and the `skipped` count
|
|
24
|
+
* of rows read but not delivered: structurally
|
|
25
|
+
* malformed, transform-dropped, or
|
|
26
|
+
* transform-failed. For in-range rows,
|
|
27
|
+
* count + skipped covers every data row read.
|
|
28
|
+
* `phase: 'errored'` — terminal red: the run loop failed and the
|
|
29
|
+
* stream is dead (the ADR-018 two-tier rule).
|
|
30
|
+
* `phase: 'stopped'` — forced stop exceeded its time budget
|
|
31
|
+
* (`status: 'yellow'`, with a `note`).
|
|
32
|
+
*
|
|
33
|
+
* `err.code` vocabulary (per-adapter; ADR-018 requires it documented here):
|
|
34
|
+
* - `SOURCE_UNREACHABLE` — file open failed (Node fs error code ENOENT,
|
|
35
|
+
* EACCES, or EISDIR). Classified at error time so consumers can route
|
|
36
|
+
* on code without parsing fs error strings.
|
|
37
|
+
* - `DECODE_ERROR` — one data row could not be parsed (field count
|
|
38
|
+
* does not match the header, or an unterminated quoted field). The row
|
|
39
|
+
* is skipped, the skip is signalled with `status: 'yellow'`, and the
|
|
40
|
+
* stream continues — per-record skip-classify-continue (ADR-018).
|
|
41
|
+
* A bad field VALUE in a parseable row is NOT a decode error;
|
|
42
|
+
* it passes through for the pipeline (`sanitize`) to judge.
|
|
43
|
+
* - `CALLBACK_FAILED` — the user's `transform` threw on one row. The
|
|
44
|
+
* row is skipped (counted in `skipped`), the throw is reported per
|
|
45
|
+
* record with `status: 'yellow'`, and the stream continues — user
|
|
46
|
+
* code is never reported as a stream failure. Fix the transform
|
|
47
|
+
* function; the report names the data row. Uniform with the MQTT
|
|
48
|
+
* source (transform contract, 2026-07-11).
|
|
49
|
+
* - `READ_ERROR` — stream read failed mid-stream (encoding error,
|
|
50
|
+
* truncation, decoder failure, etc.). Catch-all for non-open failures.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
import fs from 'node:fs';
|
|
54
|
+
import readline from 'node:readline';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Detects delimiter from header line.
|
|
58
|
+
* @param {string} headerLine - First line of CSV
|
|
59
|
+
* @returns {string} Detected delimiter
|
|
60
|
+
*/
|
|
61
|
+
const detectDelimiter = function ( headerLine ) {
|
|
62
|
+
if ( headerLine.includes( '\t' ) ) return '\t';
|
|
63
|
+
if ( headerLine.includes( ';' ) ) return ';';
|
|
64
|
+
return ',';
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Parses a CSV line into values.
|
|
69
|
+
*
|
|
70
|
+
* Also reports whether the line ended inside an open quote — a
|
|
71
|
+
* structural fault the caller classifies as `DECODE_ERROR` (ADR-018).
|
|
72
|
+
*
|
|
73
|
+
* @param {string} line - CSV line
|
|
74
|
+
* @param {string} delimiter - Field delimiter
|
|
75
|
+
* @returns {{values: string[], unterminated: boolean}} Parsed values
|
|
76
|
+
* and the unterminated-quote flag
|
|
77
|
+
*/
|
|
78
|
+
const parseLine = function ( line, delimiter ) {
|
|
79
|
+
const values = [];
|
|
80
|
+
let current = '';
|
|
81
|
+
let inQuotes = false;
|
|
82
|
+
|
|
83
|
+
for ( let i = 0; i < line.length; i += 1 ) {
|
|
84
|
+
const char = line[ i ];
|
|
85
|
+
|
|
86
|
+
if ( char === '"' ) {
|
|
87
|
+
inQuotes = !inQuotes;
|
|
88
|
+
} else if ( char === delimiter && !inQuotes ) {
|
|
89
|
+
values.push( current.trim() );
|
|
90
|
+
current = '';
|
|
91
|
+
} else {
|
|
92
|
+
current += char;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
values.push( current.trim() );
|
|
96
|
+
|
|
97
|
+
return { values, unterminated: inQuotes };
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Names the structural fault in a data row, or returns null when the
|
|
102
|
+
* row is structurally sound (the ADR-018 boundary: field-count
|
|
103
|
+
* mismatch and an unterminated quote are the source's DECODE_ERROR;
|
|
104
|
+
* a bad field VALUE inside a parseable row is the pipeline's concern
|
|
105
|
+
* and passes through).
|
|
106
|
+
*
|
|
107
|
+
* @param {{values: string[], unterminated: boolean}} parsed - parseLine result
|
|
108
|
+
* @param {string[]} headers - Column headers
|
|
109
|
+
* @returns {string|null} Fault description, or null when sound
|
|
110
|
+
*/
|
|
111
|
+
const structuralFault = function ( parsed, headers ) {
|
|
112
|
+
if ( parsed.unterminated ) {
|
|
113
|
+
return 'unterminated quoted field';
|
|
114
|
+
}
|
|
115
|
+
if ( parsed.values.length !== headers.length ) {
|
|
116
|
+
return `expected ${headers.length} fields, got ${parsed.values.length}`;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Casts string value to appropriate type.
|
|
123
|
+
* @param {string} value - String value
|
|
124
|
+
* @returns {*} Typed value
|
|
125
|
+
*/
|
|
126
|
+
const castValue = function ( value ) {
|
|
127
|
+
if ( value === '' ) return null;
|
|
128
|
+
if ( value.toLowerCase() === 'true' ) return true;
|
|
129
|
+
if ( value.toLowerCase() === 'false' ) return false;
|
|
130
|
+
|
|
131
|
+
const num = Number( value );
|
|
132
|
+
if ( !Number.isNaN( num ) ) return num;
|
|
133
|
+
|
|
134
|
+
return value;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Builds a row object from parsed CSV values.
|
|
139
|
+
* @param {string[]} headers - Column headers
|
|
140
|
+
* @param {string[]} values - Parsed values for this row
|
|
141
|
+
* @param {boolean} dynamicTyping - Whether to auto-cast values
|
|
142
|
+
* @returns {Object} Row object with header keys
|
|
143
|
+
*/
|
|
144
|
+
const buildRow = function ( headers, values, dynamicTyping ) {
|
|
145
|
+
const row = Object.create( null );
|
|
146
|
+
|
|
147
|
+
for ( let i = 0; i < headers.length; i += 1 ) {
|
|
148
|
+
let value = values[ i ];
|
|
149
|
+
if ( dynamicTyping ) {
|
|
150
|
+
value = castValue( value );
|
|
151
|
+
}
|
|
152
|
+
row[ headers[ i ] ] = value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return row;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Checks range filter status for a row.
|
|
160
|
+
* @param {*} rangeKey - Current row's range key (field value or index)
|
|
161
|
+
* @param {boolean} wasInRange - Previous inRange state
|
|
162
|
+
* @param {*} startMsgId - Start of range (inclusive)
|
|
163
|
+
* @param {*} endMsgId - End of range (inclusive)
|
|
164
|
+
* @returns {{inRange: boolean, shouldStop: boolean}} Range status
|
|
165
|
+
*/
|
|
166
|
+
const checkRange = function ( rangeKey, wasInRange, startMsgId, endMsgId ) {
|
|
167
|
+
let inRange = wasInRange;
|
|
168
|
+
|
|
169
|
+
// Check if we should start processing
|
|
170
|
+
if ( !inRange && startMsgId !== null && rangeKey >= startMsgId ) {
|
|
171
|
+
inRange = true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check if we should stop after this row
|
|
175
|
+
const shouldStop = endMsgId !== null && rangeKey >= endMsgId;
|
|
176
|
+
|
|
177
|
+
return { inRange, shouldStop };
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Starts streaming CSV source.
|
|
182
|
+
*
|
|
183
|
+
* @param {Object} config - Source configuration
|
|
184
|
+
* @param {string} config.path - Path to CSV file
|
|
185
|
+
* @param {Function} config.onMessage - Message handler
|
|
186
|
+
* @param {number} [config.delayMs=0] - Delay between messages (ms)
|
|
187
|
+
* @param {boolean} [config.dynamicTyping=true] - Auto-cast values
|
|
188
|
+
* @param {Function} [config.transform] - Optional row transform:
|
|
189
|
+
* ( row ) => row; return null/undefined to drop (counted in skipped);
|
|
190
|
+
* a throw skips the row (CALLBACK_FAILED) and the stream continues
|
|
191
|
+
* @param {Function} [config.onStatus] - Status callback; completion arrives
|
|
192
|
+
* here as `{phase: 'complete', count, skipped}` (per ADR-018 there is
|
|
193
|
+
* no onComplete callback)
|
|
194
|
+
* @param {Function} [config.onShutdown] - Shutdown handler (injected by runtime)
|
|
195
|
+
* @param {boolean} [config.shutdownOnComplete=true] - Auto-shutdown pipeline when CSV ends
|
|
196
|
+
* @param {string} [config.idField] - Field to use for range matching (row index if omitted)
|
|
197
|
+
* @param {number|string} [config.startMsgId] - Start processing at this id/row (inclusive)
|
|
198
|
+
* @param {number|string} [config.endMsgId] - Stop processing after this id/row (inclusive)
|
|
199
|
+
* @returns {Function} Stop function
|
|
200
|
+
*/
|
|
201
|
+
export const start = function ( config ) {
|
|
202
|
+
const {
|
|
203
|
+
path,
|
|
204
|
+
onMessage,
|
|
205
|
+
delayMs = 0,
|
|
206
|
+
dynamicTyping = true,
|
|
207
|
+
transform = null,
|
|
208
|
+
onStatus = null,
|
|
209
|
+
onShutdown = null,
|
|
210
|
+
shutdownOnComplete = true,
|
|
211
|
+
idField = null,
|
|
212
|
+
startMsgId = null,
|
|
213
|
+
endMsgId = null
|
|
214
|
+
} = config;
|
|
215
|
+
|
|
216
|
+
let stopped = false;
|
|
217
|
+
let rowCount = 0;
|
|
218
|
+
let rowIndex = 0;
|
|
219
|
+
let skippedCount = 0;
|
|
220
|
+
let inRange = ( startMsgId === null ); // Start immediately if no startMsgId
|
|
221
|
+
|
|
222
|
+
// Per-record skip-classify-continue (ADR-018): a structurally
|
|
223
|
+
// malformed row is skipped and signalled — never a silent drop. When
|
|
224
|
+
// the caller owns reporting (onStatus supplied) the framework stays
|
|
225
|
+
// quiet; without one, the classified console.error keeps the skip
|
|
226
|
+
// visible (ADR-018's two-party rule).
|
|
227
|
+
const reportDecodeError = function ( dataRowIndex, fault ) {
|
|
228
|
+
const message = `data row ${dataRowIndex}: ${fault} — row skipped`;
|
|
229
|
+
if ( onStatus ) {
|
|
230
|
+
onStatus( {
|
|
231
|
+
status: 'yellow',
|
|
232
|
+
connected: true,
|
|
233
|
+
phase: 'running',
|
|
234
|
+
error: { code: 'DECODE_ERROR', message }
|
|
235
|
+
} );
|
|
236
|
+
} else {
|
|
237
|
+
console.error( `CSV source error [DECODE_ERROR]: ${message}` );
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// A throwing transform is user code, never a stream failure: the
|
|
242
|
+
// row is skipped with a per-record CALLBACK_FAILED report and the
|
|
243
|
+
// stream continues (transform contract, uniform with the MQTT
|
|
244
|
+
// source, 2026-07-11). Same two-party reporting rule as decode
|
|
245
|
+
// errors above.
|
|
246
|
+
const reportCallbackError = function ( dataRowIndex, err ) {
|
|
247
|
+
const message = `data row ${dataRowIndex}: transform threw: ${err.message} — row skipped`;
|
|
248
|
+
if ( onStatus ) {
|
|
249
|
+
onStatus( {
|
|
250
|
+
status: 'yellow',
|
|
251
|
+
connected: true,
|
|
252
|
+
phase: 'running',
|
|
253
|
+
error: { code: 'CALLBACK_FAILED', message }
|
|
254
|
+
} );
|
|
255
|
+
} else {
|
|
256
|
+
console.error( `CSV source error [CALLBACK_FAILED]: ${message}` );
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Run the user's transform under guard. Returns the transformed
|
|
261
|
+
// row; null when the transform threw (reported above) — the caller
|
|
262
|
+
// counts every null/undefined result as skipped.
|
|
263
|
+
const applyTransform = function ( row, dataRowIndex ) {
|
|
264
|
+
if ( !transform ) {
|
|
265
|
+
return row;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
return transform( row );
|
|
269
|
+
} catch ( err ) {
|
|
270
|
+
reportCallbackError( dataRowIndex, err );
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// Holds the file stream so the stop function can close it if the
|
|
276
|
+
// read loop runs out of time. `finished` is a Promise that turns
|
|
277
|
+
// ready as soon as the loop exits, success or error. Together they
|
|
278
|
+
// give the stop function its time-budget behaviour, the same shape
|
|
279
|
+
// sinks use per ADR-018.
|
|
280
|
+
let activeStream = null;
|
|
281
|
+
let resolveFinished;
|
|
282
|
+
const finished = new Promise( ( resolve ) => {
|
|
283
|
+
resolveFinished = resolve;
|
|
284
|
+
} );
|
|
285
|
+
|
|
286
|
+
const run = async function () {
|
|
287
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- `path` comes from the caller-supplied CSV source config (`config.path`). This adapter does not validate path provenance; per composer's adapter contract the caller (flow runtime + its configuration source) owns that. Sufficient under zone=internal (SECURITY-MODEL.md §2); re-evaluate if CSV source is ever exposed to untrusted configuration.
|
|
288
|
+
const fileStream = fs.createReadStream( path, { encoding: 'utf8' } );
|
|
289
|
+
activeStream = fileStream;
|
|
290
|
+
const rl = readline.createInterface( { input: fileStream, crlfDelay: Infinity } );
|
|
291
|
+
|
|
292
|
+
let headers = null;
|
|
293
|
+
let delimiter = ',';
|
|
294
|
+
|
|
295
|
+
if ( onStatus ) onStatus( {
|
|
296
|
+
status: 'green',
|
|
297
|
+
connected: true,
|
|
298
|
+
phase: 'starting',
|
|
299
|
+
path
|
|
300
|
+
} );
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
for await ( const line of rl ) {
|
|
304
|
+
if ( stopped ) break;
|
|
305
|
+
if ( !line.trim() ) continue; // eslint-disable-line no-continue
|
|
306
|
+
|
|
307
|
+
// First line is header
|
|
308
|
+
if ( !headers ) {
|
|
309
|
+
delimiter = detectDelimiter( line );
|
|
310
|
+
headers = parseLine( line, delimiter ).values;
|
|
311
|
+
if ( onStatus ) onStatus( {
|
|
312
|
+
status: 'green',
|
|
313
|
+
connected: true,
|
|
314
|
+
phase: 'headers',
|
|
315
|
+
headers
|
|
316
|
+
} );
|
|
317
|
+
continue; // eslint-disable-line no-continue
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Parse the data row; a structurally malformed row is
|
|
321
|
+
// skipped, classified, and the stream continues (ADR-018).
|
|
322
|
+
// The skipped row still occupies its position — rowIndex
|
|
323
|
+
// advances so positional range filtering stays stable.
|
|
324
|
+
const parsed = parseLine( line, delimiter );
|
|
325
|
+
const fault = structuralFault( parsed, headers );
|
|
326
|
+
if ( fault ) {
|
|
327
|
+
skippedCount += 1;
|
|
328
|
+
reportDecodeError( rowIndex, fault );
|
|
329
|
+
rowIndex += 1;
|
|
330
|
+
continue; // eslint-disable-line no-continue
|
|
331
|
+
}
|
|
332
|
+
const row = buildRow( headers, parsed.values, dynamicTyping );
|
|
333
|
+
|
|
334
|
+
// Determine range key and check filter
|
|
335
|
+
const rangeKey = idField ? row[ idField ] : rowIndex;
|
|
336
|
+
const rangeStatus = checkRange( rangeKey, inRange, startMsgId, endMsgId );
|
|
337
|
+
inRange = rangeStatus.inRange;
|
|
338
|
+
|
|
339
|
+
// Process only if in range
|
|
340
|
+
if ( inRange ) {
|
|
341
|
+
const msg = applyTransform( row, rowIndex );
|
|
342
|
+
|
|
343
|
+
// Only null/undefined mean drop — any other return,
|
|
344
|
+
// however falsy, is delivered (transform contract,
|
|
345
|
+
// uniform with the MQTT source, 2026-07-11).
|
|
346
|
+
if ( msg !== null && msg !== undefined ) {
|
|
347
|
+
await onMessage( msg );
|
|
348
|
+
rowCount += 1;
|
|
349
|
+
} else {
|
|
350
|
+
skippedCount += 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if ( delayMs > 0 && !stopped ) {
|
|
354
|
+
await new Promise( ( resolve ) => setTimeout( resolve, delayMs ) );
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if ( rangeStatus.shouldStop ) break;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
rowIndex += 1;
|
|
361
|
+
}
|
|
362
|
+
} finally {
|
|
363
|
+
activeStream = null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Completion travels onStatus with the uniform `count` field
|
|
367
|
+
// (per ADR-018 there is no onComplete callback).
|
|
368
|
+
if ( onStatus ) onStatus( {
|
|
369
|
+
status: 'green',
|
|
370
|
+
connected: false,
|
|
371
|
+
phase: 'complete',
|
|
372
|
+
count: rowCount,
|
|
373
|
+
skipped: skippedCount
|
|
374
|
+
} );
|
|
375
|
+
|
|
376
|
+
// Auto-shutdown pipeline if requested
|
|
377
|
+
if ( shutdownOnComplete && onShutdown ) {
|
|
378
|
+
await onShutdown();
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// Start async processing. Failures inside the run loop are classified
|
|
383
|
+
// and routed through onStatus per ADR-018; callers without an
|
|
384
|
+
// onStatus handler still see the failure via a classified console.error
|
|
385
|
+
// (fallback) so a misconfigured pipeline is never silently swallowed.
|
|
386
|
+
// The `finished` Promise resolves once the run loop exits (success or
|
|
387
|
+
// error) so `stopFn({ timeout })` can wait on it.
|
|
388
|
+
run().catch( ( err ) => {
|
|
389
|
+
const fsCode = err && err.code;
|
|
390
|
+
const code = ( fsCode === 'ENOENT' || fsCode === 'EACCES' || fsCode === 'EISDIR' ) ?
|
|
391
|
+
'SOURCE_UNREACHABLE' :
|
|
392
|
+
'READ_ERROR';
|
|
393
|
+
const message = ( err && err.message ) ? err.message : String( err );
|
|
394
|
+
if ( onStatus ) {
|
|
395
|
+
// Terminal red: the stream is dead. Uniform payload with
|
|
396
|
+
// phase 'errored' per the ADR-018 two-tier rule.
|
|
397
|
+
onStatus( {
|
|
398
|
+
status: 'red',
|
|
399
|
+
connected: false,
|
|
400
|
+
phase: 'errored',
|
|
401
|
+
error: { code, message }
|
|
402
|
+
} );
|
|
403
|
+
} else {
|
|
404
|
+
console.error( `CSV source error [${code}]: ${message}` );
|
|
405
|
+
}
|
|
406
|
+
} ).finally( () => {
|
|
407
|
+
resolveFinished();
|
|
408
|
+
} );
|
|
409
|
+
|
|
410
|
+
// Stop the source, with a time budget. Per ADR-018.
|
|
411
|
+
//
|
|
412
|
+
// What it does:
|
|
413
|
+
// - Asks the read loop to stop at the next row by setting `stopped`.
|
|
414
|
+
// - Waits for the loop to finish reading. If it finishes within
|
|
415
|
+
// `timeout` ms, the stop returns right away.
|
|
416
|
+
// - If the loop is stuck (for example, waiting forever inside
|
|
417
|
+
// `onMessage(msg)`), the time budget runs out. We then close
|
|
418
|
+
// the file and let the stop return anyway, so the rest of the
|
|
419
|
+
// shutdown can proceed.
|
|
420
|
+
// - When the budget runs out, we send a yellow status so callers
|
|
421
|
+
// know the stop was forced rather than clean.
|
|
422
|
+
//
|
|
423
|
+
// The timer is `unref()`ed so it does not keep Node alive while
|
|
424
|
+
// the loop is finishing in time.
|
|
425
|
+
//
|
|
426
|
+
// Default time budget (5000 ms) matches what sinks use, so the
|
|
427
|
+
// whole pipeline shutdown has consistent timing.
|
|
428
|
+
return function ( { timeout = 5000 } = {} ) {
|
|
429
|
+
stopped = true;
|
|
430
|
+
return new Promise( ( resolve ) => {
|
|
431
|
+
let settled = false;
|
|
432
|
+
let forceTimer = null;
|
|
433
|
+
const settle = function () {
|
|
434
|
+
if ( settled ) return;
|
|
435
|
+
settled = true;
|
|
436
|
+
if ( forceTimer ) clearTimeout( forceTimer );
|
|
437
|
+
resolve();
|
|
438
|
+
};
|
|
439
|
+
forceTimer = setTimeout( () => {
|
|
440
|
+
if ( activeStream ) activeStream.destroy();
|
|
441
|
+
if ( onStatus ) {
|
|
442
|
+
onStatus( {
|
|
443
|
+
status: 'yellow',
|
|
444
|
+
connected: false,
|
|
445
|
+
phase: 'stopped',
|
|
446
|
+
note: `Stop took longer than ${timeout}ms — forced.`
|
|
447
|
+
} );
|
|
448
|
+
}
|
|
449
|
+
settle();
|
|
450
|
+
}, timeout );
|
|
451
|
+
forceTimer.unref();
|
|
452
|
+
finished.then( settle, settle );
|
|
453
|
+
} );
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
|