@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,571 @@
|
|
|
1
|
+
// core/semantics/loader.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Semantics Loader with Cross-Reference Validation
|
|
5
|
+
*
|
|
6
|
+
* Loads and validates semantics JSON files from a config directory.
|
|
7
|
+
* Validation is performed in two phases:
|
|
8
|
+
*
|
|
9
|
+
* 1. Schema validation (via validateWithSchema)
|
|
10
|
+
* - Enum files validated against enumSchema
|
|
11
|
+
* - Asset class files validated against assetClassSchema
|
|
12
|
+
* - Column validation delegated to columnSchema via propertySchema
|
|
13
|
+
*
|
|
14
|
+
* 2. Cross-reference validation (this module)
|
|
15
|
+
* - enumRef → enum must exist
|
|
16
|
+
* - contexts[].when.column → column must exist in same asset class
|
|
17
|
+
* - insightTypes[].columns → all columns must exist in asset class (no duplicates)
|
|
18
|
+
*
|
|
19
|
+
* Fail-fast behavior: throws on first validation error.
|
|
20
|
+
*
|
|
21
|
+
* Platform notes:
|
|
22
|
+
* - Glob patterns use POSIX separators (/) for cross-platform compatibility
|
|
23
|
+
* - File lists are sorted for deterministic load order
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { readFile } from 'node:fs/promises';
|
|
27
|
+
import { basename } from 'node:path';
|
|
28
|
+
|
|
29
|
+
// Standard error prefix for all semantic loader errors
|
|
30
|
+
const ERR_PREFIX = 'WinkComposer/semantics: ';
|
|
31
|
+
import { glob } from 'glob';
|
|
32
|
+
import { validateWithSchema } from '../utils/validate/index.js';
|
|
33
|
+
import {
|
|
34
|
+
enumSchema,
|
|
35
|
+
assetClassSchema,
|
|
36
|
+
validLimitsMutualExclusivity,
|
|
37
|
+
validLimitsHierarchy,
|
|
38
|
+
validContextLimitsHierarchy
|
|
39
|
+
} from './schemas/index.js';
|
|
40
|
+
import { computeSemanticsDigest } from './digest.js';
|
|
41
|
+
import { createWarningCollector } from './warnings.js';
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// CROSS-REFERENCE VALIDATION
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validates that context.when.column references exist in the asset class.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} assetClassName - Asset class name for error messages
|
|
51
|
+
* @param {string} colName - Column name for error messages
|
|
52
|
+
* @param {Array} contexts - Array of context entries
|
|
53
|
+
* @param {Set} columnNames - Set of valid column names
|
|
54
|
+
* @throws {Error} If a referenced column does not exist
|
|
55
|
+
*/
|
|
56
|
+
const validateContextColumnRefs = function ( assetClassName, colName, contexts, columnNames ) {
|
|
57
|
+
for ( let j = 0; j < contexts.length; j += 1 ) {
|
|
58
|
+
const ctx = contexts[ j ];
|
|
59
|
+
// Only validate object-form when clauses (not "default" string)
|
|
60
|
+
if ( ctx.when !== 'default' ) {
|
|
61
|
+
const refColumn = ctx.when.column;
|
|
62
|
+
if ( !columnNames.has( refColumn ) ) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
ERR_PREFIX + `AssetClass '${assetClassName}', column '${colName}', ` +
|
|
65
|
+
`context[${j}]: when.column '${refColumn}' not found in asset class`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate all cross-references within an asset class.
|
|
74
|
+
* Throws on first error for fail-fast behavior.
|
|
75
|
+
*
|
|
76
|
+
* Validates:
|
|
77
|
+
* 1. Mutual exclusivity: contexts vs direct operational/specification
|
|
78
|
+
* 2. Limits hierarchy: physicalRange ⊇ operational ⊇ specification
|
|
79
|
+
* 3. Context limits hierarchy: physicalRange ⊇ ctx.operational ⊇ ctx.specification
|
|
80
|
+
* 4. enumRef → enum exists in loaded enums
|
|
81
|
+
* 5. contexts[].when.column → column exists in same asset class
|
|
82
|
+
* 6. insightTypes[].columns → all exist in asset class
|
|
83
|
+
*
|
|
84
|
+
* @param {string} assetClassName - Name of the asset class
|
|
85
|
+
* @param {Object} assetClass - Asset class definition
|
|
86
|
+
* @param {Object} enums - Loaded enums object
|
|
87
|
+
* @throws {Error} On validation failure
|
|
88
|
+
*/
|
|
89
|
+
const validateCrossReferences = function ( assetClassName, assetClass, enums ) {
|
|
90
|
+
const columnNames = new Set( Object.keys( assetClass.columns ) );
|
|
91
|
+
|
|
92
|
+
// Validate each column's cross-references
|
|
93
|
+
const columnEntries = Object.entries( assetClass.columns );
|
|
94
|
+
for ( let i = 0; i < columnEntries.length; i += 1 ) {
|
|
95
|
+
const [ colName, colSpec ] = columnEntries[ i ];
|
|
96
|
+
|
|
97
|
+
// 1. Validate mutual exclusivity: contexts vs direct operational/specification
|
|
98
|
+
if ( !validLimitsMutualExclusivity( colSpec ) ) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
ERR_PREFIX + `AssetClass '${assetClassName}', column '${colName}': ` +
|
|
101
|
+
'cannot have both contexts and direct operational/specification limits (mutually exclusive)'
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 2. Validate limits hierarchy: physicalRange ⊇ operational ⊇ specification
|
|
106
|
+
if ( !validLimitsHierarchy( colSpec ) ) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
ERR_PREFIX + `AssetClass '${assetClassName}', column '${colName}': ` +
|
|
109
|
+
'operational/specification limits exceed physicalRange bounds or specification exceeds operational bounds'
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 3. Validate context limits hierarchy
|
|
114
|
+
if ( !validContextLimitsHierarchy( colSpec ) ) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
ERR_PREFIX + `AssetClass '${assetClassName}', column '${colName}': ` +
|
|
117
|
+
'context limits exceed physicalRange bounds or context specification exceeds context operational bounds'
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 4. Validate enumRef points to existing enum
|
|
122
|
+
if ( colSpec.enumRef !== undefined && !enums[ colSpec.enumRef ] ) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
ERR_PREFIX + `AssetClass '${assetClassName}', column '${colName}': ` +
|
|
125
|
+
`enumRef '${colSpec.enumRef}' not found in loaded enums`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 5. Validate context.when.column exists in same asset class
|
|
130
|
+
if ( colSpec.contexts && colSpec.contexts.length > 0 ) {
|
|
131
|
+
validateContextColumnRefs( assetClassName, colName, colSpec.contexts, columnNames );
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 6. Validate insightType columns exist and are unique
|
|
136
|
+
// 7. Validate designatedTimestamp cross-references
|
|
137
|
+
if ( assetClass.insightTypes ) {
|
|
138
|
+
const insightTypeEntries = Object.entries( assetClass.insightTypes );
|
|
139
|
+
for ( let i = 0; i < insightTypeEntries.length; i += 1 ) {
|
|
140
|
+
const [ itName, itSpec ] = insightTypeEntries[ i ];
|
|
141
|
+
const seenColumns = new Set();
|
|
142
|
+
for ( let j = 0; j < itSpec.columns.length; j += 1 ) {
|
|
143
|
+
const colName = itSpec.columns[ j ];
|
|
144
|
+
// Check for duplicate column references
|
|
145
|
+
if ( seenColumns.has( colName ) ) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
ERR_PREFIX + `AssetClass '${assetClassName}', insightType '${itName}': ` +
|
|
148
|
+
`duplicate column '${colName}'`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
seenColumns.add( colName );
|
|
152
|
+
// Check column exists in asset class
|
|
153
|
+
if ( !columnNames.has( colName ) ) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
ERR_PREFIX + `AssetClass '${assetClassName}', insightType '${itName}': ` +
|
|
156
|
+
`column '${colName}' not found in asset class columns`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 7a. Validate designatedTimestamp is in columns list
|
|
162
|
+
if ( !itSpec.columns.includes( itSpec.designatedTimestamp ) ) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
ERR_PREFIX + `AssetClass '${assetClassName}', insightType '${itName}': ` +
|
|
165
|
+
`designatedTimestamp '${itSpec.designatedTimestamp}' not in columns list`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 7b. Validate designatedTimestamp references a timestamp-type column
|
|
170
|
+
const designatedTsColSpec = assetClass.columns[ itSpec.designatedTimestamp ];
|
|
171
|
+
if ( !designatedTsColSpec || designatedTsColSpec.type !== 'timestamp' ) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
ERR_PREFIX + `AssetClass '${assetClassName}', insightType '${itName}': ` +
|
|
174
|
+
`designatedTimestamp '${itSpec.designatedTimestamp}' must reference a column with type 'timestamp'`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// ============================================================================
|
|
182
|
+
// NUMERIC TYPE CHECK
|
|
183
|
+
// ============================================================================
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Set of numeric column types.
|
|
187
|
+
* @type {Set<string>}
|
|
188
|
+
*/
|
|
189
|
+
const NUMERIC_TYPES = new Set( [ 'float64', 'int64' ] );
|
|
190
|
+
|
|
191
|
+
// ============================================================================
|
|
192
|
+
// COMPLETENESS WARNING FUNCTIONS
|
|
193
|
+
// ============================================================================
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Check for columns missing interpretation (advisory).
|
|
197
|
+
*
|
|
198
|
+
* @param {string} assetClassName - Asset class name for warning messages
|
|
199
|
+
* @param {Object} columns - Columns object
|
|
200
|
+
* @param {Object} warningCollector - Warning collector with add() method
|
|
201
|
+
*/
|
|
202
|
+
const checkMissingInterpretation = function ( assetClassName, columns, warningCollector ) {
|
|
203
|
+
const columnNames = Object.keys( columns );
|
|
204
|
+
for ( let i = 0; i < columnNames.length; i += 1 ) {
|
|
205
|
+
const colName = columnNames[ i ];
|
|
206
|
+
const colSpec = columns[ colName ];
|
|
207
|
+
const hasInterp = colSpec.interpretation && colSpec.interpretation.length > 0;
|
|
208
|
+
if ( !hasInterp ) {
|
|
209
|
+
warningCollector.add(
|
|
210
|
+
`AssetClass '${assetClassName}', column '${colName}': missing interpretation`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Check for numeric columns missing physicalRange (advisory).
|
|
218
|
+
*
|
|
219
|
+
* @param {string} assetClassName - Asset class name for warning messages
|
|
220
|
+
* @param {Object} columns - Columns object
|
|
221
|
+
* @param {Object} warningCollector - Warning collector with add() method
|
|
222
|
+
*/
|
|
223
|
+
const checkMissingPhysicalRange = function ( assetClassName, columns, warningCollector ) {
|
|
224
|
+
const columnNames = Object.keys( columns );
|
|
225
|
+
for ( let i = 0; i < columnNames.length; i += 1 ) {
|
|
226
|
+
const colName = columnNames[ i ];
|
|
227
|
+
const colSpec = columns[ colName ];
|
|
228
|
+
if ( NUMERIC_TYPES.has( colSpec.type ) && !colSpec.physicalRange ) {
|
|
229
|
+
warningCollector.add(
|
|
230
|
+
`AssetClass '${assetClassName}', column '${colName}': numeric column missing physicalRange`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Check for numeric columns missing unit (advisory).
|
|
238
|
+
*
|
|
239
|
+
* @param {string} assetClassName - Asset class name for warning messages
|
|
240
|
+
* @param {Object} columns - Columns object
|
|
241
|
+
* @param {Object} warningCollector - Warning collector with add() method
|
|
242
|
+
*/
|
|
243
|
+
const checkMissingUnit = function ( assetClassName, columns, warningCollector ) {
|
|
244
|
+
const columnNames = Object.keys( columns );
|
|
245
|
+
for ( let i = 0; i < columnNames.length; i += 1 ) {
|
|
246
|
+
const colName = columnNames[ i ];
|
|
247
|
+
const colSpec = columns[ colName ];
|
|
248
|
+
const hasUnit = colSpec.unit && colSpec.unit.length > 0;
|
|
249
|
+
if ( NUMERIC_TYPES.has( colSpec.type ) && !hasUnit ) {
|
|
250
|
+
warningCollector.add(
|
|
251
|
+
`AssetClass '${assetClassName}', column '${colName}': numeric column missing unit`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Check for enums not referenced by any column (advisory).
|
|
259
|
+
*
|
|
260
|
+
* @param {Object} enums - Loaded enums object
|
|
261
|
+
* @param {Object} assetClasses - Loaded asset classes object
|
|
262
|
+
* @param {Object} warningCollector - Warning collector with add() method
|
|
263
|
+
*/
|
|
264
|
+
const checkUnreferencedEnums = function ( enums, assetClasses, warningCollector ) {
|
|
265
|
+
const referencedEnums = new Set();
|
|
266
|
+
|
|
267
|
+
// Collect all enumRef values from all asset classes
|
|
268
|
+
const assetClassEntries = Object.entries( assetClasses );
|
|
269
|
+
for ( let i = 0; i < assetClassEntries.length; i += 1 ) {
|
|
270
|
+
const [ , assetClass ] = assetClassEntries[ i ];
|
|
271
|
+
const columnNames = Object.keys( assetClass.columns );
|
|
272
|
+
for ( let j = 0; j < columnNames.length; j += 1 ) {
|
|
273
|
+
const colSpec = assetClass.columns[ columnNames[ j ] ];
|
|
274
|
+
if ( colSpec.enumRef ) {
|
|
275
|
+
referencedEnums.add( colSpec.enumRef );
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check each enum for references
|
|
281
|
+
const enumNames = Object.keys( enums );
|
|
282
|
+
for ( let i = 0; i < enumNames.length; i += 1 ) {
|
|
283
|
+
const enumName = enumNames[ i ];
|
|
284
|
+
if ( !referencedEnums.has( enumName ) ) {
|
|
285
|
+
warningCollector.add(
|
|
286
|
+
`Enum '${enumName}': not referenced by any column`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Check for columns not used in any insightType (advisory).
|
|
294
|
+
*
|
|
295
|
+
* @param {string} assetClassName - Asset class name for warning messages
|
|
296
|
+
* @param {Object} assetClass - Asset class definition
|
|
297
|
+
* @param {Object} warningCollector - Warning collector with add() method
|
|
298
|
+
*/
|
|
299
|
+
const checkUnusedColumns = function ( assetClassName, assetClass, warningCollector ) {
|
|
300
|
+
// Collect all columns used in insightTypes
|
|
301
|
+
const usedColumns = new Set();
|
|
302
|
+
if ( assetClass.insightTypes ) {
|
|
303
|
+
const insightTypeEntries = Object.entries( assetClass.insightTypes );
|
|
304
|
+
for ( let i = 0; i < insightTypeEntries.length; i += 1 ) {
|
|
305
|
+
const [ , itSpec ] = insightTypeEntries[ i ];
|
|
306
|
+
for ( let j = 0; j < itSpec.columns.length; j += 1 ) {
|
|
307
|
+
usedColumns.add( itSpec.columns[ j ] );
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Check each column for usage
|
|
313
|
+
const columnNames = Object.keys( assetClass.columns );
|
|
314
|
+
for ( let i = 0; i < columnNames.length; i += 1 ) {
|
|
315
|
+
const colName = columnNames[ i ];
|
|
316
|
+
if ( !usedColumns.has( colName ) ) {
|
|
317
|
+
warningCollector.add(
|
|
318
|
+
`AssetClass '${assetClassName}', column '${colName}': not used in any insightType`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Check for int64 columns with small value sets that lack enumRef (advisory).
|
|
326
|
+
* This helps identify columns that look like enums but aren't declared as such.
|
|
327
|
+
*
|
|
328
|
+
* Heuristic: if a column is int64 and has physicalRange with max-min < 20,
|
|
329
|
+
* it may represent an enum-like state variable.
|
|
330
|
+
*
|
|
331
|
+
* @param {string} assetClassName - Asset class name for warning messages
|
|
332
|
+
* @param {Object} columns - Columns object
|
|
333
|
+
* @param {Object} warningCollector - Warning collector with add() method
|
|
334
|
+
*/
|
|
335
|
+
const checkEnumLikeColumnsWithoutEnumRef = function ( assetClassName, columns, warningCollector ) {
|
|
336
|
+
const MAX_ENUM_RANGE = 20; // Threshold for "small value set"
|
|
337
|
+
|
|
338
|
+
const columnNames = Object.keys( columns );
|
|
339
|
+
for ( let i = 0; i < columnNames.length; i += 1 ) {
|
|
340
|
+
const colName = columnNames[ i ];
|
|
341
|
+
const colSpec = columns[ colName ];
|
|
342
|
+
|
|
343
|
+
// Only check int64 columns without enumRef that have physicalRange
|
|
344
|
+
if ( colSpec.type === 'int64' && !colSpec.enumRef && colSpec.physicalRange ) {
|
|
345
|
+
const range = colSpec.physicalRange.max - colSpec.physicalRange.min;
|
|
346
|
+
if ( range >= 0 && range < MAX_ENUM_RANGE ) {
|
|
347
|
+
warningCollector.add(
|
|
348
|
+
`AssetClass '${assetClassName}', column '${colName}': int64 column with small range ` +
|
|
349
|
+
`(${colSpec.physicalRange.min}-${colSpec.physicalRange.max}) may need enumRef`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// FILE LOADING
|
|
358
|
+
// ============================================================================
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Load and parse a JSON file.
|
|
362
|
+
*
|
|
363
|
+
* @param {string} filePath - Path to JSON file
|
|
364
|
+
* @returns {Promise<Object>} Parsed JSON object
|
|
365
|
+
* @throws {Error} On file read or parse failure
|
|
366
|
+
*/
|
|
367
|
+
const loadJsonFile = async function ( filePath ) {
|
|
368
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- exported helper; the function itself does not validate path provenance, callers do. Current callers (lines ~393, ~433) pass filePaths from glob over composer-configured enumsDir; any future caller must ensure the same before invoking.
|
|
369
|
+
const content = await readFile( filePath, 'utf8' );
|
|
370
|
+
try {
|
|
371
|
+
return JSON.parse( content );
|
|
372
|
+
} catch ( err ) {
|
|
373
|
+
throw new Error( ERR_PREFIX + `Failed to parse JSON file '${filePath}': ${err.message}` );
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Load all enum files from a directory.
|
|
379
|
+
*
|
|
380
|
+
* @param {string} enumsDir - Path to enums directory
|
|
381
|
+
* @returns {Promise<Object>} Map of enum name to enum definition
|
|
382
|
+
* @throws {Error} On validation failure
|
|
383
|
+
*/
|
|
384
|
+
const loadEnums = async function ( enumsDir ) {
|
|
385
|
+
// Use POSIX separators for cross-platform glob compatibility
|
|
386
|
+
const pattern = `${enumsDir}/*.json`;
|
|
387
|
+
// Sort for deterministic load order
|
|
388
|
+
const files = ( await glob( pattern ) ).sort();
|
|
389
|
+
|
|
390
|
+
const enums = Object.create( null );
|
|
391
|
+
|
|
392
|
+
for ( let i = 0; i < files.length; i += 1 ) {
|
|
393
|
+
const filePath = files[ i ];
|
|
394
|
+
const data = await loadJsonFile( filePath ); // eslint-disable-line no-await-in-loop
|
|
395
|
+
|
|
396
|
+
// Validate against enum schema
|
|
397
|
+
const validation = validateWithSchema( enumSchema, data, `enum file '${basename( filePath )}'` );
|
|
398
|
+
if ( !validation.valid ) {
|
|
399
|
+
throw new Error(
|
|
400
|
+
ERR_PREFIX + `Enum file '${filePath}' validation failed:\n - ${validation.errors.join( '\n - ' )}`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Check for duplicate enum names
|
|
405
|
+
if ( enums[ data.name ] ) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
ERR_PREFIX + `Duplicate enum name '${data.name}' found in '${filePath}'`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
enums[ data.name ] = data;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return enums;
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Load all asset class files from a directory.
|
|
419
|
+
*
|
|
420
|
+
* @param {string} assetClassesDir - Path to asset-classes directory
|
|
421
|
+
* @returns {Promise<Object>} Map of asset class name to definition
|
|
422
|
+
* @throws {Error} On validation failure
|
|
423
|
+
*/
|
|
424
|
+
const loadAssetClasses = async function ( assetClassesDir ) {
|
|
425
|
+
// Use POSIX separators for cross-platform glob compatibility
|
|
426
|
+
const pattern = `${assetClassesDir}/*.json`;
|
|
427
|
+
// Sort for deterministic load order
|
|
428
|
+
const files = ( await glob( pattern ) ).sort();
|
|
429
|
+
|
|
430
|
+
const assetClasses = Object.create( null );
|
|
431
|
+
|
|
432
|
+
for ( let i = 0; i < files.length; i += 1 ) {
|
|
433
|
+
const filePath = files[ i ];
|
|
434
|
+
const data = await loadJsonFile( filePath ); // eslint-disable-line no-await-in-loop
|
|
435
|
+
|
|
436
|
+
// Validate against asset class schema (includes column validation via propertySchema)
|
|
437
|
+
const validation = validateWithSchema( assetClassSchema, data, `asset class file '${basename( filePath )}'` );
|
|
438
|
+
if ( !validation.valid ) {
|
|
439
|
+
throw new Error(
|
|
440
|
+
ERR_PREFIX + `Asset class file '${filePath}' validation failed:\n - ${validation.errors.join( '\n - ' )}`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Check for duplicate asset class names
|
|
445
|
+
if ( assetClasses[ data.name ] ) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
ERR_PREFIX + `Duplicate asset class name '${data.name}' found in '${filePath}'`
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
assetClasses[ data.name ] = data;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return assetClasses;
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// ============================================================================
|
|
458
|
+
// MAIN LOADER
|
|
459
|
+
// ============================================================================
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Load semantics from a config directory with full validation.
|
|
463
|
+
*
|
|
464
|
+
* Directory structure expected:
|
|
465
|
+
* configPath/
|
|
466
|
+
* ├── enums/
|
|
467
|
+
* │ └── *.json
|
|
468
|
+
* └── asset-classes/
|
|
469
|
+
* └── *.json
|
|
470
|
+
*
|
|
471
|
+
* Validation phases:
|
|
472
|
+
* 1. Load and parse JSON files
|
|
473
|
+
* 2. Schema validation (enum, asset-class, column)
|
|
474
|
+
* 3. Cross-reference validation (enumRef, context columns, insightType columns)
|
|
475
|
+
* 4. Completeness warnings (missing interpretation, physicalRange, unit, etc.)
|
|
476
|
+
*
|
|
477
|
+
* @param {string} configPath - Path to semantics config directory
|
|
478
|
+
* @param {Object} [options] - Loading options
|
|
479
|
+
* @param {string[]} [options.assetClasses] - Filter to load only these asset classes by name
|
|
480
|
+
* @param {string} [options.version] - Semantic version string for digest (default: '1.0.0')
|
|
481
|
+
* @param {boolean} [options.suppressWarnings=false] - Suppress all completeness warnings
|
|
482
|
+
* @param {Function} [options.onWarning] - Custom warning handler (msg) => void
|
|
483
|
+
* @returns {Promise<Object>} { enums, assetClasses, digest }
|
|
484
|
+
* @throws {Error} Fail-fast on any validation error
|
|
485
|
+
*/
|
|
486
|
+
const loadSemantics = async function ( configPath, options = {} ) {
|
|
487
|
+
const {
|
|
488
|
+
assetClasses: filterNames,
|
|
489
|
+
suppressWarnings = false,
|
|
490
|
+
onWarning
|
|
491
|
+
} = options;
|
|
492
|
+
|
|
493
|
+
// Create warning collector for completeness checks
|
|
494
|
+
const warningCollector = createWarningCollector( { suppressWarnings, onWarning } );
|
|
495
|
+
|
|
496
|
+
// Phase 1-2: Load and validate individual files (schema validation)
|
|
497
|
+
// Use POSIX separators for cross-platform compatibility
|
|
498
|
+
const enumsDir = `${configPath}/enums`;
|
|
499
|
+
const assetClassesDir = `${configPath}/asset-classes`;
|
|
500
|
+
|
|
501
|
+
// Always load all enums (required for enumRef validation)
|
|
502
|
+
const enums = await loadEnums( enumsDir );
|
|
503
|
+
|
|
504
|
+
// Load all asset classes, then filter if requested
|
|
505
|
+
let assetClasses = await loadAssetClasses( assetClassesDir );
|
|
506
|
+
|
|
507
|
+
// Apply filter if provided (strict: unknown names throw)
|
|
508
|
+
if ( filterNames && filterNames.length > 0 ) {
|
|
509
|
+
const filtered = Object.create( null );
|
|
510
|
+
for ( let i = 0; i < filterNames.length; i += 1 ) {
|
|
511
|
+
const name = filterNames[ i ];
|
|
512
|
+
if ( !assetClasses[ name ] ) {
|
|
513
|
+
throw new Error(
|
|
514
|
+
ERR_PREFIX + `Asset class '${name}' not found in loaded definitions`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
filtered[ name ] = assetClasses[ name ];
|
|
518
|
+
}
|
|
519
|
+
assetClasses = filtered;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Phase 3: Cross-reference validation (only on filtered subset)
|
|
523
|
+
const assetClassEntries = Object.entries( assetClasses );
|
|
524
|
+
for ( let i = 0; i < assetClassEntries.length; i += 1 ) {
|
|
525
|
+
const [ name, assetClass ] = assetClassEntries[ i ];
|
|
526
|
+
validateCrossReferences( name, assetClass, enums );
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Phase 4: Completeness warnings (advisory, non-blocking)
|
|
530
|
+
for ( let i = 0; i < assetClassEntries.length; i += 1 ) {
|
|
531
|
+
const [ name, assetClass ] = assetClassEntries[ i ];
|
|
532
|
+
checkMissingInterpretation( name, assetClass.columns, warningCollector );
|
|
533
|
+
checkMissingPhysicalRange( name, assetClass.columns, warningCollector );
|
|
534
|
+
checkMissingUnit( name, assetClass.columns, warningCollector );
|
|
535
|
+
checkUnusedColumns( name, assetClass, warningCollector );
|
|
536
|
+
checkEnumLikeColumnsWithoutEnumRef( name, assetClass.columns, warningCollector );
|
|
537
|
+
}
|
|
538
|
+
checkUnreferencedEnums( enums, assetClasses, warningCollector );
|
|
539
|
+
|
|
540
|
+
// Emit accumulated warnings
|
|
541
|
+
warningCollector.emit();
|
|
542
|
+
|
|
543
|
+
// Phase 5: Compute semantic digest
|
|
544
|
+
const digest = computeSemanticsDigest(
|
|
545
|
+
{ enums, assetClasses },
|
|
546
|
+
options.version || '1.0.0'
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
return { enums, assetClasses, digest };
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// EXPORTS
|
|
554
|
+
// ============================================================================
|
|
555
|
+
|
|
556
|
+
export {
|
|
557
|
+
loadSemantics,
|
|
558
|
+
loadEnums,
|
|
559
|
+
loadAssetClasses,
|
|
560
|
+
loadJsonFile,
|
|
561
|
+
validateCrossReferences,
|
|
562
|
+
// Completeness warning functions (used internally, exported for testing)
|
|
563
|
+
checkMissingInterpretation,
|
|
564
|
+
checkMissingPhysicalRange,
|
|
565
|
+
checkMissingUnit,
|
|
566
|
+
checkUnreferencedEnums,
|
|
567
|
+
checkUnusedColumns,
|
|
568
|
+
checkEnumLikeColumnsWithoutEnumRef
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
export default loadSemantics;
|