@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,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tunable resolution for groupBy expansion.
|
|
3
|
+
*
|
|
4
|
+
* When a tunable (function with `.semantics`) has `semantics.field` matching
|
|
5
|
+
* the groupBy field, we resolve it to the concrete value for that group.
|
|
6
|
+
*
|
|
7
|
+
* Example:
|
|
8
|
+
* lookupByField( 'rpmBand', { idle: 3.4, low: 3.2 } )
|
|
9
|
+
* For group 'idle' → resolves to 3.4
|
|
10
|
+
* For group 'low' → resolves to 3.2
|
|
11
|
+
*
|
|
12
|
+
* Tunables with different fields are preserved for runtime evaluation:
|
|
13
|
+
* lookupByField( 'tempRegime', { warm: 0.02, hot: 0.05 } )
|
|
14
|
+
* Preserved as-is regardless of groupBy field
|
|
15
|
+
*
|
|
16
|
+
* @see ADR-006: Tunable Pattern for Dynamic Parameters
|
|
17
|
+
* @see ADR-007: groupBy DSL Construct
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Recursively resolves tunables in a value structure.
|
|
22
|
+
*
|
|
23
|
+
* For `lookupByField` tunables where `semantics.field === groupByField`:
|
|
24
|
+
* - Resolves to the mapped value for the group
|
|
25
|
+
* - Falls back to semantics.default if group value not in map
|
|
26
|
+
*
|
|
27
|
+
* All other values pass through unchanged (including non-matching tunables).
|
|
28
|
+
*
|
|
29
|
+
* @param {*} value - Value to process (may contain tunables)
|
|
30
|
+
* @param {string} groupByField - The field used for grouping (e.g., 'rpmBand')
|
|
31
|
+
* @param {string|number} groupValue - The group value to resolve for (e.g., 'idle')
|
|
32
|
+
* @returns {*} Value with matching tunables resolved
|
|
33
|
+
*/
|
|
34
|
+
const resolveTunablesInValue = function ( value, groupByField, groupValue ) {
|
|
35
|
+
// Check if this is a resolvable tunable
|
|
36
|
+
if ( typeof value === 'function' && value.semantics ) {
|
|
37
|
+
const { type, field, map } = value.semantics;
|
|
38
|
+
if ( type === 'lookupByField' && field === groupByField ) {
|
|
39
|
+
// Resolve: map[groupValue] or default
|
|
40
|
+
return map[ groupValue ] ?? value.semantics.default;
|
|
41
|
+
}
|
|
42
|
+
// Non-matching tunable - preserve for runtime
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Functions without semantics - preserve for runtime
|
|
47
|
+
if ( typeof value === 'function' ) {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Null/undefined/primitives pass through
|
|
52
|
+
if ( value === null || value === undefined || typeof value !== 'object' ) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Arrays - recurse into each element
|
|
57
|
+
if ( Array.isArray( value ) ) {
|
|
58
|
+
const result = [];
|
|
59
|
+
for ( let i = 0; i < value.length; i += 1 ) {
|
|
60
|
+
result.push( resolveTunablesInValue( value[ i ], groupByField, groupValue ) );
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Objects - recurse into each property
|
|
66
|
+
const result = Object.create( null );
|
|
67
|
+
const keys = Object.keys( value );
|
|
68
|
+
for ( let i = 0; i < keys.length; i += 1 ) {
|
|
69
|
+
const key = keys[ i ];
|
|
70
|
+
result[ key ] = resolveTunablesInValue( value[ key ], groupByField, groupValue );
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export { resolveTunablesInValue };
|
package/src/flow/run.js
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// flow/run.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Runtime bootstrap for direct pipeline execution.
|
|
5
|
+
*
|
|
6
|
+
* Orchestrates: module loading, emitter wiring, partition manager setup,
|
|
7
|
+
* source initialization, and graceful shutdown coordination.
|
|
8
|
+
*
|
|
9
|
+
* Supports two modes:
|
|
10
|
+
* - Single-pipeline: Traditional flow with specs array (backward compatible)
|
|
11
|
+
* - Multi-specialization: Switch/case flow with specsByCase object
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { loadNodeModules } from './load-node-module.js';
|
|
15
|
+
import { validateFlowOrThrow } from './validate.js';
|
|
16
|
+
import * as partitionManager from '../core/partition-manager/index.js';
|
|
17
|
+
import { wireLinearGraph, emitters, storages, assertModuleDurability } from '../core/wiring/index.js';
|
|
18
|
+
import shutdownManager from '../core/shutdown-manager/index.js';
|
|
19
|
+
import { ENV_VARS } from '../core/env-vars.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Wires and starts the pipeline for direct execution.
|
|
23
|
+
* Handles both single-pipeline and multi-specialization modes.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} flowName - Name of the flow
|
|
26
|
+
* @param {Array|Object} specsOrSpecsByCase - Node specs array OR specsByCase object
|
|
27
|
+
* @param {Set} importSet - Set of node names used
|
|
28
|
+
* @param {Object} runtime - Runtime configuration
|
|
29
|
+
* @param {Array} [caseOrder] - Order of case keys (only for multi-specialization)
|
|
30
|
+
* @returns {Promise<Object>} Pipeline handle with shutdown method
|
|
31
|
+
*/
|
|
32
|
+
export const runFlow = async function ( flowName, specsOrSpecsByCase, importSet, runtime, caseOrder ) {
|
|
33
|
+
// Determine if this is multi-specialization mode
|
|
34
|
+
const isMultiSpec = caseOrder !== undefined && !Array.isArray( specsOrSpecsByCase );
|
|
35
|
+
|
|
36
|
+
// 1. Load node modules dynamically
|
|
37
|
+
const nodeNames = Array.from( importSet );
|
|
38
|
+
const nodeModules = await loadNodeModules( nodeNames );
|
|
39
|
+
|
|
40
|
+
// Convert Map to plain object for compatibility
|
|
41
|
+
const nodeModulesObj = Object.create( null );
|
|
42
|
+
for ( const [ name, mod ] of nodeModules ) {
|
|
43
|
+
nodeModulesObj[ name ] = mod;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Build emitter configs and modules for wiring
|
|
47
|
+
const emitterConfigs = Object.create( null );
|
|
48
|
+
const emitterModules = Object.create( null );
|
|
49
|
+
for ( const [ id, { adapter, config } ] of Object.entries( runtime.emitters ) ) {
|
|
50
|
+
emitterConfigs[ id ] = config;
|
|
51
|
+
emitterModules[ id ] = adapter;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3. Build storage configs and modules for wiring (optional)
|
|
55
|
+
const storageConfigs = Object.create( null );
|
|
56
|
+
const storageModules = Object.create( null );
|
|
57
|
+
const runtimeStorages = runtime.storages || Object.create( null );
|
|
58
|
+
for ( const [ id, { adapter, config } ] of Object.entries( runtimeStorages ) ) {
|
|
59
|
+
storageConfigs[ id ] = config;
|
|
60
|
+
storageModules[ id ] = adapter;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let specsBySpecialization;
|
|
64
|
+
let graphs;
|
|
65
|
+
|
|
66
|
+
// emitterModules / storageModules are already `{ id: adapterModule }`
|
|
67
|
+
// maps (built above from runtime.emitters / runtime.storages). They
|
|
68
|
+
// serve double duty: cross-flow target validation (existence check
|
|
69
|
+
// via `id in modules`) and capability-driven semanticsRequirement
|
|
70
|
+
// checks (read `module.semanticsRequirement`).
|
|
71
|
+
// validateFlowOrThrow takes these maps directly.
|
|
72
|
+
|
|
73
|
+
if ( isMultiSpec ) {
|
|
74
|
+
// Multi-specialization mode
|
|
75
|
+
specsBySpecialization = specsOrSpecsByCase;
|
|
76
|
+
|
|
77
|
+
// Validate all specializations
|
|
78
|
+
for ( let i = 0; i < caseOrder.length; i += 1 ) {
|
|
79
|
+
const key = caseOrder[ i ];
|
|
80
|
+
validateFlowOrThrow( `${flowName}:${key}`, specsBySpecialization[ key ], nodeModulesObj, emitterModules, storageModules, runtime.assetClass );
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Wire emitters and storages across all specializations
|
|
84
|
+
for ( let i = 0; i < caseOrder.length; i += 1 ) {
|
|
85
|
+
const key = caseOrder[ i ];
|
|
86
|
+
await emitters.wire( specsBySpecialization[ key ], emitterConfigs, emitterModules, runtime.assetClass ); // eslint-disable-line no-await-in-loop
|
|
87
|
+
await storages.wire( specsBySpecialization[ key ], storageConfigs, storageModules, runtime.assetClass ); // eslint-disable-line no-await-in-loop
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Wire graphs for each specialization
|
|
91
|
+
graphs = Object.create( null );
|
|
92
|
+
for ( let i = 0; i < caseOrder.length; i += 1 ) {
|
|
93
|
+
const key = caseOrder[ i ];
|
|
94
|
+
graphs[ key ] = wireLinearGraph( specsBySpecialization[ key ], nodeModulesObj );
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
// Single-pipeline mode (backward compatible)
|
|
98
|
+
const specs = specsOrSpecsByCase;
|
|
99
|
+
|
|
100
|
+
// Validate flow
|
|
101
|
+
validateFlowOrThrow( flowName, specs, nodeModulesObj, emitterModules, storageModules, runtime.assetClass );
|
|
102
|
+
|
|
103
|
+
// Wire emitters and storages
|
|
104
|
+
await emitters.wire( specs, emitterConfigs, emitterModules, runtime.assetClass );
|
|
105
|
+
await storages.wire( specs, storageConfigs, storageModules, runtime.assetClass );
|
|
106
|
+
|
|
107
|
+
// Build specsBySpecialization with single entry at key 0
|
|
108
|
+
specsBySpecialization = { 0: specs };
|
|
109
|
+
|
|
110
|
+
// Wire single graph
|
|
111
|
+
graphs = { 0: wireLinearGraph( specs, nodeModulesObj ) };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 3. Build flow config for partition manager
|
|
115
|
+
const flow = {
|
|
116
|
+
specsBySpecialization,
|
|
117
|
+
nodeModules: nodeModulesObj,
|
|
118
|
+
partitionField: runtime.partitionField || null,
|
|
119
|
+
specializationField: runtime.specializationField || null,
|
|
120
|
+
// Default comes from YIELD_TIME_THRESHOLD_MS (500 ms, ADR-024);
|
|
121
|
+
// `.yield( { threshold } )` overrides it per flow.
|
|
122
|
+
yieldThreshold: runtime.yieldThreshold ?? ENV_VARS.yieldTimeThresholdMs
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// 4. Initialize partition manager
|
|
126
|
+
const composerState = partitionManager.init( flow );
|
|
127
|
+
|
|
128
|
+
// 4a. Populate the backpressure-aware sinks registry. Wire-emitters
|
|
129
|
+
// and wire-storages each return the subset of their singletons that
|
|
130
|
+
// expose `getPressure()`, keyed 'emitter:<target>' /
|
|
131
|
+
// 'storage:<storageName>'. Nothing reads the registry yet — the
|
|
132
|
+
// pressure-aware yield decision (ADR-020, Draft) will iterate it
|
|
133
|
+
// when it lands; the assembly here keeps the partition manager's
|
|
134
|
+
// view ready for that.
|
|
135
|
+
Object.assign(
|
|
136
|
+
composerState.partitionState.backpressureAwareSinks,
|
|
137
|
+
emitters.getBackpressureAwareSinks(),
|
|
138
|
+
storages.getBackpressureAwareSinks()
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// ========================================================================
|
|
142
|
+
// 5. MESSAGE DISPATCH — SYNC-FIRST HOT PATH (ADR-013, ADR-024)
|
|
143
|
+
// ========================================================================
|
|
144
|
+
//
|
|
145
|
+
// processMessage is deliberately NOT an `async function`. Wrapping it in
|
|
146
|
+
// `async/await` would allocate a Promise and schedule two microtask hops
|
|
147
|
+
// on EVERY message (~155 ns/msg measured overhead — ADR-013).
|
|
148
|
+
//
|
|
149
|
+
// Every message is processed synchronously, in arrival order. When the
|
|
150
|
+
// yield threshold fires, pmUpdate sets `partitionState.yieldPending`
|
|
151
|
+
// after handing back the graph — the message is processed first, the
|
|
152
|
+
// breath comes after (ADR-024). processMessage then returns a Promise
|
|
153
|
+
// that resolves on the next setImmediate turn, so an awaiting caller
|
|
154
|
+
// gives the event loop a chance to run background I/O (MQTT drain,
|
|
155
|
+
// QuestDB flush timers, stdout). Processing before breathing means a
|
|
156
|
+
// caller that ignores the Promise (a push source such as the MQTT
|
|
157
|
+
// subscriber) can never see messages update a partition out of order.
|
|
158
|
+
//
|
|
159
|
+
// The caller contract (ADR-013) is unchanged: `undefined` on the hot
|
|
160
|
+
// path, a Promise on the yield tick; `await` works with both, and the
|
|
161
|
+
// Promise never rejects — a pipeline fault throws synchronously here,
|
|
162
|
+
// the same as on every other message.
|
|
163
|
+
//
|
|
164
|
+
// The default threshold comes from YIELD_TIME_THRESHOLD_MS (500 ms);
|
|
165
|
+
// `.yield( { threshold } )` overrides it per flow, and Infinity turns
|
|
166
|
+
// yielding off. The yield only matters to callers that wait on this
|
|
167
|
+
// function — a push source breathes with the event loop on its own.
|
|
168
|
+
//
|
|
169
|
+
// This extends ADR-004's zero-allocation hot-path principle to the flow
|
|
170
|
+
// wrapper layer: one boolean read per message; the Promise is allocated
|
|
171
|
+
// only on the yield tick.
|
|
172
|
+
// ========================================================================
|
|
173
|
+
|
|
174
|
+
// Graph selection uses same field as partition manager for consistency.
|
|
175
|
+
const getSpecializationType = runtime.specializationField ?
|
|
176
|
+
( msg ) => msg[ runtime.specializationField ] :
|
|
177
|
+
() => 0;
|
|
178
|
+
|
|
179
|
+
// Pipeline dispatch. Defined once at wire time, reused every message.
|
|
180
|
+
const runPipeline = function ( graph, msg ) {
|
|
181
|
+
const type = getSpecializationType( msg );
|
|
182
|
+
const pipeline = graphs[ type ];
|
|
183
|
+
if ( pipeline ) {
|
|
184
|
+
pipeline( graph, msg );
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const partitionState = composerState.partitionState;
|
|
189
|
+
|
|
190
|
+
const processMessage = function ( msg ) {
|
|
191
|
+
const graph = partitionManager.update( composerState, msg );
|
|
192
|
+
|
|
193
|
+
// Process first — pmUpdate always returns the graph synchronously
|
|
194
|
+
// (or null for a dropped message, which never sets the flag).
|
|
195
|
+
if ( graph ) {
|
|
196
|
+
runPipeline( graph, msg );
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Breathe after (yield tick, rare): hand the awaiting caller a
|
|
200
|
+
// Promise that resolves once the event loop has had a full turn.
|
|
201
|
+
if ( partitionState.yieldPending ) {
|
|
202
|
+
partitionState.yieldPending = false;
|
|
203
|
+
return new Promise( ( resolve ) => {
|
|
204
|
+
setImmediate( resolve );
|
|
205
|
+
} );
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return undefined;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// 6. Setup the flow's shutdown closure.
|
|
212
|
+
//
|
|
213
|
+
// Per ADR-018, the flow layer owns the orchestrated drain
|
|
214
|
+
// (source → emitters → storages).
|
|
215
|
+
// OS signals (SIGINT/SIGTERM) and the top-level forced-shutdown
|
|
216
|
+
// timeout live in the process layer (`shutdown-manager`); we
|
|
217
|
+
// auto-register this handle there a few lines down so signal
|
|
218
|
+
// routing works without the caller wiring anything up.
|
|
219
|
+
//
|
|
220
|
+
// Concurrent-safe: a second `shutdown()` call while the first is
|
|
221
|
+
// still draining returns the same in-flight Promise. Callers that
|
|
222
|
+
// `await handle.shutdown()` after a concurrent caller has already
|
|
223
|
+
// started see the actual drain finish, not a premature resolve.
|
|
224
|
+
// The `unregister()` call also detaches this handle from
|
|
225
|
+
// shutdown-manager so a SIGINT after a direct `handle.shutdown()`
|
|
226
|
+
// doesn't re-invoke a torn-down flow.
|
|
227
|
+
//
|
|
228
|
+
// Natural-completion signal: `handle.whenComplete()` returns a
|
|
229
|
+
// Promise that resolves when either:
|
|
230
|
+
// - the source emits a `phase: 'complete'` status (finite
|
|
231
|
+
// sources reach their natural end), or
|
|
232
|
+
// - shutdown is called (forced exit).
|
|
233
|
+
// For infinite sources (the MQTT subscriber), only the second
|
|
234
|
+
// path resolves it. Test pattern:
|
|
235
|
+
//
|
|
236
|
+
// const handle = await flow(...).source(...).run();
|
|
237
|
+
// await handle.whenComplete(); // wait for the source to finish
|
|
238
|
+
// await handle.shutdown(); // drain the sinks
|
|
239
|
+
//
|
|
240
|
+
// Auto-shutdown on natural completion is owned by the runtime, not
|
|
241
|
+
// the source. When the source emits `phase: 'complete'` we trigger
|
|
242
|
+
// `shutdown()` here — fire-and-forget, since shutdown is
|
|
243
|
+
// concurrent-safe. The source's own `shutdownOnComplete` config is
|
|
244
|
+
// overridden to `false` for in-flow use so the source never invokes
|
|
245
|
+
// `onShutdown` itself; that prevents the recursion where the
|
|
246
|
+
// source's run() awaits onShutdown which awaits stopSource which
|
|
247
|
+
// awaits the source's run(). Source-level `shutdownOnComplete`
|
|
248
|
+
// remains a feature for direct (non-flow) callers.
|
|
249
|
+
let stopSource = null;
|
|
250
|
+
let unregister = null;
|
|
251
|
+
let shutdownPromise = null;
|
|
252
|
+
// Time budget for each shutdown stage (source, then emitters, then
|
|
253
|
+
// storages). The shutdown-manager wraps the whole flow shutdown in
|
|
254
|
+
// its own outer timeout; this per-stage value keeps any single
|
|
255
|
+
// stuck stage from using up that whole budget. Each stage uses
|
|
256
|
+
// `{ timeout }` per ADR-018 — this is the middle of its three
|
|
257
|
+
// nested shutdown timeouts.
|
|
258
|
+
const STAGE_TIMEOUT_MS = 5000;
|
|
259
|
+
|
|
260
|
+
let resolveWhenComplete;
|
|
261
|
+
const whenCompletePromise = new Promise( function ( resolve ) {
|
|
262
|
+
resolveWhenComplete = resolve;
|
|
263
|
+
} );
|
|
264
|
+
|
|
265
|
+
// Runs one drain stage and absorbs its failure into `stageErrors`,
|
|
266
|
+
// so a failing stage cannot skip the stages after it — a source
|
|
267
|
+
// that will not stop must not strand buffered emitter and
|
|
268
|
+
// storage data. The failure is logged here, naming the stage, and
|
|
269
|
+
// the first one is rethrown by drainAll below.
|
|
270
|
+
const runDrainStage = async function ( label, stageErrors, stage ) {
|
|
271
|
+
try {
|
|
272
|
+
await stage();
|
|
273
|
+
} catch ( err ) {
|
|
274
|
+
stageErrors.push( err );
|
|
275
|
+
console.error(
|
|
276
|
+
`WinkComposer/flow: ${label} drain stage failed [${( err && err.code ) || 'UNKNOWN'}]: ` +
|
|
277
|
+
`${( err && err.message ) || String( err )} — later drain stages still ran`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}; // runDrainStage()
|
|
281
|
+
|
|
282
|
+
const drainAll = async function () {
|
|
283
|
+
const stageErrors = [];
|
|
284
|
+
try {
|
|
285
|
+
if ( unregister ) unregister();
|
|
286
|
+
if ( stopSource ) {
|
|
287
|
+
await runDrainStage( 'source-stop', stageErrors,
|
|
288
|
+
() => stopSource( { timeout: STAGE_TIMEOUT_MS } ) );
|
|
289
|
+
}
|
|
290
|
+
await runDrainStage( 'emitter', stageErrors,
|
|
291
|
+
() => emitters.shutdown( { timeout: STAGE_TIMEOUT_MS } ) );
|
|
292
|
+
await runDrainStage( 'storage', stageErrors,
|
|
293
|
+
() => storages.shutdown( { timeout: STAGE_TIMEOUT_MS } ) );
|
|
294
|
+
} finally {
|
|
295
|
+
// Whatever the path here — clean drain or a stage that threw —
|
|
296
|
+
// the source has stopped producing, so unblock any
|
|
297
|
+
// whenComplete() waiter. The error itself still propagates to
|
|
298
|
+
// shutdown()'s caller; it must not also hang bystanders.
|
|
299
|
+
resolveWhenComplete();
|
|
300
|
+
}
|
|
301
|
+
// Every stage ran; the caller is still owed a rejection when any
|
|
302
|
+
// failed. First error wins — it is the earliest loss.
|
|
303
|
+
if ( stageErrors.length > 0 ) {
|
|
304
|
+
throw stageErrors[ 0 ];
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
const shutdown = function () {
|
|
308
|
+
if ( !shutdownPromise ) {
|
|
309
|
+
shutdownPromise = drainAll();
|
|
310
|
+
}
|
|
311
|
+
return shutdownPromise;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// 7. Start source (singleton).
|
|
315
|
+
if ( runtime.source ) {
|
|
316
|
+
const { adapter, config } = runtime.source;
|
|
317
|
+
|
|
318
|
+
// ADR-018 module-surface check: the source module must say what
|
|
319
|
+
// a crash costs — before start() runs. Placed here
|
|
320
|
+
// rather than in flow.js so direct runFlow() callers are covered
|
|
321
|
+
// too (they bypass the DSL entirely).
|
|
322
|
+
assertModuleDurability( adapter.id || 'source', adapter );
|
|
323
|
+
|
|
324
|
+
// Wrap user's onStatus so the runtime can spot the natural-
|
|
325
|
+
// completion signal without taking it away from the user.
|
|
326
|
+
// When the source signals `phase: 'complete'`:
|
|
327
|
+
// 1. Resolve the whenComplete Promise so callers can stop
|
|
328
|
+
// waiting for natural completion.
|
|
329
|
+
// 2. Trigger the drain (`shutdown()` is fire-and-forget —
|
|
330
|
+
// it's concurrent-safe and stages have their own timeouts).
|
|
331
|
+
const userOnStatus = config.onStatus;
|
|
332
|
+
const wrappedOnStatus = function ( s ) {
|
|
333
|
+
if ( userOnStatus ) {
|
|
334
|
+
userOnStatus( s );
|
|
335
|
+
} else if ( s && s.status === 'red' ) {
|
|
336
|
+
// No user handler: log the failure here, classified. The
|
|
337
|
+
// source's own console fallback fires only when NO handler
|
|
338
|
+
// exists — and this wrapper IS a handler, so the source
|
|
339
|
+
// stays quiet. Without this branch a red status inside
|
|
340
|
+
// a flow would vanish unreported.
|
|
341
|
+
const code = ( s.error && s.error.code ) || 'UNKNOWN';
|
|
342
|
+
const message = ( s.error && s.error.message ) ||
|
|
343
|
+
'source reported status red with no error detail';
|
|
344
|
+
console.error( `WinkComposer/flow '${flowName}': source error [${code}]: ${message}` );
|
|
345
|
+
}
|
|
346
|
+
if ( s && s.phase === 'complete' ) {
|
|
347
|
+
resolveWhenComplete();
|
|
348
|
+
shutdown();
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
stopSource = adapter.start( {
|
|
352
|
+
...config,
|
|
353
|
+
// Override any source-level auto-shutdown setting. The
|
|
354
|
+
// runtime owns the trigger when the source runs inside a
|
|
355
|
+
// flow; sources that auto-shutdown themselves would call
|
|
356
|
+
// back into our `shutdown()` while their run loop awaited
|
|
357
|
+
// it — a circular wait that takes the source-level force
|
|
358
|
+
// timer to break.
|
|
359
|
+
shutdownOnComplete: false,
|
|
360
|
+
onStatus: wrappedOnStatus,
|
|
361
|
+
onMessage: processMessage,
|
|
362
|
+
onShutdown: shutdown
|
|
363
|
+
} );
|
|
364
|
+
} else {
|
|
365
|
+
|
|
366
|
+
/* c8 ignore next 3 -- defensive: the flow API rejects empty flows before run() is called, so a flow without a source cannot reach this branch via the public API. Kept as a safety net for direct runFlow() callers. */
|
|
367
|
+
// No source means "nothing to wait for" — resolve immediately
|
|
368
|
+
// so any `await handle.whenComplete()` is a no-op.
|
|
369
|
+
resolveWhenComplete();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 8. Auto-register with shutdown-manager so OS signals are routed
|
|
373
|
+
// here. The flow layer no longer attaches its own SIGINT/SIGTERM
|
|
374
|
+
// handlers — that's the process layer's exclusive
|
|
375
|
+
// responsibility. Caller experience is unchanged: Ctrl-C still
|
|
376
|
+
// shuts the flow down gracefully, and `handle.shutdown()` still
|
|
377
|
+
// works directly for callers that want explicit control.
|
|
378
|
+
unregister = shutdownManager.register( { shutdown } );
|
|
379
|
+
shutdownManager.attachHandlers();
|
|
380
|
+
|
|
381
|
+
// Return pipeline handle.
|
|
382
|
+
return {
|
|
383
|
+
flowName,
|
|
384
|
+
composerState,
|
|
385
|
+
shutdown,
|
|
386
|
+
processMessage,
|
|
387
|
+
whenComplete: function () {
|
|
388
|
+
return whenCompletePromise;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
};
|
|
392
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Function-aware, deterministic serializer for generated modules.
|
|
3
|
+
* - Preserves functions verbatim
|
|
4
|
+
* - Single quotes for strings
|
|
5
|
+
* - Stable key order
|
|
6
|
+
* - Identifier keys unquoted when safe
|
|
7
|
+
* - 4-space indents
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const INDENT = 4;
|
|
11
|
+
|
|
12
|
+
const repeat = function ( n ) {
|
|
13
|
+
return ' '.repeat( n );
|
|
14
|
+
}; // repeat()
|
|
15
|
+
|
|
16
|
+
const isIdentifier = function ( key ) {
|
|
17
|
+
return ( /^[A-Za-z_$][A-Za-z0-9_$]*$/ ).test( key );
|
|
18
|
+
}; // isIdentifier()
|
|
19
|
+
|
|
20
|
+
const escapeString = function ( str ) {
|
|
21
|
+
return str
|
|
22
|
+
.replace( /\\/g, '\\\\' ) // Backslashes first
|
|
23
|
+
.replace( /'/g, '\\\'' ) // Single quotes
|
|
24
|
+
.replace( /\n/g, '\\n' ) // Newlines
|
|
25
|
+
.replace( /\r/g, '\\r' ) // Carriage returns
|
|
26
|
+
.replace( /\t/g, '\\t' ); // Tabs
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const serializeValue = function ( value, indent ) {
|
|
30
|
+
const pad = repeat( indent );
|
|
31
|
+
const next = indent + INDENT;
|
|
32
|
+
|
|
33
|
+
// Handle undefined, null & RegExp explicitly
|
|
34
|
+
if ( value === undefined ) {
|
|
35
|
+
return 'undefined';
|
|
36
|
+
} else if ( value === null ) {
|
|
37
|
+
return 'null';
|
|
38
|
+
} else if ( value instanceof RegExp ) {
|
|
39
|
+
return value.toString();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const t = typeof value;
|
|
43
|
+
|
|
44
|
+
if ( t === 'string' ) {
|
|
45
|
+
return `'${escapeString( value )}'`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if ( t === 'number' ) {
|
|
49
|
+
// Handle special numbers
|
|
50
|
+
if ( Number.isNaN( value ) ) return 'NaN';
|
|
51
|
+
if ( value === Infinity ) return 'Infinity';
|
|
52
|
+
if ( value === -Infinity ) return '-Infinity';
|
|
53
|
+
return String( value );
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if ( t === 'boolean' ) {
|
|
57
|
+
return String( value );
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if ( t === 'function' ) {
|
|
61
|
+
const funcStr = value.toString();
|
|
62
|
+
if ( funcStr.includes( '[native code]' ) ) throw Error( 'WinkComposer/flow: only regular or arrow functions are supported.' );
|
|
63
|
+
return funcStr;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if ( Array.isArray( value ) ) {
|
|
67
|
+
if ( value.length === 0 ) {
|
|
68
|
+
return '[]';
|
|
69
|
+
}
|
|
70
|
+
const parts = [];
|
|
71
|
+
for ( let i = 0; i < value.length; i += 1 ) {
|
|
72
|
+
parts.push( `${repeat( next )}${serializeValue( value[ i ], next )}` );
|
|
73
|
+
}
|
|
74
|
+
return `[\n${parts.join( ',\n' )}\n${pad}]`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const keys = Object.keys( value ).sort();
|
|
78
|
+
if ( keys.length === 0 ) {
|
|
79
|
+
return '{}';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const lines = [];
|
|
83
|
+
for ( let i = 0; i < keys.length; i += 1 ) {
|
|
84
|
+
const k = keys[ i ];
|
|
85
|
+
const v = value[ k ];
|
|
86
|
+
const key = isIdentifier( k ) ? k : `'${k.replace( /'/g, '\\\'' )}'`;
|
|
87
|
+
lines.push( `${repeat( next )}${key}: ${serializeValue( v, next )}` );
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return `{\n${lines.join( ',\n' )}\n${pad}}`;
|
|
91
|
+
}; // serializeValue()
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Serializes a case key for code generation.
|
|
95
|
+
* String keys become quoted, numeric keys remain unquoted.
|
|
96
|
+
*
|
|
97
|
+
* @param {string|number} key - The case key
|
|
98
|
+
* @returns {string} Serialized key for code output
|
|
99
|
+
*/
|
|
100
|
+
const serializeCaseKey = function ( key ) {
|
|
101
|
+
if ( typeof key === 'string' ) {
|
|
102
|
+
return `'${escapeString( key )}'`;
|
|
103
|
+
}
|
|
104
|
+
return String( key );
|
|
105
|
+
}; // serializeCaseKey()
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Generates a JS module from flow specifications.
|
|
109
|
+
* Supports both single-pipeline (backward compatible) and multi-specialization modes.
|
|
110
|
+
*
|
|
111
|
+
* @param {Object} ctx - Serialization context
|
|
112
|
+
* @param {Array} ctx.imports - List of node modules to import
|
|
113
|
+
* @param {string} ctx.flowName - Name of the flow
|
|
114
|
+
* @param {Array} [ctx.specs] - Single-pipeline specs (backward compatible)
|
|
115
|
+
* @param {Object} [ctx.specsByCase] - Multi-specialization specs keyed by case
|
|
116
|
+
* @param {Array} [ctx.caseOrder] - Order of case keys for deterministic output
|
|
117
|
+
* @returns {string} Generated JS module source code
|
|
118
|
+
*/
|
|
119
|
+
const serializeModule = function ( ctx ) {
|
|
120
|
+
const imports = ctx.imports || [];
|
|
121
|
+
|
|
122
|
+
const importLine = ( imports.length > 0 ) ?
|
|
123
|
+
`import { ${imports.join( ', ' )} } from '../src/nodes/index.js';\n\n` : '';
|
|
124
|
+
|
|
125
|
+
let body;
|
|
126
|
+
|
|
127
|
+
if ( ctx.specsByCase && ctx.caseOrder ) {
|
|
128
|
+
// Multi-specialization mode: generate entry per case
|
|
129
|
+
const entries = [];
|
|
130
|
+
for ( let i = 0; i < ctx.caseOrder.length; i += 1 ) {
|
|
131
|
+
const key = ctx.caseOrder[ i ];
|
|
132
|
+
const specs = ctx.specsByCase[ key ];
|
|
133
|
+
const serializedKey = serializeCaseKey( key );
|
|
134
|
+
const specsBody = serializeValue( specs, INDENT );
|
|
135
|
+
entries.push( `flowBySpecialization[ ${serializedKey} ] = ${specsBody};` );
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
body =
|
|
139
|
+
`${importLine}const flowBySpecialization = Object.create( null );
|
|
140
|
+
|
|
141
|
+
${entries.join( '\n\n' )}
|
|
142
|
+
|
|
143
|
+
export default flowBySpecialization;
|
|
144
|
+
`;
|
|
145
|
+
} else {
|
|
146
|
+
// Single-pipeline mode (backward compatible)
|
|
147
|
+
const specs = ctx.specs || [];
|
|
148
|
+
const specsBody = serializeValue( specs, INDENT );
|
|
149
|
+
|
|
150
|
+
body =
|
|
151
|
+
`${importLine}const flowBySpecialization = Object.create( null );
|
|
152
|
+
flowBySpecialization[ 0 ] = ${specsBody};
|
|
153
|
+
|
|
154
|
+
export default flowBySpecialization;
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return body;
|
|
159
|
+
}; // serializeModule()
|
|
160
|
+
|
|
161
|
+
export { serializeModule };
|