@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.
Files changed (451) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +106 -80
  3. package/package.json +56 -28
  4. package/src/composer.js +75 -0
  5. package/src/core/batch/aoo-encoder.js +148 -0
  6. package/src/core/batch/index.js +12 -0
  7. package/src/core/batch/sliding-window.js +91 -0
  8. package/src/core/codec/index.js +137 -0
  9. package/src/core/emitter-manager/mqtt/constants.js +102 -0
  10. package/src/core/emitter-manager/mqtt/emitter.js +780 -0
  11. package/src/core/emitter-manager/mqtt/index.js +190 -0
  12. package/src/core/emitter-manager/mqtt/mqtt-store.js +538 -0
  13. package/src/core/emitter-manager/terminal/assert-columns.js +98 -0
  14. package/src/core/emitter-manager/terminal/formatters.js +160 -0
  15. package/src/core/emitter-manager/terminal/index.js +341 -0
  16. package/src/core/env-vars.js +245 -0
  17. package/src/core/mqtt-protocol.js +47 -0
  18. package/src/core/partition-manager/index.js +7 -0
  19. package/src/core/partition-manager/init.js +44 -0
  20. package/src/core/partition-manager/introspect.js +15 -0
  21. package/src/core/partition-manager/publish-to.js +6 -0
  22. package/src/core/partition-manager/recompute.js +5 -0
  23. package/src/core/partition-manager/reset.js +5 -0
  24. package/src/core/partition-manager/update.js +264 -0
  25. package/src/core/partition-manager/validate-flow.js +157 -0
  26. package/src/core/semantics/digest.js +128 -0
  27. package/src/core/semantics/index.js +83 -0
  28. package/src/core/semantics/loader.js +571 -0
  29. package/src/core/semantics/schemas/asset-class-schema.js +218 -0
  30. package/src/core/semantics/schemas/column-schema.js +651 -0
  31. package/src/core/semantics/schemas/enum-schema.js +153 -0
  32. package/src/core/semantics/schemas/index.js +51 -0
  33. package/src/core/semantics/warnings.js +141 -0
  34. package/src/core/shutdown-manager/index.js +289 -0
  35. package/src/core/source-manager/csv/index.js +171 -0
  36. package/src/core/source-manager/csv/start.js +456 -0
  37. package/src/core/source-manager/mqtt/client.js +406 -0
  38. package/src/core/source-manager/mqtt/constants.js +147 -0
  39. package/src/core/source-manager/mqtt/dedup.js +244 -0
  40. package/src/core/source-manager/mqtt/index.js +260 -0
  41. package/src/core/source-manager/mqtt/status.js +546 -0
  42. package/src/core/source-manager/test-harness/comparator.js +226 -0
  43. package/src/core/source-manager/test-harness/field-generator.js +85 -0
  44. package/src/core/source-manager/test-harness/fuzz.js +87 -0
  45. package/src/core/source-manager/test-harness/index.js +115 -0
  46. package/src/core/source-manager/test-harness/prng.js +57 -0
  47. package/src/core/source-manager/test-harness/start.js +232 -0
  48. package/src/core/source-manager/test-harness/validate.js +213 -0
  49. package/src/core/storage-manager/questdb/assert-columns.js +120 -0
  50. package/src/core/storage-manager/questdb/ensure-tables.js +187 -0
  51. package/src/core/storage-manager/questdb/index.js +1251 -0
  52. package/src/core/storage-manager/questdb/persist-plan.js +488 -0
  53. package/src/core/storage-manager/questdb/writers.js +142 -0
  54. package/src/core/test-utils/sealed-record.js +80 -0
  55. package/src/core/test-utils/soak-signature.js +149 -0
  56. package/src/core/test-utils/tcp-proxy.js +106 -0
  57. package/src/core/tunable/helpers.js +254 -0
  58. package/src/core/tunable/index.js +87 -0
  59. package/src/core/utils/console-formatter/index.js +207 -0
  60. package/src/core/utils/create-smart-store-as/index.js +98 -0
  61. package/src/core/utils/create-smart-store-as/sanitize-field.js +46 -0
  62. package/src/core/utils/create-smart-store-as/smart-truncate.js +93 -0
  63. package/src/core/utils/flow/index.js +5 -0
  64. package/src/core/utils/flow/make-collision-checker.js +23 -0
  65. package/src/core/utils/flow/to-kebab.js +20 -0
  66. package/src/core/utils/half-life/index.js +111 -0
  67. package/src/core/utils/node/disable.js +11 -0
  68. package/src/core/utils/node/enable.js +11 -0
  69. package/src/core/utils/node/execute-triggers.js +40 -0
  70. package/src/core/utils/node/index.js +10 -0
  71. package/src/core/utils/node/pause.js +11 -0
  72. package/src/core/utils/node/populate-predicate-inputs.js +14 -0
  73. package/src/core/utils/node/publish-nan.js +19 -0
  74. package/src/core/utils/node/reset-predicate-inputs.js +11 -0
  75. package/src/core/utils/node/unpause.js +11 -0
  76. package/src/core/utils/node/validate-spec.js +22 -0
  77. package/src/core/utils/options/resolve-field-keyed.js +115 -0
  78. package/src/core/utils/quantize/index.js +75 -0
  79. package/src/core/utils/stats/formulas.js +67 -0
  80. package/src/core/utils/stats/index.js +3 -0
  81. package/src/core/utils/validate/composer-validators.js +141 -0
  82. package/src/core/utils/validate/helpers.js +206 -0
  83. package/src/core/utils/validate/index.js +12 -0
  84. package/src/core/utils/validate/type-validators.js +538 -0
  85. package/src/core/utils/validate/validate.js +107 -0
  86. package/src/core/utils/validate/validators.js +177 -0
  87. package/src/core/wiring/assert-handle.js +57 -0
  88. package/src/core/wiring/assert-module.js +63 -0
  89. package/src/core/wiring/index.js +5 -0
  90. package/src/core/wiring/node-type-to-module.js +17 -0
  91. package/src/core/wiring/wire-emitters.js +256 -0
  92. package/src/core/wiring/wire-linear-graph.js +25 -0
  93. package/src/core/wiring/wire-node.js +157 -0
  94. package/src/core/wiring/wire-semantics.js +173 -0
  95. package/src/core/wiring/wire-storages.js +344 -0
  96. package/src/flow/check-output-collisions.js +93 -0
  97. package/src/flow/consts.js +9 -0
  98. package/src/flow/deep-clone-spec.js +64 -0
  99. package/src/flow/driver.js +171 -0
  100. package/src/flow/expand-single-input-array.js +76 -0
  101. package/src/flow/expand-template-spec.js +93 -0
  102. package/src/flow/flow-api-schemas.js +300 -0
  103. package/src/flow/flow.js +658 -0
  104. package/src/flow/for-each.js +225 -0
  105. package/src/flow/get-signature-pattern.js +31 -0
  106. package/src/flow/get-target-array.js +46 -0
  107. package/src/flow/inspect.js +67 -0
  108. package/src/flow/load-node-module.js +81 -0
  109. package/src/flow/make-node-method.js +112 -0
  110. package/src/flow/resolve-pick-by-field.js +77 -0
  111. package/src/flow/resolve-tunables.js +76 -0
  112. package/src/flow/run.js +392 -0
  113. package/src/flow/serialize.js +161 -0
  114. package/src/flow/signatures-schema.js +222 -0
  115. package/src/flow/spec-builder.js +65 -0
  116. package/src/flow/validate-triggers.js +250 -0
  117. package/src/flow/validate.js +261 -0
  118. package/src/nodes/accumulate/index.js +21 -0
  119. package/src/nodes/accumulate/init.js +48 -0
  120. package/src/nodes/accumulate/introspect.js +123 -0
  121. package/src/nodes/accumulate/publish-to.js +34 -0
  122. package/src/nodes/accumulate/recompute.js +27 -0
  123. package/src/nodes/accumulate/reset.js +25 -0
  124. package/src/nodes/accumulate/update.js +42 -0
  125. package/src/nodes/appraise/calibrate.js +64 -0
  126. package/src/nodes/appraise/decision.js +66 -0
  127. package/src/nodes/appraise/deviation.js +140 -0
  128. package/src/nodes/appraise/index.js +23 -0
  129. package/src/nodes/appraise/init.js +165 -0
  130. package/src/nodes/appraise/integrate.js +45 -0
  131. package/src/nodes/appraise/introspect.js +352 -0
  132. package/src/nodes/appraise/publish-to.js +93 -0
  133. package/src/nodes/appraise/receptor.js +69 -0
  134. package/src/nodes/appraise/recompute.js +79 -0
  135. package/src/nodes/appraise/reset.js +53 -0
  136. package/src/nodes/appraise/update.js +130 -0
  137. package/src/nodes/archive/csv/index.js +6 -0
  138. package/src/nodes/archive/csv/init.js +36 -0
  139. package/src/nodes/archive/csv/load.js +70 -0
  140. package/src/nodes/archive/csv/reset.js +14 -0
  141. package/src/nodes/archive/csv/simple-parser.js +216 -0
  142. package/src/nodes/archive/csv/test.js +173 -0
  143. package/src/nodes/archive/csv/update.js +63 -0
  144. package/src/nodes/butterworth-filter/index.js +18 -0
  145. package/src/nodes/butterworth-filter/init.js +184 -0
  146. package/src/nodes/butterworth-filter/introspect.js +228 -0
  147. package/src/nodes/butterworth-filter/publish-to.js +21 -0
  148. package/src/nodes/butterworth-filter/recompute.js +13 -0
  149. package/src/nodes/butterworth-filter/reset.js +30 -0
  150. package/src/nodes/butterworth-filter/update.js +38 -0
  151. package/src/nodes/categorize/index.js +19 -0
  152. package/src/nodes/categorize/init.js +58 -0
  153. package/src/nodes/categorize/introspect.js +177 -0
  154. package/src/nodes/categorize/publish-to.js +29 -0
  155. package/src/nodes/categorize/recompute.js +14 -0
  156. package/src/nodes/categorize/reset.js +16 -0
  157. package/src/nodes/categorize/update.js +53 -0
  158. package/src/nodes/controller/index.js +9 -0
  159. package/src/nodes/controller/init.js +55 -0
  160. package/src/nodes/controller/introspect.js +91 -0
  161. package/src/nodes/controller/publish-to.js +8 -0
  162. package/src/nodes/controller/recompute.js +9 -0
  163. package/src/nodes/controller/reset.js +17 -0
  164. package/src/nodes/controller/update.js +63 -0
  165. package/src/nodes/diff/index.js +19 -0
  166. package/src/nodes/diff/init.js +37 -0
  167. package/src/nodes/diff/introspect.js +132 -0
  168. package/src/nodes/diff/publish-to.js +21 -0
  169. package/src/nodes/diff/recompute.js +12 -0
  170. package/src/nodes/diff/reset.js +12 -0
  171. package/src/nodes/diff/update.js +37 -0
  172. package/src/nodes/digest-moments/formulas.js +12 -0
  173. package/src/nodes/digest-moments/index.js +14 -0
  174. package/src/nodes/digest-moments/init.js +67 -0
  175. package/src/nodes/digest-moments/introspect.js +154 -0
  176. package/src/nodes/digest-moments/publish-to.js +34 -0
  177. package/src/nodes/digest-moments/recompute.js +14 -0
  178. package/src/nodes/digest-moments/reset.js +14 -0
  179. package/src/nodes/digest-moments/update.js +120 -0
  180. package/src/nodes/dwell-time-tracker/index.js +14 -0
  181. package/src/nodes/dwell-time-tracker/init.js +59 -0
  182. package/src/nodes/dwell-time-tracker/introspect.js +137 -0
  183. package/src/nodes/dwell-time-tracker/publish-to.js +22 -0
  184. package/src/nodes/dwell-time-tracker/recompute.js +8 -0
  185. package/src/nodes/dwell-time-tracker/reset.js +23 -0
  186. package/src/nodes/dwell-time-tracker/update.js +103 -0
  187. package/src/nodes/emit-if/helpers.js +94 -0
  188. package/src/nodes/emit-if/index.js +16 -0
  189. package/src/nodes/emit-if/init.js +67 -0
  190. package/src/nodes/emit-if/introspect.js +102 -0
  191. package/src/nodes/emit-if/publish-to.js +13 -0
  192. package/src/nodes/emit-if/recompute.js +12 -0
  193. package/src/nodes/emit-if/reset.js +27 -0
  194. package/src/nodes/emit-if/update.js +93 -0
  195. package/src/nodes/es-correlation/index.js +19 -0
  196. package/src/nodes/es-correlation/init.js +73 -0
  197. package/src/nodes/es-correlation/introspect.js +176 -0
  198. package/src/nodes/es-correlation/publish-to.js +32 -0
  199. package/src/nodes/es-correlation/recompute.js +56 -0
  200. package/src/nodes/es-correlation/reset.js +27 -0
  201. package/src/nodes/es-correlation/update.js +110 -0
  202. package/src/nodes/es-mean/index.js +20 -0
  203. package/src/nodes/es-mean/init.js +58 -0
  204. package/src/nodes/es-mean/introspect.js +129 -0
  205. package/src/nodes/es-mean/publish-to.js +19 -0
  206. package/src/nodes/es-mean/recompute.js +16 -0
  207. package/src/nodes/es-mean/reset.js +24 -0
  208. package/src/nodes/es-mean/update.js +69 -0
  209. package/src/nodes/es-pairwise-correlation/index.js +19 -0
  210. package/src/nodes/es-pairwise-correlation/init.js +98 -0
  211. package/src/nodes/es-pairwise-correlation/introspect.js +187 -0
  212. package/src/nodes/es-pairwise-correlation/publish-to.js +31 -0
  213. package/src/nodes/es-pairwise-correlation/recompute.js +69 -0
  214. package/src/nodes/es-pairwise-correlation/reset.js +29 -0
  215. package/src/nodes/es-pairwise-correlation/update.js +89 -0
  216. package/src/nodes/es-stats/index.js +21 -0
  217. package/src/nodes/es-stats/init.js +105 -0
  218. package/src/nodes/es-stats/introspect.js +157 -0
  219. package/src/nodes/es-stats/publish-to.js +42 -0
  220. package/src/nodes/es-stats/recompute.js +74 -0
  221. package/src/nodes/es-stats/reset.js +49 -0
  222. package/src/nodes/es-stats/update-envelope.js +44 -0
  223. package/src/nodes/es-stats/update-welford.js +64 -0
  224. package/src/nodes/es-stats/update.js +59 -0
  225. package/src/nodes/index.js +83 -0
  226. package/src/nodes/invert-flag/index.js +20 -0
  227. package/src/nodes/invert-flag/init.js +36 -0
  228. package/src/nodes/invert-flag/introspect.js +115 -0
  229. package/src/nodes/invert-flag/publish-to.js +23 -0
  230. package/src/nodes/invert-flag/recompute.js +12 -0
  231. package/src/nodes/invert-flag/reset.js +12 -0
  232. package/src/nodes/invert-flag/update.js +32 -0
  233. package/src/nodes/kalman1d/index.js +20 -0
  234. package/src/nodes/kalman1d/init.js +103 -0
  235. package/src/nodes/kalman1d/introspect.js +250 -0
  236. package/src/nodes/kalman1d/publish-to.js +34 -0
  237. package/src/nodes/kalman1d/recompute.js +23 -0
  238. package/src/nodes/kalman1d/reset.js +29 -0
  239. package/src/nodes/kalman1d/update.js +144 -0
  240. package/src/nodes/kernel/index.js +18 -0
  241. package/src/nodes/kernel/init.js +66 -0
  242. package/src/nodes/kernel/introspect.js +163 -0
  243. package/src/nodes/kernel/presets.js +74 -0
  244. package/src/nodes/kernel/publish-to.js +25 -0
  245. package/src/nodes/kernel/recompute.js +14 -0
  246. package/src/nodes/kernel/reset.js +20 -0
  247. package/src/nodes/kernel/update.js +69 -0
  248. package/src/nodes/lag/index.js +21 -0
  249. package/src/nodes/lag/init.js +87 -0
  250. package/src/nodes/lag/introspect.js +202 -0
  251. package/src/nodes/lag/publish-to.js +64 -0
  252. package/src/nodes/lag/recompute.js +33 -0
  253. package/src/nodes/lag/reset.js +49 -0
  254. package/src/nodes/lag/update.js +133 -0
  255. package/src/nodes/median3/index.js +19 -0
  256. package/src/nodes/median3/init.js +40 -0
  257. package/src/nodes/median3/introspect.js +116 -0
  258. package/src/nodes/median3/publish-to.js +21 -0
  259. package/src/nodes/median3/recompute.js +12 -0
  260. package/src/nodes/median3/reset.js +15 -0
  261. package/src/nodes/median3/update.js +58 -0
  262. package/src/nodes/moments-digest/copy-from.js +12 -0
  263. package/src/nodes/moments-digest/flush.js +17 -0
  264. package/src/nodes/moments-digest/index.js +132 -0
  265. package/src/nodes/moments-digest/init.js +84 -0
  266. package/src/nodes/moments-digest/introspect.js +122 -0
  267. package/src/nodes/moments-digest/prelude.js +28 -0
  268. package/src/nodes/moments-digest/publish-to.js +31 -0
  269. package/src/nodes/moments-digest/recompute.js +9 -0
  270. package/src/nodes/moments-digest/reset.js +20 -0
  271. package/src/nodes/moments-digest/update.js +182 -0
  272. package/src/nodes/node-names.js +67 -0
  273. package/src/nodes/page-hinkley/index.js +20 -0
  274. package/src/nodes/page-hinkley/init.js +66 -0
  275. package/src/nodes/page-hinkley/introspect.js +193 -0
  276. package/src/nodes/page-hinkley/publish-to.js +28 -0
  277. package/src/nodes/page-hinkley/recompute.js +14 -0
  278. package/src/nodes/page-hinkley/reset.js +23 -0
  279. package/src/nodes/page-hinkley/update.js +105 -0
  280. package/src/nodes/pass-if/index.js +21 -0
  281. package/src/nodes/pass-if/init.js +37 -0
  282. package/src/nodes/pass-if/introspect.js +93 -0
  283. package/src/nodes/pass-if/publish-to.js +15 -0
  284. package/src/nodes/pass-if/recompute.js +15 -0
  285. package/src/nodes/pass-if/reset.js +18 -0
  286. package/src/nodes/pass-if/update.js +37 -0
  287. package/src/nodes/persist-if/helpers.js +111 -0
  288. package/src/nodes/persist-if/index.js +9 -0
  289. package/src/nodes/persist-if/init.js +98 -0
  290. package/src/nodes/persist-if/introspect.js +106 -0
  291. package/src/nodes/persist-if/publish-to.js +21 -0
  292. package/src/nodes/persist-if/recompute.js +20 -0
  293. package/src/nodes/persist-if/reset.js +35 -0
  294. package/src/nodes/persist-if/update.js +112 -0
  295. package/src/nodes/persistence-check/index.js +14 -0
  296. package/src/nodes/persistence-check/init.js +38 -0
  297. package/src/nodes/persistence-check/introspect.js +144 -0
  298. package/src/nodes/persistence-check/publish-to.js +11 -0
  299. package/src/nodes/persistence-check/recompute.js +7 -0
  300. package/src/nodes/persistence-check/reset.js +12 -0
  301. package/src/nodes/persistence-check/update.js +57 -0
  302. package/src/nodes/process-index/index.js +15 -0
  303. package/src/nodes/process-index/init.js +48 -0
  304. package/src/nodes/process-index/introspect.js +182 -0
  305. package/src/nodes/process-index/publish-to.js +29 -0
  306. package/src/nodes/process-index/recompute.js +8 -0
  307. package/src/nodes/process-index/reset.js +8 -0
  308. package/src/nodes/process-index/update.js +75 -0
  309. package/src/nodes/ratio/index.js +14 -0
  310. package/src/nodes/ratio/init.js +37 -0
  311. package/src/nodes/ratio/introspect.js +155 -0
  312. package/src/nodes/ratio/publish-to.js +15 -0
  313. package/src/nodes/ratio/recompute.js +7 -0
  314. package/src/nodes/ratio/reset.js +7 -0
  315. package/src/nodes/ratio/update.js +47 -0
  316. package/src/nodes/sanitize/index.js +82 -0
  317. package/src/nodes/sanitize/init.js +82 -0
  318. package/src/nodes/sanitize/introspect.js +194 -0
  319. package/src/nodes/sanitize/publish-to.js +15 -0
  320. package/src/nodes/sanitize/recompute.js +8 -0
  321. package/src/nodes/sanitize/reset.js +14 -0
  322. package/src/nodes/sanitize/update.js +102 -0
  323. package/src/nodes/spike-guard/index.js +14 -0
  324. package/src/nodes/spike-guard/init.js +47 -0
  325. package/src/nodes/spike-guard/introspect.js +130 -0
  326. package/src/nodes/spike-guard/publish-to.js +35 -0
  327. package/src/nodes/spike-guard/recompute.js +14 -0
  328. package/src/nodes/spike-guard/reset.js +19 -0
  329. package/src/nodes/spike-guard/update.js +89 -0
  330. package/src/nodes/sse-emitter/index.js +7 -0
  331. package/src/nodes/sse-emitter/init.js +105 -0
  332. package/src/nodes/sse-emitter/introspect.js +134 -0
  333. package/src/nodes/sse-emitter/publish-to.js +6 -0
  334. package/src/nodes/sse-emitter/recompute.js +16 -0
  335. package/src/nodes/sse-emitter/reset.js +19 -0
  336. package/src/nodes/sse-emitter/update.js +69 -0
  337. package/src/nodes/sse-emitter/utils/broadcast-heartbeat.js +36 -0
  338. package/src/nodes/sse-emitter/utils/build-message.js +31 -0
  339. package/src/nodes/sse-emitter/utils/connection-manager.js +45 -0
  340. package/src/nodes/sse-emitter/utils/endpoint-initializer.js +20 -0
  341. package/src/nodes/sse-emitter/utils/health-monitor.js +38 -0
  342. package/src/nodes/sse-emitter/utils/qos-handler.js +41 -0
  343. package/src/nodes/state-change-detector/helpers.js +35 -0
  344. package/src/nodes/state-change-detector/index.js +12 -0
  345. package/src/nodes/state-change-detector/init.js +56 -0
  346. package/src/nodes/state-change-detector/introspect.js +146 -0
  347. package/src/nodes/state-change-detector/publish-to.js +20 -0
  348. package/src/nodes/state-change-detector/recompute.js +6 -0
  349. package/src/nodes/state-change-detector/reset.js +20 -0
  350. package/src/nodes/state-change-detector/update.js +97 -0
  351. package/src/nodes/sw-stats/index.js +14 -0
  352. package/src/nodes/sw-stats/init.js +51 -0
  353. package/src/nodes/sw-stats/introspect.js +131 -0
  354. package/src/nodes/sw-stats/publish-to.js +45 -0
  355. package/src/nodes/sw-stats/recompute.js +33 -0
  356. package/src/nodes/sw-stats/reset.js +15 -0
  357. package/src/nodes/sw-stats/update.js +39 -0
  358. package/src/nodes/swing-watch/index.js +17 -0
  359. package/src/nodes/swing-watch/init.js +144 -0
  360. package/src/nodes/swing-watch/introspect.js +188 -0
  361. package/src/nodes/swing-watch/publish-to.js +53 -0
  362. package/src/nodes/swing-watch/recompute.js +22 -0
  363. package/src/nodes/swing-watch/reset.js +55 -0
  364. package/src/nodes/swing-watch/update.js +767 -0
  365. package/src/nodes/tally/index.js +15 -0
  366. package/src/nodes/tally/init.js +32 -0
  367. package/src/nodes/tally/introspect.js +116 -0
  368. package/src/nodes/tally/publish-to.js +28 -0
  369. package/src/nodes/tally/recompute.js +10 -0
  370. package/src/nodes/tally/reset.js +10 -0
  371. package/src/nodes/tally/update.js +44 -0
  372. package/src/nodes/threshold/index.js +14 -0
  373. package/src/nodes/threshold/init.js +121 -0
  374. package/src/nodes/threshold/introspect.js +192 -0
  375. package/src/nodes/threshold/publish-to.js +11 -0
  376. package/src/nodes/threshold/recompute.js +8 -0
  377. package/src/nodes/threshold/reset.js +14 -0
  378. package/src/nodes/threshold/update.js +54 -0
  379. package/src/nodes/transform/helpers.js +39 -0
  380. package/src/nodes/transform/index.js +15 -0
  381. package/src/nodes/transform/init.js +35 -0
  382. package/src/nodes/transform/introspect.js +118 -0
  383. package/src/nodes/transform/publish-to.js +23 -0
  384. package/src/nodes/transform/recompute.js +12 -0
  385. package/src/nodes/transform/reset.js +11 -0
  386. package/src/nodes/transform/update.js +32 -0
  387. package/src/nodes/trend/compute-confidence.js +112 -0
  388. package/src/nodes/trend/index.js +14 -0
  389. package/src/nodes/trend/init.js +81 -0
  390. package/src/nodes/trend/introspect.js +151 -0
  391. package/src/nodes/trend/publish-to.js +22 -0
  392. package/src/nodes/trend/recompute.js +15 -0
  393. package/src/nodes/trend/reset.js +26 -0
  394. package/src/nodes/trend/update.js +93 -0
  395. package/src/nodes/tw-stats/copy-from.js +16 -0
  396. package/src/nodes/tw-stats/flush.js +12 -0
  397. package/src/nodes/tw-stats/index.js +41 -0
  398. package/src/nodes/tw-stats/init.js +92 -0
  399. package/src/nodes/tw-stats/introspect.js +171 -0
  400. package/src/nodes/tw-stats/publish-to.js +108 -0
  401. package/src/nodes/tw-stats/recompute.js +9 -0
  402. package/src/nodes/tw-stats/reset.js +16 -0
  403. package/src/nodes/tw-stats/update.js +113 -0
  404. package/src/nodes/unbalance/index.js +15 -0
  405. package/src/nodes/unbalance/init.js +59 -0
  406. package/src/nodes/unbalance/introspect.js +167 -0
  407. package/src/nodes/unbalance/publish-to.js +52 -0
  408. package/src/nodes/unbalance/recompute.js +11 -0
  409. package/src/nodes/unbalance/reset.js +10 -0
  410. package/src/nodes/unbalance/update.js +83 -0
  411. package/src/nodes/vector-distance/index.js +14 -0
  412. package/src/nodes/vector-distance/init.js +54 -0
  413. package/src/nodes/vector-distance/introspect.js +149 -0
  414. package/src/nodes/vector-distance/publish-to.js +35 -0
  415. package/src/nodes/vector-distance/recompute.js +11 -0
  416. package/src/nodes/vector-distance/reset.js +20 -0
  417. package/src/nodes/vector-distance/update.js +139 -0
  418. package/src/nodes/winnow/index.js +12 -0
  419. package/src/nodes/winnow/init.js +82 -0
  420. package/src/nodes/winnow/introspect.js +253 -0
  421. package/src/nodes/winnow/publish-to.js +51 -0
  422. package/src/nodes/winnow/recompute.js +14 -0
  423. package/src/nodes/winnow/reset.js +34 -0
  424. package/src/nodes/winnow/update.js +203 -0
  425. package/src/tools/training/features.js +227 -0
  426. package/src/tools/training/index.js +21 -0
  427. package/src/tools/training/logistic.js +238 -0
  428. package/src/tools/training/metrics.js +126 -0
  429. package/src/tools/training/scale.js +239 -0
  430. package/src/tools/training/validate.js +103 -0
  431. package/src/windowing/count-sliding/create.js +16 -0
  432. package/src/windowing/count-sliding/get-reverse-chronological-index.js +19 -0
  433. package/src/windowing/count-sliding/index.js +6 -0
  434. package/src/windowing/count-sliding/is-not-full.js +5 -0
  435. package/src/windowing/count-sliding/push.js +15 -0
  436. package/src/windowing/count-sliding/reset.js +11 -0
  437. package/src/windowing/count-sliding/size.js +5 -0
  438. package/src/windowing/time-sliding/create.js +25 -0
  439. package/src/windowing/time-sliding/push.js +42 -0
  440. package/CODE_OF_CONDUCT.md +0 -46
  441. package/CONTRIBUTING.md +0 -132
  442. package/docs/.nojekyll +0 -0
  443. package/docs/CONTRIBUTING.md +0 -134
  444. package/docs/README.md +0 -132
  445. package/docs/_coverpage.md +0 -26
  446. package/docs/_media/bg.png +0 -0
  447. package/docs/_media/wink-logo.png +0 -0
  448. package/docs/_navbar.md +0 -14
  449. package/docs/_sidebar.md +0 -2
  450. package/docs/index.html +0 -44
  451. package/eslint.config.js +0 -283
