@winkjs/composer 0.0.2 → 0.4.0

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 +42 -103
  3. package/package.json +53 -29
  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,15 @@
1
+ /**
2
+ * @fileoverview Entry point for the tally node. Re-exports lifecycle functions,
3
+ * the shared control signals, and introspection metadata.
4
+ */
5
+
6
+ export { default as init } from './init.js';
7
+ export { default as update } from './update.js';
8
+ export { default as reset } from './reset.js';
9
+ export { default as recompute } from './recompute.js';
10
+ export { default as publishTo } from './publish-to.js';
11
+ export { default as disable } from '../../core/utils/node/disable.js';
12
+ export { default as enable } from '../../core/utils/node/enable.js';
13
+ export { default as pause } from '../../core/utils/node/pause.js';
14
+ export { default as unpause } from '../../core/utils/node/unpause.js';
15
+ export * from './introspect.js';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @fileoverview Initializes tally node state from a validated spec. Pre-resolves
3
+ * the field-name list and keeps state.stats so publishTo() / publishNaN() can
4
+ * target every configured output. Stateless: no buffers, no temporal state.
5
+ */
6
+
7
+ import { validateSpec } from '../../core/utils/node/index.js';
8
+ import * as introspect from './introspect.js';
9
+
10
+ const init = function ( spec ) {
11
+ validateSpec( spec, introspect );
12
+
13
+ const state = Object.create( null );
14
+ state.inputValidationFailed = false;
15
+ state.disable = false;
16
+ state.pause = false;
17
+
18
+ state.fields = spec.from.x.slice( 0 );
19
+ state.n = state.fields.length;
20
+ state.stats = spec.stats;
21
+
22
+ // Computed outputs (fixed shape, overwritten each tick).
23
+ state.any = false;
24
+ state.all = false;
25
+ state.count = 0;
26
+
27
+ state.nodeType = introspect.getNodeType();
28
+
29
+ return state;
30
+ }; // init()
31
+
32
+ export default init;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @fileoverview Single source of truth for the tally node — supported stats,
3
+ * capabilities, control methods, and the DSL/validation schema. All getters
4
+ * return defensive copies. `tally` is the logical member of the cross-field
5
+ * family: it reduces N flag fields of one message to a single logical answer —
6
+ * is any flag true (`any`), are all flags true (`all`), how many are true
7
+ * (`count`). Flags are read by truthiness; a `NaN` flag is the one fault and
8
+ * propagates as NaN to every output. `ratio` (two numeric fields) and
9
+ * `unbalance` (N numeric fields, spread) are the numeric members of the family.
10
+ */
11
+
12
+ import { validators } from '../../core/utils/validate/index.js';
13
+
14
+ const SUPPORTED_STATS = [
15
+ 'any',
16
+ 'all',
17
+ 'count'
18
+ ];
19
+
20
+ const STAT_DESCRIPTIONS = {
21
+ any: 'True if at least one input flag is truthy',
22
+ all: 'True only if every input flag is truthy',
23
+ count: 'How many input flags are truthy'
24
+ };
25
+
26
+ const SUPPORTED_CONTROL_METHODS = {
27
+ reset: 'No-op (stateless node)',
28
+ enable: 'Enables node processing',
29
+ disable: 'Disables node processing',
30
+ pause: 'Pauses update() while keeping last values visible via publishTo()',
31
+ unpause: 'Resumes update() after pause'
32
+ };
33
+
34
+ const NODE_TYPE = 'Tally';
35
+
36
+ const CAPABILITIES = {
37
+ description: 'Instantaneous logical reduce over N flag fields in one message — any / all / count',
38
+ features: [
39
+ 'Stateless and allocation-free',
40
+ 'Domain-agnostic (any boolean or truthy/falsy flag fields)',
41
+ 'Reads flags by truthiness; null / undefined / false / 0 count as not-true',
42
+ 'NaN propagation for fault isolation (a NaN flag -> every output NaN)',
43
+ 'Single pass: the count of truthy flags drives any (count >= 1) and all (count === N)'
44
+ ]
45
+ };
46
+
47
+ export const getSupportedStats = () => SUPPORTED_STATS.slice();
48
+ export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
49
+ export const getSupportedControlMethods = () => ( { ...SUPPORTED_CONTROL_METHODS } );
50
+ export const getNodeType = () => NODE_TYPE;
51
+ export const getCapabilities = () => ( { ...CAPABILITIES, features: CAPABILITIES.features.slice() } );
52
+
53
+ const DSL_METADATA = {
54
+ specSchema: {
55
+ nodeType: { type: 'string', required: true, value: NODE_TYPE },
56
+ name: {
57
+ type: 'string',
58
+ required: true,
59
+ validator: validators.identifier,
60
+ error: 'name must be a valid identifier'
61
+ },
62
+ from: {
63
+ type: 'object',
64
+ required: true,
65
+ properties: {
66
+ x: {
67
+ type: 'array',
68
+ required: true,
69
+ minItems: 1,
70
+ itemSchema: {
71
+ type: 'string',
72
+ validator: validators.noSpaces,
73
+ error: 'field names cannot contain spaces'
74
+ }
75
+ }
76
+ }
77
+ },
78
+ stats: {
79
+ type: 'object',
80
+ required: true,
81
+ minProperties: 1,
82
+ propertyNames: SUPPORTED_STATS,
83
+ propertySchema: {
84
+ type: 'object',
85
+ required: true,
86
+ properties: {
87
+ storeAs: {
88
+ type: 'string',
89
+ required: true,
90
+ minLength: 1,
91
+ validator: validators.identifier,
92
+ error: 'storeAs must be a valid identifier'
93
+ }
94
+ }
95
+ }
96
+ }
97
+ },
98
+
99
+ crossFieldValidators: [
100
+ {
101
+ fields: [ 'from.x' ],
102
+ validator: ( spec ) => ( new Set( spec.from.x ).size === spec.from.x.length ),
103
+ error: 'field names must be unique'
104
+ }
105
+ ],
106
+
107
+ buildSpec: ( name, x, stats, options ) => ( {
108
+ nodeType: NODE_TYPE,
109
+ name,
110
+ from: { x },
111
+ stats,
112
+ ...options
113
+ } )
114
+ };
115
+
116
+ export const getDSLMetadata = () => ( { ...DSL_METADATA } );
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @fileoverview Copies the computed tally reductions onto the message, or
3
+ * propagates NaN to every configured output when the last input was a NaN flag.
4
+ * Skipped when disabled; still runs when paused so last values stay visible.
5
+ */
6
+
7
+ import { publishNaN } from '../../core/utils/node/index.js';
8
+
9
+ const publishTo = function ( state, msg ) {
10
+ if ( state.disable ) return;
11
+ if ( state.inputValidationFailed ) {
12
+ publishNaN( state, msg );
13
+ return;
14
+ }
15
+
16
+ const stats = state.stats;
17
+ if ( stats.any ) {
18
+ msg[ stats.any.storeAs ] = state.any;
19
+ }
20
+ if ( stats.all ) {
21
+ msg[ stats.all.storeAs ] = state.all;
22
+ }
23
+ if ( stats.count ) {
24
+ msg[ stats.count.storeAs ] = state.count;
25
+ }
26
+ }; // publishTo()
27
+
28
+ export default publishTo;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @fileoverview Recompute for the tally node. The reduction is exact and
3
+ * stateless — there is no incremental drift to correct — so recompute is a no-op.
4
+ */
5
+
6
+ const recompute = function () {
7
+ return true;
8
+ }; // recompute()
9
+
10
+ export default recompute;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @fileoverview Reset for the tally node. Nothing accumulates — every output is
3
+ * recomputed from scratch each tick — so reset is a no-op.
4
+ */
5
+
6
+ const reset = function () {
7
+ return true;
8
+ }; // reset()
9
+
10
+ export default reset;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @fileoverview Hot path for the tally node.
3
+ *
4
+ * One linear pass reads each flag field by truthiness and counts the truthy
5
+ * flags. The three reductions follow from that count with no second pass:
6
+ * count is the number itself, any is ( count > 0 ), all is ( count === n ).
7
+ *
8
+ * Fault handling: a flag is read by truthiness, so null, undefined, false and 0
9
+ * are not-true ( in composer null/undefined mean "no value this tick", not bad
10
+ * data ). The one fault is NaN — a flag goes NaN only when its own producer
11
+ * faulted — which marks the tick invalid so publishTo writes NaN to every output.
12
+ * This is why the guard is Number.isNaN, not the numeric Number.isFinite.
13
+ */
14
+
15
+ const update = function ( state, msg ) {
16
+ if ( state.disable || state.pause ) return state;
17
+ state.inputValidationFailed = false;
18
+
19
+ const fields = state.fields;
20
+ const n = state.n;
21
+
22
+ let trueCount = 0;
23
+ let i = 0;
24
+
25
+ while ( i < n ) {
26
+ const v = msg[ fields[ i ] ];
27
+ if ( Number.isNaN( v ) ) {
28
+ state.inputValidationFailed = true;
29
+ return state;
30
+ }
31
+ if ( v ) {
32
+ trueCount += 1;
33
+ }
34
+ i += 1;
35
+ }
36
+
37
+ state.count = trueCount;
38
+ state.any = ( trueCount > 0 );
39
+ state.all = ( trueCount === n );
40
+
41
+ return state;
42
+ }; // update()
43
+
44
+ export default update;
@@ -0,0 +1,14 @@
1
+ // nodes/threshold/index.js
2
+
3
+ export { default as init } from './init.js';
4
+ export { default as update } from './update.js';
5
+ export { default as reset } from './reset.js';
6
+ export { default as recompute } from './recompute.js';
7
+ export { default as publishTo } from './publish-to.js';
8
+ // Direct imports for common control functions
9
+ export { default as disable } from '../../core/utils/node/disable.js';
10
+ export { default as enable } from '../../core/utils/node/enable.js';
11
+ export { default as pause } from '../../core/utils/node/pause.js';
12
+ export { default as unpause } from '../../core/utils/node/unpause.js';
13
+ // Re-export everything from introspection.js
14
+ export * from './introspect.js';
@@ -0,0 +1,121 @@
1
+ // nodes/threshold/init.js
2
+
3
+ import { validateSpec } from '../../core/utils/node/index.js';
4
+ import * as introspect from './introspect.js';
5
+ import { resolveScalar } from '../../core/utils/options/resolve-field-keyed.js';
6
+ import { asTunable } from '../../core/tunable/index.js';
7
+
8
+ // Mode handler functions - pure functions for threshold checking
9
+ const MODE_HANDLERS = {
10
+ above: function (value, state) {
11
+ if (state.hasHysteresis) {
12
+ // With hysteresis: different thresholds for activation/deactivation
13
+ return state.active ?
14
+ value >= (state.threshold - state.hysteresis) : // Stay active until below reset point (inclusive)
15
+ value >= state.threshold; // Activate at threshold
16
+ }
17
+ // Simple threshold
18
+ return value >= state.threshold;
19
+ },
20
+
21
+ below: function (value, state) {
22
+ if (state.hasHysteresis) {
23
+ // With hysteresis: opposite direction from 'above'
24
+ return state.active ?
25
+ value <= (state.threshold + state.hysteresis) : // Stay active until above reset point (inclusive)
26
+ value <= state.threshold; // Activate at threshold
27
+ }
28
+ // Simple threshold
29
+ return value <= state.threshold;
30
+ },
31
+
32
+ outside: function (value, state) {
33
+ if (state.hasHysteresis) {
34
+ // With hysteresis: need to come further inside to deactivate
35
+ if (state.active) {
36
+ // Currently active (outside) - need to come well inside to deactivate
37
+ return value <= (state.min + state.hysteresis) || value >= (state.max - state.hysteresis);
38
+ }
39
+ // Currently inactive (inside) - activate when going outside original bounds
40
+ return value < state.min || value > state.max;
41
+ }
42
+ // Simple range check
43
+ return value < state.min || value > state.max;
44
+ },
45
+
46
+ inside: function (value, state) {
47
+ if (state.hasHysteresis) {
48
+ // With hysteresis: expand range for deactivation
49
+ return state.active ?
50
+ value >= (state.min - state.hysteresis) && value <= (state.max + state.hysteresis) :
51
+ value >= state.min && value <= state.max;
52
+ }
53
+ // Simple range check
54
+ return value >= state.min && value <= state.max;
55
+ }
56
+ };
57
+
58
+ const init = function (spec) {
59
+ validateSpec( spec, introspect );
60
+
61
+ // Create state after validation passes
62
+ const state = Object.create(null);
63
+ // Ensure node is enabled by default
64
+ state.disable = false;
65
+ state.pause = false;
66
+
67
+ // Extract common configuration
68
+ state.x = spec.from.x;
69
+ state.mode = spec.mode;
70
+
71
+ // Apply defaults from introspect (validation doesn't enforce them)
72
+ // Supports direct, field-keyed, and tunable specification
73
+ const hysteresisSpec = resolveScalar( spec.hysteresis, state.x );
74
+ state.hysteresisFn = asTunable( hysteresisSpec ?? introspect.DEFAULT_OPTIONS.hysteresis );
75
+
76
+ // Store mode-specific parameters (supports field-keying and tunables)
77
+ if ( spec.mode === 'above' || spec.mode === 'below' ) {
78
+ const thresholdSpec = resolveScalar( spec.threshold, state.x );
79
+ state.thresholdFn = asTunable( thresholdSpec );
80
+ } else {
81
+ const minSpec = resolveScalar( spec.min, state.x );
82
+ const maxSpec = resolveScalar( spec.max, state.x );
83
+ state.minFn = asTunable( minSpec );
84
+ state.maxFn = asTunable( maxSpec );
85
+ }
86
+
87
+ // Seed tunable state fields — overwritten on every successful update().
88
+ // hysteresis has a DEFAULT_OPTIONS entry; threshold/min/max are required
89
+ // user values with no defaults — undefined keeps node inactive until
90
+ // first successful resolve (value >= undefined → false in all modes).
91
+ state.hysteresis = introspect.DEFAULT_OPTIONS.hysteresis;
92
+ state.hasHysteresis = false;
93
+ if ( spec.mode === 'above' || spec.mode === 'below' ) {
94
+ state.threshold = undefined;
95
+ } else {
96
+ state.min = undefined;
97
+ state.max = undefined;
98
+ }
99
+ // Log suppression: one log per error episode; reset on recovery.
100
+ state.tunableErrorLogged = false;
101
+
102
+ // Config copy
103
+ state.stats = spec.stats;
104
+
105
+ // State variables - clear boolean semantics
106
+ state.active = false; // Current state
107
+ state.wasActive = false; // Previous state (for trigger detection)
108
+
109
+ // For hysteresis, we need to know if we've seen any values yet
110
+ state.hasSeenValue = false;
111
+
112
+ // Assign the appropriate handler function based on mode (no allocation, just reference)
113
+ state.checkActive = MODE_HANDLERS[spec.mode];
114
+
115
+ state.nodeType = introspect.getNodeType();
116
+ state.inControlPhase = false;
117
+
118
+ return state;
119
+ }; // init()
120
+
121
+ export default init;
@@ -0,0 +1,192 @@
1
+ // nodes/threshold/introspect.js
2
+
3
+ import { validators, composerValidators } from '../../core/utils/validate/index.js';
4
+ import { resolveScalar } from '../../core/utils/options/resolve-field-keyed.js';
5
+
6
+ /** Single source of truth for what this plugin can do */
7
+ const SUPPORTED_STATS = [
8
+ 'active' // Current state - the only output
9
+ ];
10
+
11
+ /** Human-readable descriptions (for introspection) */
12
+ const STAT_DESCRIPTIONS = {
13
+ active: 'Boolean state indicating if threshold condition is currently met'
14
+ };
15
+
16
+ /** Supported threshold modes */
17
+ const THRESHOLD_MODES = [ 'above', 'below', 'inside', 'outside' ];
18
+
19
+ /** Control methods available for triggers */
20
+ const SUPPORTED_CONTROL_METHODS = {
21
+ reset: 'Clears accumulated state and restarts computation',
22
+ enable: 'Enables node processing',
23
+ disable: 'Disables node processing',
24
+ pause: 'Pauses node processing while keeping state visible',
25
+ unpause: 'Resumes node processing after pause'
26
+ };
27
+
28
+ /** The type of this node */
29
+ const NODE_TYPE = 'Threshold';
30
+
31
+ /** Node capabilities for documentation */
32
+ const CAPABILITIES = {
33
+ description: 'Multi-mode threshold detection with optional hysteresis for stable state transitions',
34
+ features: [
35
+ 'Mode: above - Activates when value exceeds threshold',
36
+ 'Mode: below - Activates when value falls below threshold',
37
+ 'Mode: inside - Activates when value is within range',
38
+ 'Mode: outside - Activates when value is outside range',
39
+ 'Hysteresis support for chatter-free operation'
40
+ ]
41
+ };
42
+
43
+ // Export functions for accessing metadata
44
+ export const getSupportedStats = () => SUPPORTED_STATS.slice();
45
+ export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
46
+ export const getSupportedControlMethods = () => ( { ...SUPPORTED_CONTROL_METHODS } );
47
+ export const getNodeType = () => NODE_TYPE;
48
+ export const getCapabilities = () => ( { ...CAPABILITIES, features: CAPABILITIES.features.slice() } );
49
+ export const getThresholdModes = () => THRESHOLD_MODES.slice();
50
+
51
+ export const DEFAULT_OPTIONS = {
52
+ hysteresis: 0
53
+ };
54
+
55
+ /** Parameters that support tunable (dynamic) values */
56
+ export const TUNABLE_PARAMS = [ 'threshold', 'min', 'max', 'hysteresis' ];
57
+
58
+ // DSL Metadata for transpilation
59
+ const DSL_METADATA = {
60
+ // Spec schema for validation
61
+ specSchema: {
62
+ nodeType: {
63
+ type: 'string',
64
+ required: true,
65
+ value: NODE_TYPE
66
+ },
67
+ name: {
68
+ type: 'string',
69
+ required: true,
70
+ validator: validators.identifier,
71
+ error: 'Name must be a valid identifier'
72
+ },
73
+ from: {
74
+ type: 'object',
75
+ required: true,
76
+ properties: {
77
+ x: {
78
+ type: 'string',
79
+ required: true,
80
+ validator: validators.noSpaces,
81
+ error: 'Field name cannot contain spaces'
82
+ }
83
+ }
84
+ },
85
+ mode: {
86
+ type: 'string',
87
+ required: true,
88
+ validator: validators.oneOf(THRESHOLD_MODES),
89
+ error: `Mode must be one of: ${THRESHOLD_MODES.join(', ')}`
90
+ },
91
+ // For 'above' and 'below' modes (supports tunable for adaptive thresholds)
92
+ threshold: {
93
+ type: 'numberOrFunctionOrFieldKeyed',
94
+ required: false, // Validated by cross-field validator
95
+ validator: ( value ) => typeof value === 'function' || ( !isNaN( value ) && isFinite( value ) ),
96
+ error: 'Threshold must be a finite number or function'
97
+ },
98
+ // For 'inside' and 'outside' modes (supports tunable for adaptive ranges)
99
+ min: {
100
+ type: 'numberOrFunctionOrFieldKeyed',
101
+ required: false, // Validated by cross-field validator
102
+ validator: ( value ) => typeof value === 'function' || ( !isNaN( value ) && isFinite( value ) ),
103
+ error: 'Min must be a finite number or function'
104
+ },
105
+ max: {
106
+ type: 'numberOrFunctionOrFieldKeyed',
107
+ required: false, // Validated by cross-field validator
108
+ validator: ( value ) => typeof value === 'function' || ( !isNaN( value ) && isFinite( value ) ),
109
+ error: 'Max must be a finite number or function'
110
+ },
111
+ // Optional hysteresis for all modes (supports tunable for adaptive hysteresis)
112
+ hysteresis: {
113
+ type: 'numberOrFunctionOrFieldKeyed',
114
+ required: false,
115
+ default: 0,
116
+ validator: validators.nonNegativeOrFunction,
117
+ error: 'Hysteresis must be a non-negative finite number or function'
118
+ },
119
+ stats: {
120
+ type: 'object',
121
+ required: true,
122
+ minProperties: 1,
123
+ propertyNames: SUPPORTED_STATS,
124
+ propertySchema: {
125
+ type: 'object',
126
+ required: true,
127
+ properties: {
128
+ storeAs: {
129
+ type: 'string',
130
+ required: true,
131
+ minLength: 1,
132
+ validator: validators.identifier,
133
+ error: 'storeAs must be a valid identifier'
134
+ }
135
+ }
136
+ }
137
+ },
138
+ triggers: {
139
+ type: 'array',
140
+ required: false,
141
+ itemSchema: {
142
+ type: 'object',
143
+ validator: composerValidators.trigger,
144
+ error: 'Invalid trigger specification'
145
+ }
146
+ }
147
+ },
148
+
149
+ // Cross-field validators
150
+ crossFieldValidators: [
151
+ {
152
+ fields: [ 'mode', 'threshold', 'min', 'max' ],
153
+ validator: ( spec ) => {
154
+ // For 'above' and 'below' modes, require threshold
155
+ if ( spec.mode === 'above' || spec.mode === 'below' ) {
156
+ if ( spec.threshold === undefined ) return false;
157
+ // These modes should not have min/max
158
+ if ( spec.min !== undefined || spec.max !== undefined ) return false;
159
+ }
160
+ // For 'inside' and 'outside' modes, require min and max
161
+ if ( spec.mode === 'inside' || spec.mode === 'outside' ) {
162
+ if ( spec.min === undefined || spec.max === undefined ) return false;
163
+ // These modes should not have threshold
164
+ if ( spec.threshold !== undefined ) return false;
165
+ // Resolve for the node's field so a direct value and a field-keyed
166
+ // map are compared the same way; min must be less than max
167
+ // (skip the comparison when either resolves to a function).
168
+ const min = resolveScalar( spec.min, spec.from?.x );
169
+ const max = resolveScalar( spec.max, spec.from?.x );
170
+ if ( typeof min !== 'function' && typeof max !== 'function' ) {
171
+ if ( min >= max ) return false;
172
+ }
173
+ }
174
+ return true;
175
+ },
176
+ error: 'Invalid parameters for mode. Above/below need threshold. Inside/outside need min and max (min < max).'
177
+ },
178
+ composerValidators.noSelfTriggers
179
+ ],
180
+
181
+ // How to build the spec from DSL params
182
+ buildSpec: ( name, x, stats, options ) => ( {
183
+ nodeType: NODE_TYPE,
184
+ name,
185
+ from: { x },
186
+ stats,
187
+ ...options
188
+ } ),
189
+
190
+ };
191
+
192
+ export const getDSLMetadata = () => ( { ...DSL_METADATA } );
@@ -0,0 +1,11 @@
1
+ // nodes/threshold/publish-to.js
2
+
3
+ const publishTo = function ( state, msg ) {
4
+ if ( state.disable ) return;
5
+ // Only publish active stat
6
+ if ( state.stats.active ) {
7
+ msg[ state.stats.active.storeAs ] = state.active;
8
+ }
9
+ }; // publishTo()
10
+
11
+ export default publishTo;
@@ -0,0 +1,8 @@
1
+ // nodes/threshold/recompute.js
2
+
3
+ const recompute = function () {
4
+ // Nothing to recompute - threshold is stateless except for hysteresis memory
5
+ return true;
6
+ }; // recompute()
7
+
8
+ export default recompute;
@@ -0,0 +1,14 @@
1
+ // nodes/threshold/reset.js
2
+
3
+ const reset = function ( state ) {
4
+ // Reset to initial conditions
5
+ state.active = false;
6
+ state.wasActive = false;
7
+ state.hasSeenValue = false;
8
+ // Clear error suppression so next error episode is logged
9
+ state.tunableErrorLogged = false;
10
+
11
+ return true;
12
+ }; // reset()
13
+
14
+ export default reset;
@@ -0,0 +1,54 @@
1
+ // nodes/threshold/update.js
2
+
3
+ import { executeTriggers } from '../../core/utils/node/index.js';
4
+
5
+ const update = function ( state, msg ) {
6
+ if ( state.disable || state.pause ) return state;
7
+ // Extract value using field name
8
+ const xVal = msg[ state.x ];
9
+
10
+ // Skip if not a valid number (different from regular NaN publishing)
11
+ if ( !Number.isFinite( xVal ) ) return state;
12
+
13
+ // Guard tunable resolve — on throw, state retains previous good value
14
+ // (JS: RHS evaluated first; failed RHS ⇒ assignment never executes).
15
+ try {
16
+ state.hysteresis = state.hysteresisFn( msg );
17
+ state.hasHysteresis = state.hysteresis > 0;
18
+ if ( state.thresholdFn ) {
19
+ state.threshold = state.thresholdFn( msg );
20
+ } else {
21
+ state.min = state.minFn( msg );
22
+ state.max = state.maxFn( msg );
23
+ }
24
+ // Recovery: clear log-suppression flag on success
25
+ if ( state.tunableErrorLogged ) state.tunableErrorLogged = false;
26
+ } catch ( error ) {
27
+ // Log first error per episode; suppress subsequent until recovery
28
+ if ( !state.tunableErrorLogged ) {
29
+ state.tunableErrorLogged = true;
30
+ console.error( `WinkComposer/${state.nodeType}: tunable threw: ${error.message}` );
31
+ }
32
+ }
33
+
34
+ // Remember previous state for trigger detection
35
+ state.wasActive = state.active;
36
+
37
+ // Update threshold state using mode-specific handler
38
+ if ( state.hasSeenValue ) {
39
+ state.active = state.checkActive( xVal, state );
40
+ } else {
41
+ // First value - compute initial state
42
+ state.active = state.checkActive( xVal, state );
43
+ state.hasSeenValue = true;
44
+ }
45
+
46
+ // Trigger controls on activation if configured (rising edge only)
47
+ if ( state.active && !state.wasActive ) {
48
+ executeTriggers( state );
49
+ }
50
+
51
+ return state;
52
+ }; // update()
53
+
54
+ export default update;