@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,225 @@
1
+ /**
2
+ * @fileoverview `forEach` - the chain fan. It runs a callback once per field in a
3
+ * concrete list, giving each run an `each` handle, and adds one copy of the chain
4
+ * the callback builds for that field. It is the multi-node sibling of array sugar:
5
+ * array sugar fans a single node across a field list; `forEach` fans a whole chain,
6
+ * including predicate-input nodes (e.g. `persistenceCheck`) and two-input nodes
7
+ * (e.g. `ratio`) that array sugar cannot reach.
8
+ *
9
+ * Everything happens at build time. The fan expands into ordinary node specs through
10
+ * the same machinery the per-node DSL methods use - the signature-pattern dispatch
11
+ * (`spec-builder.js`), the shared router (`get-target-array.js`), the collision
12
+ * checkers, and `init` validation - so nothing new runs per message and the wiring,
13
+ * partition, and run layers are untouched.
14
+ *
15
+ * Naming is the fan-owned scheme (ADR-006/ADR-007): a node's
16
+ * name takes the field as a suffix (`clean` -> `clean_scb1`); a node's output takes
17
+ * the field as a prefix (the output labelled `delta` is stored as `scb1_delta`). The
18
+ * scheme is fixed, not the configurable naming policy, so the names are predictable -
19
+ * which `each.out` and the reduce nodes (`tally`, `unbalance`) rely on.
20
+ *
21
+ * @see get-target-array.js - the shared spec router
22
+ * @see resolve-pick-by-field.js - the build-time pickByField resolver
23
+ * @see expand-single-input-array.js - the single-node fan (array sugar)
24
+ */
25
+
26
+ import * as nodes from '../nodes/index.js';
27
+ import { validateWithSchema } from '../core/utils/validate/index.js';
28
+ import { schemas } from './signatures-schema.js';
29
+ import { SIGNATURE_PATTERNS } from './consts.js';
30
+ import { getSignaturePattern } from './get-signature-pattern.js';
31
+ import { specBuilder } from './spec-builder.js';
32
+ import { getTargetArray } from './get-target-array.js';
33
+ import { resolvePickByField } from './resolve-pick-by-field.js';
34
+
35
+ /**
36
+ * Locate the index of a named argument slot in a signature schema (the source of
37
+ * truth for argument positions). Returns -1 when the pattern has no such slot.
38
+ *
39
+ * @param {Array} slots - The positional schema for a signature pattern
40
+ * @param {string} name - The slot name to find ('outputs' or 'options')
41
+ * @returns {number} The slot index, or -1
42
+ */
43
+ const slotIndex = function ( slots, name ) {
44
+ for ( let i = 0; i < slots.length; i += 1 ) {
45
+ if ( slots[ i ].name === name ) {
46
+ return i;
47
+ }
48
+ }
49
+ return -1;
50
+ }; // slotIndex()
51
+
52
+ /**
53
+ * Build `api.forEach`. Precomputes node metadata once, then returns the chainable
54
+ * `forEach( fields, callback )` method.
55
+ *
56
+ * @param {Object} api - The fluent API object (returned by forEach for chaining)
57
+ * @param {Object} flowState - Shared flow build state (definitions, checkers, etc.)
58
+ * @returns {Function} The forEach method
59
+ */
60
+ export const makeForEach = function ( api, flowState ) {
61
+ const {
62
+ flowDefinition,
63
+ importSet,
64
+ isNodeNameDuplicate,
65
+ switchState,
66
+ groupByState,
67
+ markPipelineStarted
68
+ } = flowState;
69
+
70
+ // Precompute metadata once, over the same DSL-eligible node filter flow.js uses.
71
+ const metaByNode = Object.create( null );
72
+ const nodeNames = Object.keys( nodes ).filter( ( n ) => ( typeof nodes[ n ]?.getDSLMetadata === 'function' ) );
73
+ for ( let i = 0; i < nodeNames.length; i += 1 ) {
74
+ metaByNode[ nodeNames[ i ] ] = nodes[ nodeNames[ i ] ].getDSLMetadata();
75
+ }
76
+
77
+ // Expand one fanned node call for one field: transform the args (fan naming +
78
+ // pickByField resolution), then build, validate, and route the spec through the
79
+ // shared machinery. `recorded` maps each output label to its stored field, for
80
+ // this channel's `each.out`.
81
+ const fanBuild = function ( node, meta, field, recorded, args ) {
82
+ importSet.add( node );
83
+
84
+ const pattern = getSignaturePattern( meta.specSchema );
85
+ const callContext = `.forEach[ ${field} ].${node}( ${args[ 0 ]} )`;
86
+
87
+ try {
88
+ // Controllers orchestrate other nodes by name; fanning one would leave its
89
+ // trigger targets unfanned and pointing at the wrong nodes. Reject it loudly
90
+ // rather than emit a silently-wrong per-field controller.
91
+ if ( pattern === SIGNATURE_PATTERNS.nameLogic ) {
92
+ throw new Error(
93
+ `WinkComposer/flow: forEach cannot fan a controller ('${args[ 0 ]}'); ` +
94
+ 'fan data-processing nodes only.'
95
+ );
96
+ }
97
+
98
+ // Validate the original args, so any message names what the author wrote.
99
+ const status = validateWithSchema( schemas[ pattern ], [ ...args ], 'Arg' );
100
+ status.throwIfInvalid( 'argument' );
101
+
102
+ const slots = schemas[ pattern ];
103
+ const outIdx = slotIndex( slots, 'outputs' );
104
+ const optIdx = slotIndex( slots, 'options' );
105
+
106
+ const t = [ ...args ];
107
+ t[ 0 ] = `${args[ 0 ]}_${field}`;
108
+
109
+ // Node-name collision check before building stats (matches make-node-method),
110
+ // so a rejected node never registers its output params in the global checker.
111
+ if ( isNodeNameDuplicate( t[ 0 ] ) ) {
112
+ throw new Error( `WinkComposer/flow: duplicate node ${t[ 0 ]} found.` );
113
+ }
114
+
115
+ // Outputs: store each labelled output as `${field}_${label}`, and record the
116
+ // label -> stored-field map so a later step can read it via each.out.
117
+ if ( outIdx >= 0 ) {
118
+ const rawOutputs = args[ outIdx ];
119
+ const fanOutputs = Object.create( null );
120
+ const outKeys = Object.keys( rawOutputs );
121
+ for ( let i = 0; i < outKeys.length; i += 1 ) {
122
+ const label = rawOutputs[ outKeys[ i ] ];
123
+ if ( Object.prototype.hasOwnProperty.call( recorded, label ) ) {
124
+ console.warn(
125
+ `WinkComposer/flow: forEach output label '${label}' written by ` +
126
+ `more than one step on field '${field}'; each.out will return the last.`
127
+ );
128
+ }
129
+ recorded[ label ] = `${field}_${label}`;
130
+ fanOutputs[ outKeys[ i ] ] = `${field}_${label}`;
131
+ }
132
+ t[ outIdx ] = fanOutputs;
133
+ }
134
+
135
+ // Options: resolve any pickByField marker to this channel's value. Every
136
+ // fannable pattern has an options slot, so optIdx is always >= 0 here; the
137
+ // guard is only for an omitted (optional) options argument.
138
+ if ( optIdx < t.length ) {
139
+ t[ optIdx ] = resolvePickByField( args[ optIdx ], field );
140
+ }
141
+
142
+ const buildSpecFor = specBuilder( meta );
143
+ const spec = buildSpecFor[ pattern ]( t );
144
+ const targetArray = getTargetArray( switchState, groupByState, flowDefinition );
145
+ nodes[ node ].init( spec );
146
+ targetArray.push( spec );
147
+ } catch ( error ) {
148
+ throw new Error(
149
+ `WinkComposer/flow: Failed to process "${callContext}"\n` +
150
+ ` Reason: ${error.message}\n`
151
+ );
152
+ }
153
+ }; // fanBuild()
154
+
155
+ // Build the per-channel `each` handle: the node methods (fan-aware, chainable),
156
+ // the current field, and each.out for reading an earlier step's stored field.
157
+ const makeEachHandle = function ( field ) {
158
+ const recorded = Object.create( null );
159
+ const each = Object.create( null );
160
+
161
+ for ( let i = 0; i < nodeNames.length; i += 1 ) {
162
+ const node = nodeNames[ i ];
163
+ each[ node ] = function ( ...args ) {
164
+ fanBuild( node, metaByNode[ node ], field, recorded, args );
165
+ return each;
166
+ };
167
+ }
168
+
169
+ each.field = field;
170
+ each.out = function ( label ) {
171
+ if ( !Object.prototype.hasOwnProperty.call( recorded, label ) ) {
172
+ throw new Error(
173
+ `WinkComposer/flow: each.out( '${label}' ) on field '${field}': ` +
174
+ `no earlier step in this chain wrote an output labelled '${label}'.`
175
+ );
176
+ }
177
+ return recorded[ label ];
178
+ };
179
+
180
+ return each;
181
+ }; // makeEachHandle()
182
+
183
+ // The shared per-field routine. The plain-array driver below calls it in a loop;
184
+ // a future groupBy-deferred path would call the same routine per resolved field.
185
+ const expandForField = function ( field, callback ) {
186
+ callback( makeEachHandle( field ) );
187
+ }; // expandForField()
188
+
189
+ return function ( fields, callback ) {
190
+ // forEach inside a groupBy is deferred: there the field list is a pickByGroup
191
+ // that only resolves at .endGroup(), so the callback cannot run where written.
192
+ if ( groupByState.active ) {
193
+ throw new Error(
194
+ 'WinkComposer/flow: forEach inside groupBy is not supported yet; ' +
195
+ 'the field list must be a concrete array at the call site.'
196
+ );
197
+ }
198
+ if ( !Array.isArray( fields ) || fields.length === 0 ) {
199
+ throw new Error( 'WinkComposer/flow: forEach requires a non-empty array of field names.' );
200
+ }
201
+ if ( typeof callback !== 'function' ) {
202
+ throw new Error( 'WinkComposer/flow: forEach requires a callback function.' );
203
+ }
204
+
205
+ const seen = Object.create( null );
206
+ for ( let i = 0; i < fields.length; i += 1 ) {
207
+ const field = fields[ i ];
208
+ if ( typeof field !== 'string' || field.length === 0 ) {
209
+ throw new Error( 'WinkComposer/flow: forEach field names must be non-empty strings.' );
210
+ }
211
+ if ( Object.prototype.hasOwnProperty.call( seen, field ) ) {
212
+ throw new Error( `WinkComposer/flow: forEach duplicate field '${field}' in field list.` );
213
+ }
214
+ seen[ field ] = 1;
215
+ }
216
+
217
+ markPipelineStarted();
218
+
219
+ for ( let i = 0; i < fields.length; i += 1 ) {
220
+ expandForField( fields[ i ], callback );
221
+ }
222
+
223
+ return api;
224
+ };
225
+ }; // makeForEach()
@@ -0,0 +1,31 @@
1
+ import { SIGNATURE_PATTERNS } from './consts.js';
2
+
3
+ export const getSignaturePattern = function ( schema ) {
4
+ // Controller node
5
+ if ( !schema.from && schema.logic ) return SIGNATURE_PATTERNS.nameLogic;
6
+
7
+ // Pure predicate based nodes (no inputs)
8
+ if ( !schema.from && schema.predicate ) {
9
+ return schema.stats?.required ?
10
+ // e.g. presistenceCheck
11
+ SIGNATURE_PATTERNS.namePredicateOutputsOptions :
12
+ // e.f. PassIf
13
+ SIGNATURE_PATTERNS.namePredicateOptions;
14
+ }
15
+
16
+ // Input-based nodes
17
+ const props = schema.from?.properties;
18
+ // This schema has no `from`, `logic`, or `predicate`, so its shape is not one we
19
+ // recognize. Return `unknown` so make-node-method.js can raise its clear "unknown
20
+ // signature pattern" error, instead of crashing on `props.x` below with a raw
21
+ // TypeError.
22
+ if ( !props ) return SIGNATURE_PATTERNS.unknown;
23
+ if ( props.x && !props.y && !schema.stats ) return SIGNATURE_PATTERNS.nameXOptions; // momentsDigest: outputs are implied
24
+ // Single input node i.e. only `x` e.g. delta
25
+ if ( props.x && !props.y ) return SIGNATURE_PATTERNS.nameXOutputsOptions;
26
+ // Dual input node i.e. `x` and `y` e.g. diff
27
+ if ( props.x && props.y ) return SIGNATURE_PATTERNS.nameXYOutputsOptions;
28
+
29
+ // Unable to determine signature pattern
30
+ return SIGNATURE_PATTERNS.unknown;
31
+ }; // getSignaturePattern()
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @fileoverview Routes built node specs to the array they belong in, based on the
3
+ * flow's current switch/case/groupBy state. This is the single router used by every
4
+ * spec-producing path in the flow DSL: the per-node method (`make-node-method.js`)
5
+ * and the `forEach` fan (`for-each.js`). Keeping it in one leaf module means both
6
+ * paths land specs identically and there is no second copy to drift.
7
+ *
8
+ * Why a function and not a field: the target depends on transient build state
9
+ * (are we collecting a groupBy template? inside a `.case()`? in the linear flow?),
10
+ * which is only known at the moment a spec is produced.
11
+ */
12
+
13
+ /**
14
+ * Determines the target array for spec accumulation based on switch/case/groupBy state.
15
+ *
16
+ * @param {Object} switchState - The switch/case state machine
17
+ * @param {Object} groupByState - The groupBy state machine
18
+ * @param {Array} flowDefinition - Default target for single-pipeline mode
19
+ * @returns {Array} Target array to push specs into
20
+ * @throws {Error} If node is called in invalid state (outside case block, after break)
21
+ */
22
+ export const getTargetArray = function ( switchState, groupByState, flowDefinition ) {
23
+ // GroupBy template collection mode - route to template array
24
+ if ( groupByState && groupByState.active ) {
25
+ return groupByState.templateSpecs;
26
+ }
27
+
28
+ // Switch/case routing
29
+ if ( switchState && switchState.active ) {
30
+ if ( switchState.currentCase === null ) {
31
+ throw new Error(
32
+ 'WinkComposer/flow: node called outside of a .case() block - ' +
33
+ 'after .switch(), you must call .case() before adding nodes'
34
+ );
35
+ }
36
+ if ( switchState.caseEnded ) {
37
+ throw new Error(
38
+ 'WinkComposer/flow: node called after .break() - ' +
39
+ 'start a new .case() before adding more nodes'
40
+ );
41
+ }
42
+ return switchState.caseSpecs[ switchState.currentCase ];
43
+ }
44
+
45
+ return flowDefinition;
46
+ }; // getTargetArray()
@@ -0,0 +1,67 @@
1
+ // flow/inspect.js
2
+
3
+ /**
4
+ * @fileoverview Flow inspection for debugging.
5
+ *
6
+ * Returns structured information about the flow configuration
7
+ * for debugging and documentation purposes.
8
+ *
9
+ * Supports both single-pipeline and multi-specialization (switch/case) modes.
10
+ */
11
+
12
+ /**
13
+ * Returns debug information about the flow.
14
+ *
15
+ * @param {string} flowName - Name of the flow
16
+ * @param {Array} flowDefinition - Node specifications (for single-pipeline mode)
17
+ * @param {Set} importSet - Set of node names used
18
+ * @param {Object} runtime - Runtime configuration
19
+ * @param {Object} [switchState] - Switch/case state machine (for multi-specialization)
20
+ * @returns {Object} Flow inspection data
21
+ */
22
+ export const inspectFlow = function ( flowName, flowDefinition, importSet, runtime, switchState ) {
23
+ const result = {
24
+ flowName,
25
+ imports: Array.from( importSet ).sort(),
26
+ runtime: {
27
+ hasSource: runtime.source !== null,
28
+ emitterCount: Object.keys( runtime.emitters ).length,
29
+ storageCount: Object.keys( runtime.storages || {} ).length,
30
+ partitionField: runtime.partitionField,
31
+ specializationField: runtime.specializationField,
32
+ yieldThreshold: runtime.yieldThreshold,
33
+ assetClass: runtime.assetClass ? {
34
+ name: runtime.assetClass.name,
35
+ insightTypes: Object.keys( runtime.assetClass.insightTypes || {} )
36
+ } : null
37
+ }
38
+ };
39
+
40
+ if ( switchState && switchState.active ) {
41
+ // Multi-specialization mode
42
+ result.mode = 'multi-specialization';
43
+ result.specializations = Object.create( null );
44
+
45
+ let totalNodes = 0;
46
+ for ( let i = 0; i < switchState.caseOrder.length; i += 1 ) {
47
+ const key = switchState.caseOrder[ i ];
48
+ const specs = switchState.caseSpecs[ key ];
49
+ result.specializations[ key ] = {
50
+ nodeCount: specs.length,
51
+ nodes: specs.map( ( s ) => ( { name: s.name, type: s.nodeType } ) )
52
+ };
53
+ totalNodes += specs.length;
54
+ }
55
+
56
+ result.nodeCount = totalNodes;
57
+ result.caseOrder = switchState.caseOrder.slice();
58
+ } else {
59
+ // Single-pipeline mode (backward compatible)
60
+ result.mode = 'single-pipeline';
61
+ result.nodeCount = flowDefinition.length;
62
+ result.nodes = flowDefinition.map( ( s ) => ( { name: s.name, type: s.nodeType } ) );
63
+ }
64
+
65
+ return result;
66
+ };
67
+
@@ -0,0 +1,81 @@
1
+ // flow/load-node-module.js
2
+
3
+ /**
4
+ * @fileoverview Dynamic loader for node modules at runtime.
5
+ *
6
+ * Provides lazy loading of node modules using dynamic imports. This keeps
7
+ * the DSL build phase lightweight by deferring module loading until .run()
8
+ * is called. Validates node names against the canonical list before loading.
9
+ */
10
+
11
+ import { toKebab } from '../core/utils/flow/index.js';
12
+ import { isValidNodeName } from '../nodes/node-names.js';
13
+
14
+ /**
15
+ * Cache for loaded node modules to avoid redundant dynamic imports.
16
+ * @type {Map<string, object>}
17
+ */
18
+ const moduleCache = new Map();
19
+
20
+ /**
21
+ * Dynamically loads a node module by its camelCase name.
22
+ *
23
+ * @param {string} nodeName - camelCase node name (e.g., 'esMean', 'pageHinkley')
24
+ * @returns {Promise<object>} The node module exports (init, update, publishTo, etc.)
25
+ * @throws {Error} If nodeName is not a valid node
26
+ *
27
+ * @example
28
+ * const esMean = await loadNodeModule('esMean');
29
+ * // esMean.init, esMean.update, esMean.publishTo, etc.
30
+ */
31
+ export const loadNodeModule = async function ( nodeName ) {
32
+ // Validate node name
33
+ if ( !isValidNodeName( nodeName ) ) {
34
+ throw new Error( `Unknown node type: "${nodeName}"` );
35
+ }
36
+
37
+ // Return cached module if available
38
+ if ( moduleCache.has( nodeName ) ) {
39
+ return moduleCache.get( nodeName );
40
+ }
41
+
42
+ const kebabName = toKebab( nodeName );
43
+ const nodeModule = await import( `../nodes/${kebabName}/index.js` );
44
+
45
+ // Cache for subsequent calls
46
+ moduleCache.set( nodeName, nodeModule );
47
+
48
+ return nodeModule;
49
+ };
50
+
51
+ /**
52
+ * Loads multiple node modules in parallel.
53
+ *
54
+ * @param {string[]} nodeNames - Array of camelCase node names
55
+ * @returns {Promise<Map<string, object>>} Map of nodeName -> module
56
+ * @throws {Error} If any nodeName is invalid
57
+ *
58
+ * @example
59
+ * const modules = await loadNodeModules(['esMean', 'threshold', 'emitIf']);
60
+ * modules.get('esMean').update(state, msg);
61
+ */
62
+ export const loadNodeModules = async function ( nodeNames ) {
63
+ const uniqueNames = [ ...new Set( nodeNames ) ];
64
+
65
+ const loadPromises = uniqueNames.map( async ( name ) => {
66
+ const mod = await loadNodeModule( name );
67
+ return [ name, mod ];
68
+ } );
69
+
70
+ const entries = await Promise.all( loadPromises );
71
+
72
+ return new Map( entries );
73
+ };
74
+
75
+ /**
76
+ * Clears the module cache. Useful for testing or hot-reloading scenarios.
77
+ */
78
+ export const clearModuleCache = function () {
79
+ moduleCache.clear();
80
+ };
81
+
@@ -0,0 +1,112 @@
1
+ import * as nodes from '../nodes/index.js';
2
+ import { validateWithSchema } from '../core/utils/validate/index.js';
3
+ import { schemas } from './signatures-schema.js';
4
+ import { expandSingleInputArraySugar } from './expand-single-input-array.js';
5
+ import { SIGNATURE_PATTERNS } from './consts.js';
6
+ import { getSignaturePattern } from './get-signature-pattern.js';
7
+ import { specBuilder } from './spec-builder.js';
8
+ import { getTargetArray } from './get-target-array.js';
9
+
10
+ /**
11
+ * Creates a chainable DSL method for a WinkComposer node.
12
+ *
13
+ * @param {Object} api - The fluent API object to extend
14
+ * @param {string} node - Node type name (e.g., 'esMean')
15
+ * @param {Object} meta - Node metadata from getDSLMetadata()
16
+ * @param {Object} flowState - Shared state including definitions and collision checkers
17
+ * @returns {Function} Chainable method that validates and adds node specs
18
+ *
19
+ * @example
20
+ * const method = makeNodeMethod(api, 'esMean', meta, flowState);
21
+ * method('smooth', 'temperature', { esMean: 'smoothTemp' }, { alpha: 0.3 });
22
+ */
23
+ export const makeNodeMethod = function ( api, node, meta, flowState ) {
24
+ const {
25
+ flowDefinition,
26
+ importSet,
27
+ isNodeNameDuplicate,
28
+ switchState,
29
+ groupByState,
30
+ markPipelineStarted
31
+ } = flowState;
32
+
33
+ return function ( ...args ) {
34
+ // Mark pipeline as started (blocks subsequent config method calls)
35
+ if ( markPipelineStarted ) {
36
+ markPipelineStarted();
37
+ }
38
+
39
+ importSet.add( node );
40
+
41
+ let built = null;
42
+ const schema = meta.specSchema;
43
+
44
+ // During groupBy, use the template-local node-name checker. Node names are
45
+ // prefixed per group at expansion (idle_corr, cruise_corr), so the global
46
+ // checker would false-positive on the shared template names.
47
+ const isGroupByActive = groupByState && groupByState.active;
48
+ const effectiveNodeChecker = isGroupByActive ?
49
+ groupByState.templateNodeChecker :
50
+ isNodeNameDuplicate;
51
+
52
+ const buildSpecFor = specBuilder( meta );
53
+
54
+ // Create effective flowState for sugar expansion
55
+ const effectiveFlowState = isGroupByActive ?
56
+ { ...flowState, isNodeNameDuplicate: effectiveNodeChecker } :
57
+ flowState;
58
+ // The eligible nodes must have `x` defined as `string` and there is no `y`
59
+ // property. Note on logic:
60
+ // Safe because of short-circuit:
61
+ // - If the left check fails, the right check is not evaluated.
62
+ // - If the left check passes, then `schema.from.properties` must exist.
63
+ const isEligible = schema.from?.properties?.x?.type === 'string' && !schema.from.properties.y;
64
+ const isSugar = isEligible && Array.isArray( args[ 1 ] );
65
+ const callContext = `.${node}( ${args[ 0 ]}${args.length > 1 ? `, +${args.length - 1}` : ''} )${isSugar ? ` → ${args[ 1 ].join( ', ' )}` : ''}`;
66
+
67
+ try {
68
+ // Array syntactic sugar for single-input nodes only: ( name, [x...], options )—this
69
+ // signature generates the `storeAs` names automatically using the fixed
70
+ // `${field}_${label}` rule. If explicit `storeAs` naming is required then
71
+ // use the alternate signature: ( name, x, output, options? ).
72
+ //
73
+ if ( isSugar ) {
74
+ // Use our standard validators to verify the input signature before building.
75
+ const status = validateWithSchema( schemas[ SIGNATURE_PATTERNS.nameSugarOutputsOptions ], [ ...args ], 'Arg' );
76
+ const pattern = getSignaturePattern( schema );
77
+ status.throwIfInvalid( 'argument' );
78
+ built = expandSingleInputArraySugar( meta, [ ...args ], effectiveFlowState, pattern );
79
+ } else {
80
+ // Canonical builder path
81
+ const instName = args[ 0 ];
82
+ if ( effectiveNodeChecker( instName ) ) throw Error( `WinkComposer/flow: duplicate node ${instName} found.` );
83
+ const pattern = getSignaturePattern( schema );
84
+ if ( pattern === SIGNATURE_PATTERNS.unknown ) throw Error( `WinkComposer/flow: unknown signature pattern found for ${instName}.` );
85
+ const status = validateWithSchema( schemas[ pattern ], [ ...args ], 'Arg' );
86
+ status.throwIfInvalid( 'argument' );
87
+ built = buildSpecFor[ pattern ]( [ ...args ] );
88
+ }
89
+
90
+ // Normalize to array: allows downstream code to handle both single specs
91
+ // and spec arrays uniformly without conditional logic.
92
+ const specs = Array.isArray( built ) ? built : [ built ];
93
+
94
+ // Determine target array based on switch/case/groupBy state
95
+ const targetArray = getTargetArray( switchState, groupByState, flowDefinition );
96
+
97
+ // Apply flow-level naming and use initialize for spec to check any error.
98
+ for ( let j = 0; j < specs.length; j += 1 ) {
99
+ nodes[ node ].init( specs[ j ] );
100
+ targetArray.push( specs[ j ] );
101
+ }
102
+ } catch ( error ) {
103
+ const enhancedError = new Error(
104
+ `WinkComposer/flow: Failed to process "${callContext}"\n` +
105
+ ` Reason: ${error.message}\n`
106
+ );
107
+
108
+ throw enhancedError;
109
+ }
110
+ return api;
111
+ };
112
+ }; // makeNodeMethod()
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @fileoverview Build-time resolver for `pickByField` markers inside a `forEach`.
3
+ *
4
+ * A `pickByField( map )` marker is a function carrying
5
+ * `.semantics = { type: 'pickByField', map }`; it stands for "the value for the
6
+ * current fan channel". When a `forEach` expands a chain for one field, this walker
7
+ * replaces every such marker in that node's options with `map[ field ]` — a plain
8
+ * value baked into the spec before the node is initialized. After it runs, nothing
9
+ * of `pickByField` survives into the wired pipeline, so it costs nothing per message.
10
+ *
11
+ * It mirrors `resolve-tunables.js` (the groupBy resolver) on every pass-through:
12
+ * other tunables and plain predicates are preserved for runtime, primitives pass
13
+ * straight through, and arrays and plain objects are rebuilt with their entries
14
+ * resolved. It differs only at the leaf — it resolves `pickByField` by the current
15
+ * field (throwing on a missing key), where the groupBy resolver resolves
16
+ * `lookupByField` by the group value (with a default). The two leaf rules diverge,
17
+ * which is why this is a separate module rather than a shared walker.
18
+ *
19
+ * @see resolve-tunables.js - the groupBy sibling resolver
20
+ * @see ADR-006 - Tunable Pattern for Dynamic Parameters
21
+ */
22
+
23
+ /**
24
+ * Recursively resolves `pickByField` markers in an options value for one fan field.
25
+ *
26
+ * @param {*} value - The value to process (an option, or a nested part of one)
27
+ * @param {string} field - The current fan channel's field name (each.field)
28
+ * @returns {*} The value with every pickByField marker replaced by map[ field ]
29
+ * @throws {Error} If a pickByField map has no entry for `field`
30
+ */
31
+ export const resolvePickByField = function ( value, field ) {
32
+ // A pickByField marker: resolve to map[ field ]. A missing key is a build error,
33
+ // not a silent undefined that would slip through to the first message.
34
+ if ( typeof value === 'function' && value.semantics && value.semantics.type === 'pickByField' ) {
35
+ const map = value.semantics.map;
36
+ if ( !Object.prototype.hasOwnProperty.call( map, field ) ) {
37
+ throw new Error(
38
+ `WinkComposer/flow: pickByField has no entry for field '${field}' ` +
39
+ `(map keys: ${Object.keys( map ).join( ', ' )}).`
40
+ );
41
+ }
42
+ // Return the resolved value as-is; do not recurse into it, so a resolved
43
+ // field-keyed object (e.g. a sanitize range) reaches the node's own resolver
44
+ // intact.
45
+ return map[ field ];
46
+ }
47
+
48
+ // Any other function - a plain predicate, or a different tunable like
49
+ // lookupByField - is preserved for runtime. pickByField is the only marker this
50
+ // resolver owns.
51
+ if ( typeof value === 'function' ) {
52
+ return value;
53
+ }
54
+
55
+ // Null/undefined/primitives pass through unchanged.
56
+ if ( value === null || value === undefined || typeof value !== 'object' ) {
57
+ return value;
58
+ }
59
+
60
+ // Arrays - resolve each element into a new array.
61
+ if ( Array.isArray( value ) ) {
62
+ const result = [];
63
+ for ( let i = 0; i < value.length; i += 1 ) {
64
+ result.push( resolvePickByField( value[ i ], field ) );
65
+ }
66
+ return result;
67
+ }
68
+
69
+ // Objects - resolve each property into a clean object.
70
+ const result = Object.create( null );
71
+ const keys = Object.keys( value );
72
+ for ( let i = 0; i < keys.length; i += 1 ) {
73
+ result[ keys[ i ] ] = resolvePickByField( value[ keys[ i ] ], field );
74
+ }
75
+
76
+ return result;
77
+ }; // resolvePickByField()