@@ -0,0 +1,253 @@
1
+ /**
2
+ * @fileoverview Introspection metadata for the winnow node.
3
+ *
4
+ * Winnow is a trajectory-aware significance detector. It tracks a
5
+ * projected trajectory from the last significant point and fires when
6
+ * the signal deviates beyond an adaptive, self-tightening threshold.
7
+ *
8
+ * The name comes from agriculture: winnowing separates the grain
9
+ * (significant samples) from the chaff (redundant ones). Pair with
10
+ * passIf for compression, emitIf for edge-to-cloud gating, or
11
+ * threshold on deviation for adaptive alarming.
12
+ *
13
+ * Reads slope, noise, direction, and gate from upstream nodes via
14
+ * configurable field names — does not compute them itself.
15
+ *
16
+ * Validated through 10 compression experiments on synthetic signals
17
+ * and real 20 kHz bearing vibration data.
18
+ */
19
+
20
+ import { validators } from '../../core/utils/validate/index.js';
21
+
22
+ // ── Supported Features ──────────────────────────────────────────────────────
23
+
24
+ const SUPPORTED_STATS = [ 'deviation', 'predicted', 'significant', 'xPrev', 'tPrev' ];
25
+
26
+ const STAT_DESCRIPTIONS = {
27
+ deviation: 'Distance from projected trajectory (always published)',
28
+ predicted: 'Where the trajectory says the signal should be',
29
+ significant: 'Boolean: did the signal stray beyond the adaptive threshold?',
30
+ xPrev: 'Previous tick input value (from 1-sample buffer). Non-NaN only on chi-squared gate-fire keeps. Requires bufferPrev: true.',
31
+ tPrev: 'Previous tick timestamp (from 1-sample buffer). Non-NaN only on chi-squared gate-fire keeps. Requires bufferPrev: true and timestampField.'
32
+ };
33
+
34
+ const SUPPORTED_CONTROL_METHODS = {
35
+ reset: 'Clears anchor, counter, and accumulated state',
36
+ enable: 'Enables node processing',
37
+ disable: 'Disables node processing',
38
+ pause: 'Pauses node processing while keeping state visible',
39
+ unpause: 'Resumes node processing after pause'
40
+ };
41
+
42
+ const NODE_TYPE = 'Winnow';
43
+
44
+ const CAPABILITIES = {
45
+ description: 'Trajectory-aware significance detector — separates the grain from the chaff',
46
+ features: [
47
+ 'Slope-aware deadband: projects anchor forward using the rate of change',
48
+ 'Progressive tightening: threshold narrows as segment grows',
49
+ 'Step detection via chi-squared innovation gate from upstream kalman1d',
50
+ 'Trend reversal detection from upstream trend node',
51
+ 'Adaptive threshold: K × noise floor (zero per-signal tuning)',
52
+ 'Gap prevention: forces a point through after maxGap samples',
53
+ 'Graceful degradation: works without slope/gate/direction (reduces to flat deadband)',
54
+ 'Publishes deviation and predicted for downstream analytics',
55
+ 'Optional 1-sample buffer (bufferPrev): publishes xPrev/tPrev on gate-fire keeps for spike-region anchoring'
56
+ ]
57
+ };
58
+
59
+ // ── Default Options ─────────────────────────────────────────────────────────
60
+
61
+ export const DEFAULT_OPTIONS = {
62
+ K: 2,
63
+ tightenBase: 100,
64
+ maxGap: 500,
65
+ slopeField: 'roc',
66
+ noiseField: 'stdev',
67
+ dirField: 'trendDir',
68
+ gateField: 'gate',
69
+ chi2Threshold: 6.63,
70
+ bufferPrev: false
71
+ };
72
+
73
+ // ── Tunable Parameters ──────────────────────────────────────────────────────
74
+
75
+ export const TUNABLE_PARAMS = [ 'K' ];
76
+
77
+ // ── Getter Functions (defensive copies) ─────────────────────────────────────
78
+
79
+ export const getSupportedStats = () => SUPPORTED_STATS.slice();
80
+ export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
81
+ export const getSupportedControlMethods = () => ( { ...SUPPORTED_CONTROL_METHODS } );
82
+ export const getNodeType = () => NODE_TYPE;
83
+ export const getCapabilities = () => ( {
84
+ ...CAPABILITIES,
85
+ features: CAPABILITIES.features.slice()
86
+ } );
87
+
88
+ // ── DSL Metadata ────────────────────────────────────────────────────────────
89
+
90
+ const DSL_METADATA = {
91
+ specSchema: {
92
+ nodeType: {
93
+ type: 'string',
94
+ required: true,
95
+ value: NODE_TYPE
96
+ },
97
+ name: {
98
+ type: 'string',
99
+ required: true,
100
+ validator: validators.identifier,
101
+ error: 'name must be a valid identifier'
102
+ },
103
+ from: {
104
+ type: 'object',
105
+ required: true,
106
+ properties: {
107
+ x: {
108
+ type: 'string',
109
+ required: true,
110
+ validator: validators.noSpaces,
111
+ error: 'from.x must not contain spaces'
112
+ }
113
+ }
114
+ },
115
+ stats: {
116
+ type: 'object',
117
+ required: true,
118
+ minProperties: 1,
119
+ propertyNames: SUPPORTED_STATS,
120
+ propertySchema: {
121
+ type: 'object',
122
+ required: true,
123
+ properties: {
124
+ storeAs: {
125
+ type: 'string',
126
+ required: true,
127
+ validator: validators.identifier,
128
+ error: 'storeAs must be a valid identifier'
129
+ }
130
+ }
131
+ }
132
+ },
133
+
134
+ // ── Sensitivity ─────────────────────────────────────────
135
+ K: {
136
+ type: 'numberOrFunctionOrFieldKeyed',
137
+ required: false,
138
+ default: DEFAULT_OPTIONS.K,
139
+ validator: validators.positiveOrFunction,
140
+ error: 'K must be positive or a function'
141
+ },
142
+
143
+ // ── Tightening ──────────────────────────────────────────
144
+ tightenBase: {
145
+ type: 'numberOrFieldKeyed',
146
+ required: false,
147
+ default: DEFAULT_OPTIONS.tightenBase,
148
+ validator: validators.positive,
149
+ error: 'tightenBase must be positive'
150
+ },
151
+
152
+ // ── Gap prevention ──────────────────────────────────────
153
+ maxGap: {
154
+ type: 'numberOrFieldKeyed',
155
+ required: false,
156
+ default: DEFAULT_OPTIONS.maxGap,
157
+ integer: true,
158
+ validator: validators.positiveInteger,
159
+ error: 'maxGap must be a positive integer'
160
+ },
161
+
162
+ // ── Upstream field names ─────────────────────────────────
163
+ slopeField: {
164
+ type: 'string',
165
+ required: false,
166
+ default: DEFAULT_OPTIONS.slopeField,
167
+ validator: validators.noSpaces,
168
+ error: 'slopeField must not contain spaces'
169
+ },
170
+ noiseField: {
171
+ type: 'string',
172
+ required: false,
173
+ default: DEFAULT_OPTIONS.noiseField,
174
+ validator: validators.noSpaces,
175
+ error: 'noiseField must not contain spaces'
176
+ },
177
+ dirField: {
178
+ type: 'string',
179
+ required: false,
180
+ default: DEFAULT_OPTIONS.dirField,
181
+ validator: validators.noSpaces,
182
+ error: 'dirField must not contain spaces'
183
+ },
184
+ gateField: {
185
+ type: 'string',
186
+ required: false,
187
+ default: DEFAULT_OPTIONS.gateField,
188
+ validator: validators.noSpaces,
189
+ error: 'gateField must not contain spaces'
190
+ },
191
+
192
+ // ── Step detection threshold ─────────────────────────────
193
+ // numberOrFieldKeyed matches its siblings tightenBase/maxGap and the
194
+ // resolveScalar call in init.js, so a per-field value is accepted.
195
+ chi2Threshold: {
196
+ type: 'numberOrFieldKeyed',
197
+ required: false,
198
+ default: DEFAULT_OPTIONS.chi2Threshold,
199
+ validator: validators.positive,
200
+ error: 'chi2Threshold must be positive'
201
+ },
202
+
203
+ // ── 1-sample buffer for spike-region anchoring ──────────
204
+ bufferPrev: {
205
+ type: 'boolean',
206
+ required: false,
207
+ default: DEFAULT_OPTIONS.bufferPrev
208
+ },
209
+ timestampField: {
210
+ type: 'string',
211
+ required: false,
212
+ validator: validators.noSpaces,
213
+ error: 'timestampField must not contain spaces'
214
+ }
215
+ },
216
+
217
+ crossFieldValidators: [
218
+ {
219
+ fields: [ 'stats', 'bufferPrev' ],
220
+ validator: ( spec ) => {
221
+ // xPrev and tPrev stats require bufferPrev to be true
222
+ if ( ( spec.stats?.xPrev !== undefined || spec.stats?.tPrev !== undefined ) &&
223
+ !spec.bufferPrev ) {
224
+ return false;
225
+ }
226
+ return true;
227
+ },
228
+ error: 'xPrev and tPrev stats require bufferPrev: true'
229
+ },
230
+ {
231
+ fields: [ 'stats', 'timestampField' ],
232
+ validator: ( spec ) => {
233
+ // tPrev stat requires timestampField
234
+ if ( spec.stats?.tPrev !== undefined && spec.timestampField === undefined ) {
235
+ return false;
236
+ }
237
+ return true;
238
+ },
239
+ error: 'tPrev stat requires timestampField to be specified'
240
+ }
241
+ ],
242
+
243
+ // Pattern: NAME_X_OUTPUTS_OPTIONS -> ( name, x, stats, options )
244
+ buildSpec: ( name, x, stats, options ) => ( {
245
+ nodeType: NODE_TYPE,
246
+ name,
247
+ from: { x },
248
+ stats,
249
+ ...options
250
+ } )
251
+ };
252
+
253
+ export const getDSLMetadata = () => ( { ...DSL_METADATA } );
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @fileoverview Publish winnow outputs to the message.
3
+ *
4
+ * Copies deviation, predicted, and significant to the message for
5
+ * downstream consumption. All three are always computed by update()
6
+ * — publishTo iterates only over stats the user configured.
7
+ *
8
+ * When input validation has failed, publishes NaN for numeric stats
9
+ * and false for significant — downstream nodes see invalid data,
10
+ * not stale values.
11
+ */
12
+
13
+ import { publishNaN } from '../../core/utils/node/index.js';
14
+
15
+ const publishTo = function ( state, msg ) {
16
+ if ( state.disable ) return;
17
+
18
+ if ( state.inputValidationFailed ) {
19
+ publishNaN( state, msg );
20
+ return;
21
+ }
22
+
23
+ const stats = state.stats;
24
+
25
+ if ( stats.deviation ) {
26
+ msg[ stats.deviation.storeAs ] = state.deviation;
27
+ }
28
+
29
+ if ( stats.predicted ) {
30
+ msg[ stats.predicted.storeAs ] = state.predicted;
31
+ }
32
+
33
+ if ( stats.significant ) {
34
+ msg[ stats.significant.storeAs ] = state.significant;
35
+ }
36
+
37
+ // ── Buffer-backed stats (conditional on gate-fire keep) ─────────
38
+ // xPrev/tPrev are published as the buffered (k-1) values only when
39
+ // the current sample was kept by Check 2 (chi-squared gate fire).
40
+ // On non-gate keeps and on non-kept messages, they publish NaN —
41
+ // signalling "no lookback anchor for this sample." This prevents
42
+ // noisy non-gate prevValues from degrading the kfSmoothed trajectory.
43
+ if ( state.hasXPrev ) {
44
+ msg[ stats.xPrev.storeAs ] = state.keptByGate ? state.xPrev : NaN;
45
+ }
46
+ if ( state.hasTPrev ) {
47
+ msg[ stats.tPrev.storeAs ] = state.keptByGate ? state.tPrev : NaN;
48
+ }
49
+ }; // publishTo()
50
+
51
+ export default publishTo;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @fileoverview Recompute for winnow — no-op.
3
+ *
4
+ * Winnow's state is a reference trajectory (anchor + slope) and a
5
+ * counter. No accumulated sums or running averages that could drift
6
+ * due to floating-point error. Counter overflow is safe to 2^53
7
+ * (at 20 kHz, that is 14 million years).
8
+ */
9
+
10
+ const recompute = function () {
11
+ return true;
12
+ }; // recompute()
13
+
14
+ export default recompute;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @fileoverview Reset winnow to initial state.
3
+ *
4
+ * Clears the anchor, counter, and all accumulated state. The next
5
+ * message will trigger the warmup path (anchor is null), establishing
6
+ * a fresh reference trajectory.
7
+ *
8
+ * Called by the controller node when a downstream condition fires
9
+ * (e.g., after a step change detected by kalman1d's innovation gate).
10
+ */
11
+
12
+ const reset = function ( state ) {
13
+ state.anchor = null;
14
+ state.anchorSlope = 0;
15
+ state.anchorTime = 0;
16
+ state.lastPassedAt = 0;
17
+ state.prevDirection = null;
18
+ state.counter = 0;
19
+ state.deviation = 0;
20
+ state.predicted = 0;
21
+ state.significant = false;
22
+ state.tunableErrorLogged = false;
23
+
24
+ // ── Buffer state ───────────────────────────────────────────────
25
+ state.bufferedX = NaN;
26
+ state.bufferedT = NaN;
27
+ state.keptByGate = false;
28
+ state.xPrev = NaN;
29
+ state.tPrev = NaN;
30
+
31
+ return true;
32
+ }; // reset()
33
+
34
+ export default reset;
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @fileoverview Update function for the winnow node (hot path).
3
+ *
4
+ * Winnow answers one question per message: "Has this signal strayed
5
+ * from where it was heading?" Five checks run in order — the first
6
+ * one that fires wins. If none fire, the signal is on trajectory
7
+ * and `significant` is set to false.
8
+ *
9
+ * The anchor is the last significant point — its value, slope, and
10
+ * timestamp. Between significant points, the anchor projects forward
11
+ * using its slope: `predicted = anchor + slope × elapsed`. The
12
+ * deviation from this projection, measured against the local noise
13
+ * floor, is the core compression signal.
14
+ *
15
+ * Zero allocation. All state mutations are scalar assignments.
16
+ * prevDirection updates unconditionally on every message — not only
17
+ * when significant (design audit finding: the closure does this on
18
+ * line 86, before the deadband check; missing it causes trend
19
+ * reversals during non-significant periods to be lost).
20
+ */
21
+
22
+ // Extracted helper: set anchor and mark significant.
23
+ // Called from each of the five checks. Zero allocation — scalar
24
+ // assignments only. Reduces update() cyclomatic complexity.
25
+ const setAnchor = function ( state, xVal, slope ) {
26
+ state.anchor = xVal;
27
+ state.anchorSlope = Number.isFinite( slope ) ? slope : 0;
28
+ state.anchorTime = state.counter;
29
+ state.lastPassedAt = state.counter;
30
+ state.significant = true;
31
+ };
32
+
33
+ const update = function ( state, msg ) {
34
+ // ── Guards ──────────────────────────────────────────────────────
35
+ // Disabled: skip everything. Paused: skip update, publishTo still
36
+ // runs (last-known values stay visible downstream).
37
+ if ( state.disable || state.pause ) return state;
38
+
39
+ // Every message increments the counter — used for elapsed time
40
+ // computation and gap prevention.
41
+ state.counter += 1;
42
+
43
+ // ── Read primary input ──────────────────────────────────────────
44
+ // The signal being tracked. Upstream median3 or sanitize should
45
+ // have cleaned it, but we guard anyway.
46
+ const xVal = msg[ state.x ];
47
+
48
+ if ( !Number.isFinite( xVal ) ) {
49
+ state.inputValidationFailed = true;
50
+ return state;
51
+ }
52
+
53
+ state.inputValidationFailed = false;
54
+
55
+ // ── Buffer previous tick (when enabled) ─────────────────────────
56
+ // Copy buffer → output, then overwrite buffer with current values.
57
+ // O(1), zero allocation. The output (xPrev/tPrev) holds the k-1
58
+ // value until publishTo decides whether to publish it.
59
+ if ( state.bufferPrev ) {
60
+ state.xPrev = state.bufferedX;
61
+ state.tPrev = state.bufferedT;
62
+ state.bufferedX = xVal;
63
+ if ( state.timestampField ) {
64
+ state.bufferedT = msg[ state.timestampField ];
65
+ }
66
+ state.keptByGate = false;
67
+ }
68
+
69
+ // ── Resolve tunable K ───────────────────────────────────────────
70
+ // K is the sensitivity multiplier: "store when deviation exceeds
71
+ // K × noise floor." It can be a static number or a function that
72
+ // reads from the message (e.g., scaleBy('signalQuality', 2)).
73
+ // On error, the last good value is retained (assignment fails
74
+ // because RHS throws before LHS is written).
75
+ try {
76
+ state.K = state.KFn( msg ); // eslint-disable-line new-cap
77
+ if ( state.tunableErrorLogged ) {
78
+ state.tunableErrorLogged = false;
79
+ }
80
+ } catch ( error ) {
81
+ if ( !state.tunableErrorLogged ) {
82
+ state.tunableErrorLogged = true;
83
+ console.error( `WinkComposer/${state.nodeType}: tunable threw: ${error.message}` );
84
+ }
85
+ }
86
+
87
+ // ── Read supporting fields from upstream nodes ───────────────────
88
+ // These are optional — winnow degrades gracefully without them:
89
+ // noise undefined → warmup fires forever (safe, visible)
90
+ // slope undefined → anchorSlope stays 0 (flat deadband)
91
+ // direction undefined → no trend reversal triggers
92
+ // gate undefined → no step detection
93
+ const noise = msg[ state.noiseField ];
94
+ const slope = msg[ state.slopeField ];
95
+ const direction = msg[ state.dirField ];
96
+ const gate = msg[ state.gateField ];
97
+
98
+ // ── Detect trend reversal ───────────────────────────────────────
99
+ // Check BEFORE updating prevDirection — we need to compare the
100
+ // current direction against what we saw last time.
101
+ // Exclude 'learning' transitions (trend node warmup) — these are
102
+ // not genuine reversals.
103
+ const dirChanged = (
104
+ state.prevDirection !== null &&
105
+ direction !== state.prevDirection &&
106
+ direction !== 'learning' &&
107
+ state.prevDirection !== 'learning'
108
+ );
109
+
110
+ // Update unconditionally — on EVERY message, not only significant
111
+ // ones. If we only update on significant events, we miss reversals
112
+ // that develop during quiet (non-significant) periods. This was
113
+ // caught in the design audit: the original closure updates
114
+ // prevTrendDir on every call at line 86, before the deadband check.
115
+ state.prevDirection = direction;
116
+
117
+ // ── Compute trajectory projection ───────────────────────────────
118
+ // The anchor projects forward using its slope at the time it was
119
+ // set. For a linear ramp, the projection matches the signal →
120
+ // deviation ≈ 0 → high compression. For a sinusoidal peak, the
121
+ // projection overshoots → deviation grows → significant fires.
122
+ const elapsed = state.counter - state.anchorTime;
123
+ const predicted = state.anchor === null ?
124
+ xVal :
125
+ state.anchor + ( state.anchorSlope * elapsed );
126
+ const deviation = Math.abs( xVal - predicted );
127
+
128
+ // Always publish deviation and predicted — useful for downstream
129
+ // analytics (adaptive alarming, process monitoring) regardless of
130
+ // whether the point is significant.
131
+ state.deviation = deviation;
132
+ state.predicted = predicted;
133
+
134
+ // ── Check 1: Warmup ─────────────────────────────────────────────
135
+ // Anchor not yet set (first message) or noise estimate not
136
+ // available (esStats needs 3 samples to publish). Accept the point
137
+ // and establish the anchor. Every partition starts here.
138
+ if ( state.anchor === null || !Number.isFinite( noise ) ) {
139
+ setAnchor( state, xVal, slope );
140
+ return state;
141
+ }
142
+
143
+ // ── Check 2: Step change ────────────────────────────────────────
144
+ // The upstream kalman1d publishes an innovation gate — a
145
+ // chi-squared(1) statistic that measures how surprising the
146
+ // current sample is relative to the model's prediction. When
147
+ // it exceeds the threshold (6.63 = 99% confidence), the signal
148
+ // did something the model could not predict. This is a structural
149
+ // break — accept immediately and reset the anchor.
150
+ if ( Number.isFinite( gate ) && gate > state.chi2Threshold ) {
151
+ state.keptByGate = true;
152
+ setAnchor( state, xVal, slope );
153
+ return state;
154
+ }
155
+
156
+ // ── Check 3: Trend reversal ─────────────────────────────────────
157
+ // The upstream trend node classifies the signal as learning,
158
+ // stable, rising, or falling. When the direction changes (rising →
159
+ // falling at a peak, stable → rising at a ramp onset), the signal
160
+ // reached an inflection point. Store this point to preserve the
161
+ // shape. The dirChanged flag was computed above, before
162
+ // prevDirection was updated.
163
+ if ( dirChanged ) {
164
+ setAnchor( state, xVal, slope );
165
+ return state;
166
+ }
167
+
168
+ // ── Check 4: Slope-aware deadband with tightening ───────────────
169
+ // The threshold starts at K × noise and narrows as elapsed grows.
170
+ // At elapsed = tightenBase: full threshold (K × noise).
171
+ // At elapsed = 2 × tightenBase: half threshold.
172
+ // At elapsed = 4 × tightenBase: quarter threshold.
173
+ // This progressive tightening ensures that long segments without
174
+ // a stored point become increasingly likely to trigger — placing
175
+ // intermediate points in regions that would otherwise be long gaps.
176
+ const tightenFactor = state.tightenBase / Math.max( state.tightenBase, elapsed );
177
+ const threshold = state.K * noise * tightenFactor;
178
+
179
+ if ( deviation > threshold ) {
180
+ setAnchor( state, xVal, slope );
181
+ return state;
182
+ }
183
+
184
+ // ── Check 5: Gap prevention ─────────────────────────────────────
185
+ // If none of the above fired and it has been maxGap samples since
186
+ // the last significant point, force one through. This prevents
187
+ // data gaps in storage or transmission. In streaming mode there is
188
+ // no "last point"; maxGap serves as the tail anchor.
189
+ if ( ( state.counter - state.lastPassedAt ) >= state.maxGap ) {
190
+ setAnchor( state, xVal, slope );
191
+ return state;
192
+ }
193
+
194
+ // ── Not significant ─────────────────────────────────────────────
195
+ // The signal is on trajectory. deviation and predicted are already
196
+ // set above — publishTo will write them downstream. The point is
197
+ // redundant: it can be reconstructed by linear interpolation
198
+ // between the previous and next significant points.
199
+ state.significant = false;
200
+ return state;
201
+ }; // update()
202
+
203
+ export default update;