@winkjs/composer 0.0.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +106 -80
  3. package/package.json +56 -28
  4. package/src/composer.js +75 -0
  5. package/src/core/batch/aoo-encoder.js +148 -0
  6. package/src/core/batch/index.js +12 -0
  7. package/src/core/batch/sliding-window.js +91 -0
  8. package/src/core/codec/index.js +137 -0
  9. package/src/core/emitter-manager/mqtt/constants.js +102 -0
  10. package/src/core/emitter-manager/mqtt/emitter.js +780 -0
  11. package/src/core/emitter-manager/mqtt/index.js +190 -0
  12. package/src/core/emitter-manager/mqtt/mqtt-store.js +538 -0
  13. package/src/core/emitter-manager/terminal/assert-columns.js +98 -0
  14. package/src/core/emitter-manager/terminal/formatters.js +160 -0
  15. package/src/core/emitter-manager/terminal/index.js +341 -0
  16. package/src/core/env-vars.js +245 -0
  17. package/src/core/mqtt-protocol.js +47 -0
  18. package/src/core/partition-manager/index.js +7 -0
  19. package/src/core/partition-manager/init.js +44 -0
  20. package/src/core/partition-manager/introspect.js +15 -0
  21. package/src/core/partition-manager/publish-to.js +6 -0
  22. package/src/core/partition-manager/recompute.js +5 -0
  23. package/src/core/partition-manager/reset.js +5 -0
  24. package/src/core/partition-manager/update.js +264 -0
  25. package/src/core/partition-manager/validate-flow.js +157 -0
  26. package/src/core/semantics/digest.js +128 -0
  27. package/src/core/semantics/index.js +83 -0
  28. package/src/core/semantics/loader.js +571 -0
  29. package/src/core/semantics/schemas/asset-class-schema.js +218 -0
  30. package/src/core/semantics/schemas/column-schema.js +651 -0
  31. package/src/core/semantics/schemas/enum-schema.js +153 -0
  32. package/src/core/semantics/schemas/index.js +51 -0
  33. package/src/core/semantics/warnings.js +141 -0
  34. package/src/core/shutdown-manager/index.js +289 -0
  35. package/src/core/source-manager/csv/index.js +171 -0
  36. package/src/core/source-manager/csv/start.js +456 -0
  37. package/src/core/source-manager/mqtt/client.js +406 -0
  38. package/src/core/source-manager/mqtt/constants.js +147 -0
  39. package/src/core/source-manager/mqtt/dedup.js +244 -0
  40. package/src/core/source-manager/mqtt/index.js +260 -0
  41. package/src/core/source-manager/mqtt/status.js +546 -0
  42. package/src/core/source-manager/test-harness/comparator.js +226 -0
  43. package/src/core/source-manager/test-harness/field-generator.js +85 -0
  44. package/src/core/source-manager/test-harness/fuzz.js +87 -0
  45. package/src/core/source-manager/test-harness/index.js +115 -0
  46. package/src/core/source-manager/test-harness/prng.js +57 -0
  47. package/src/core/source-manager/test-harness/start.js +232 -0
  48. package/src/core/source-manager/test-harness/validate.js +213 -0
  49. package/src/core/storage-manager/questdb/assert-columns.js +120 -0
  50. package/src/core/storage-manager/questdb/ensure-tables.js +187 -0
  51. package/src/core/storage-manager/questdb/index.js +1251 -0
  52. package/src/core/storage-manager/questdb/persist-plan.js +488 -0
  53. package/src/core/storage-manager/questdb/writers.js +142 -0
  54. package/src/core/test-utils/sealed-record.js +80 -0
  55. package/src/core/test-utils/soak-signature.js +149 -0
  56. package/src/core/test-utils/tcp-proxy.js +106 -0
  57. package/src/core/tunable/helpers.js +254 -0
  58. package/src/core/tunable/index.js +87 -0
  59. package/src/core/utils/console-formatter/index.js +207 -0
  60. package/src/core/utils/create-smart-store-as/index.js +98 -0
  61. package/src/core/utils/create-smart-store-as/sanitize-field.js +46 -0
  62. package/src/core/utils/create-smart-store-as/smart-truncate.js +93 -0
  63. package/src/core/utils/flow/index.js +5 -0
  64. package/src/core/utils/flow/make-collision-checker.js +23 -0
  65. package/src/core/utils/flow/to-kebab.js +20 -0
  66. package/src/core/utils/half-life/index.js +111 -0
  67. package/src/core/utils/node/disable.js +11 -0
  68. package/src/core/utils/node/enable.js +11 -0
  69. package/src/core/utils/node/execute-triggers.js +40 -0
  70. package/src/core/utils/node/index.js +10 -0
  71. package/src/core/utils/node/pause.js +11 -0
  72. package/src/core/utils/node/populate-predicate-inputs.js +14 -0
  73. package/src/core/utils/node/publish-nan.js +19 -0
  74. package/src/core/utils/node/reset-predicate-inputs.js +11 -0
  75. package/src/core/utils/node/unpause.js +11 -0
  76. package/src/core/utils/node/validate-spec.js +22 -0
  77. package/src/core/utils/options/resolve-field-keyed.js +115 -0
  78. package/src/core/utils/quantize/index.js +75 -0
  79. package/src/core/utils/stats/formulas.js +67 -0
  80. package/src/core/utils/stats/index.js +3 -0
  81. package/src/core/utils/validate/composer-validators.js +141 -0
  82. package/src/core/utils/validate/helpers.js +206 -0
  83. package/src/core/utils/validate/index.js +12 -0
  84. package/src/core/utils/validate/type-validators.js +538 -0
  85. package/src/core/utils/validate/validate.js +107 -0
  86. package/src/core/utils/validate/validators.js +177 -0
  87. package/src/core/wiring/assert-handle.js +57 -0
  88. package/src/core/wiring/assert-module.js +63 -0
  89. package/src/core/wiring/index.js +5 -0
  90. package/src/core/wiring/node-type-to-module.js +17 -0
  91. package/src/core/wiring/wire-emitters.js +256 -0
  92. package/src/core/wiring/wire-linear-graph.js +25 -0
  93. package/src/core/wiring/wire-node.js +157 -0
  94. package/src/core/wiring/wire-semantics.js +173 -0
  95. package/src/core/wiring/wire-storages.js +344 -0
  96. package/src/flow/check-output-collisions.js +93 -0
  97. package/src/flow/consts.js +9 -0
  98. package/src/flow/deep-clone-spec.js +64 -0
  99. package/src/flow/driver.js +171 -0
  100. package/src/flow/expand-single-input-array.js +76 -0
  101. package/src/flow/expand-template-spec.js +93 -0
  102. package/src/flow/flow-api-schemas.js +300 -0
  103. package/src/flow/flow.js +658 -0
  104. package/src/flow/for-each.js +225 -0
  105. package/src/flow/get-signature-pattern.js +31 -0
  106. package/src/flow/get-target-array.js +46 -0
  107. package/src/flow/inspect.js +67 -0
  108. package/src/flow/load-node-module.js +81 -0
  109. package/src/flow/make-node-method.js +112 -0
  110. package/src/flow/resolve-pick-by-field.js +77 -0
  111. package/src/flow/resolve-tunables.js +76 -0
  112. package/src/flow/run.js +392 -0
  113. package/src/flow/serialize.js +161 -0
  114. package/src/flow/signatures-schema.js +222 -0
  115. package/src/flow/spec-builder.js +65 -0
  116. package/src/flow/validate-triggers.js +250 -0
  117. package/src/flow/validate.js +261 -0
  118. package/src/nodes/accumulate/index.js +21 -0
  119. package/src/nodes/accumulate/init.js +48 -0
  120. package/src/nodes/accumulate/introspect.js +123 -0
  121. package/src/nodes/accumulate/publish-to.js +34 -0
  122. package/src/nodes/accumulate/recompute.js +27 -0
  123. package/src/nodes/accumulate/reset.js +25 -0
  124. package/src/nodes/accumulate/update.js +42 -0
  125. package/src/nodes/appraise/calibrate.js +64 -0
  126. package/src/nodes/appraise/decision.js +66 -0
  127. package/src/nodes/appraise/deviation.js +140 -0
  128. package/src/nodes/appraise/index.js +23 -0
  129. package/src/nodes/appraise/init.js +165 -0
  130. package/src/nodes/appraise/integrate.js +45 -0
  131. package/src/nodes/appraise/introspect.js +352 -0
  132. package/src/nodes/appraise/publish-to.js +93 -0
  133. package/src/nodes/appraise/receptor.js +69 -0
  134. package/src/nodes/appraise/recompute.js +79 -0
  135. package/src/nodes/appraise/reset.js +53 -0
  136. package/src/nodes/appraise/update.js +130 -0
  137. package/src/nodes/archive/csv/index.js +6 -0
  138. package/src/nodes/archive/csv/init.js +36 -0
  139. package/src/nodes/archive/csv/load.js +70 -0
  140. package/src/nodes/archive/csv/reset.js +14 -0
  141. package/src/nodes/archive/csv/simple-parser.js +216 -0
  142. package/src/nodes/archive/csv/test.js +173 -0
  143. package/src/nodes/archive/csv/update.js +63 -0
  144. package/src/nodes/butterworth-filter/index.js +18 -0
  145. package/src/nodes/butterworth-filter/init.js +184 -0
  146. package/src/nodes/butterworth-filter/introspect.js +228 -0
  147. package/src/nodes/butterworth-filter/publish-to.js +21 -0
  148. package/src/nodes/butterworth-filter/recompute.js +13 -0
  149. package/src/nodes/butterworth-filter/reset.js +30 -0
  150. package/src/nodes/butterworth-filter/update.js +38 -0
  151. package/src/nodes/categorize/index.js +19 -0
  152. package/src/nodes/categorize/init.js +58 -0
  153. package/src/nodes/categorize/introspect.js +177 -0
  154. package/src/nodes/categorize/publish-to.js +29 -0
  155. package/src/nodes/categorize/recompute.js +14 -0
  156. package/src/nodes/categorize/reset.js +16 -0
  157. package/src/nodes/categorize/update.js +53 -0
  158. package/src/nodes/controller/index.js +9 -0
  159. package/src/nodes/controller/init.js +55 -0
  160. package/src/nodes/controller/introspect.js +91 -0
  161. package/src/nodes/controller/publish-to.js +8 -0
  162. package/src/nodes/controller/recompute.js +9 -0
  163. package/src/nodes/controller/reset.js +17 -0
  164. package/src/nodes/controller/update.js +63 -0
  165. package/src/nodes/diff/index.js +19 -0
  166. package/src/nodes/diff/init.js +37 -0
  167. package/src/nodes/diff/introspect.js +132 -0
  168. package/src/nodes/diff/publish-to.js +21 -0
  169. package/src/nodes/diff/recompute.js +12 -0
  170. package/src/nodes/diff/reset.js +12 -0
  171. package/src/nodes/diff/update.js +37 -0
  172. package/src/nodes/digest-moments/formulas.js +12 -0
  173. package/src/nodes/digest-moments/index.js +14 -0
  174. package/src/nodes/digest-moments/init.js +67 -0
  175. package/src/nodes/digest-moments/introspect.js +154 -0
  176. package/src/nodes/digest-moments/publish-to.js +34 -0
  177. package/src/nodes/digest-moments/recompute.js +14 -0
  178. package/src/nodes/digest-moments/reset.js +14 -0
  179. package/src/nodes/digest-moments/update.js +120 -0
  180. package/src/nodes/dwell-time-tracker/index.js +14 -0
  181. package/src/nodes/dwell-time-tracker/init.js +59 -0
  182. package/src/nodes/dwell-time-tracker/introspect.js +137 -0
  183. package/src/nodes/dwell-time-tracker/publish-to.js +22 -0
  184. package/src/nodes/dwell-time-tracker/recompute.js +8 -0
  185. package/src/nodes/dwell-time-tracker/reset.js +23 -0
  186. package/src/nodes/dwell-time-tracker/update.js +103 -0
  187. package/src/nodes/emit-if/helpers.js +94 -0
  188. package/src/nodes/emit-if/index.js +16 -0
  189. package/src/nodes/emit-if/init.js +67 -0
  190. package/src/nodes/emit-if/introspect.js +102 -0
  191. package/src/nodes/emit-if/publish-to.js +13 -0
  192. package/src/nodes/emit-if/recompute.js +12 -0
  193. package/src/nodes/emit-if/reset.js +27 -0
  194. package/src/nodes/emit-if/update.js +93 -0
  195. package/src/nodes/es-correlation/index.js +19 -0
  196. package/src/nodes/es-correlation/init.js +73 -0
  197. package/src/nodes/es-correlation/introspect.js +176 -0
  198. package/src/nodes/es-correlation/publish-to.js +32 -0
  199. package/src/nodes/es-correlation/recompute.js +56 -0
  200. package/src/nodes/es-correlation/reset.js +27 -0
  201. package/src/nodes/es-correlation/update.js +110 -0
  202. package/src/nodes/es-mean/index.js +20 -0
  203. package/src/nodes/es-mean/init.js +58 -0
  204. package/src/nodes/es-mean/introspect.js +129 -0
  205. package/src/nodes/es-mean/publish-to.js +19 -0
  206. package/src/nodes/es-mean/recompute.js +16 -0
  207. package/src/nodes/es-mean/reset.js +24 -0
  208. package/src/nodes/es-mean/update.js +69 -0
  209. package/src/nodes/es-pairwise-correlation/index.js +19 -0
  210. package/src/nodes/es-pairwise-correlation/init.js +98 -0
  211. package/src/nodes/es-pairwise-correlation/introspect.js +187 -0
  212. package/src/nodes/es-pairwise-correlation/publish-to.js +31 -0
  213. package/src/nodes/es-pairwise-correlation/recompute.js +69 -0
  214. package/src/nodes/es-pairwise-correlation/reset.js +29 -0
  215. package/src/nodes/es-pairwise-correlation/update.js +89 -0
  216. package/src/nodes/es-stats/index.js +21 -0
  217. package/src/nodes/es-stats/init.js +105 -0
  218. package/src/nodes/es-stats/introspect.js +157 -0
  219. package/src/nodes/es-stats/publish-to.js +42 -0
  220. package/src/nodes/es-stats/recompute.js +74 -0
  221. package/src/nodes/es-stats/reset.js +49 -0
  222. package/src/nodes/es-stats/update-envelope.js +44 -0
  223. package/src/nodes/es-stats/update-welford.js +64 -0
  224. package/src/nodes/es-stats/update.js +59 -0
  225. package/src/nodes/index.js +83 -0
  226. package/src/nodes/invert-flag/index.js +20 -0
  227. package/src/nodes/invert-flag/init.js +36 -0
  228. package/src/nodes/invert-flag/introspect.js +115 -0
  229. package/src/nodes/invert-flag/publish-to.js +23 -0
  230. package/src/nodes/invert-flag/recompute.js +12 -0
  231. package/src/nodes/invert-flag/reset.js +12 -0
  232. package/src/nodes/invert-flag/update.js +32 -0
  233. package/src/nodes/kalman1d/index.js +20 -0
  234. package/src/nodes/kalman1d/init.js +103 -0
  235. package/src/nodes/kalman1d/introspect.js +250 -0
  236. package/src/nodes/kalman1d/publish-to.js +34 -0
  237. package/src/nodes/kalman1d/recompute.js +23 -0
  238. package/src/nodes/kalman1d/reset.js +29 -0
  239. package/src/nodes/kalman1d/update.js +144 -0
  240. package/src/nodes/kernel/index.js +18 -0
  241. package/src/nodes/kernel/init.js +66 -0
  242. package/src/nodes/kernel/introspect.js +163 -0
  243. package/src/nodes/kernel/presets.js +74 -0
  244. package/src/nodes/kernel/publish-to.js +25 -0
  245. package/src/nodes/kernel/recompute.js +14 -0
  246. package/src/nodes/kernel/reset.js +20 -0
  247. package/src/nodes/kernel/update.js +69 -0
  248. package/src/nodes/lag/index.js +21 -0
  249. package/src/nodes/lag/init.js +87 -0
  250. package/src/nodes/lag/introspect.js +202 -0
  251. package/src/nodes/lag/publish-to.js +64 -0
  252. package/src/nodes/lag/recompute.js +33 -0
  253. package/src/nodes/lag/reset.js +49 -0
  254. package/src/nodes/lag/update.js +133 -0
  255. package/src/nodes/median3/index.js +19 -0
  256. package/src/nodes/median3/init.js +40 -0
  257. package/src/nodes/median3/introspect.js +116 -0
  258. package/src/nodes/median3/publish-to.js +21 -0
  259. package/src/nodes/median3/recompute.js +12 -0
  260. package/src/nodes/median3/reset.js +15 -0
  261. package/src/nodes/median3/update.js +58 -0
  262. package/src/nodes/moments-digest/copy-from.js +12 -0
  263. package/src/nodes/moments-digest/flush.js +17 -0
  264. package/src/nodes/moments-digest/index.js +132 -0
  265. package/src/nodes/moments-digest/init.js +84 -0
  266. package/src/nodes/moments-digest/introspect.js +122 -0
  267. package/src/nodes/moments-digest/prelude.js +28 -0
  268. package/src/nodes/moments-digest/publish-to.js +31 -0
  269. package/src/nodes/moments-digest/recompute.js +9 -0
  270. package/src/nodes/moments-digest/reset.js +20 -0
  271. package/src/nodes/moments-digest/update.js +182 -0
  272. package/src/nodes/node-names.js +67 -0
  273. package/src/nodes/page-hinkley/index.js +20 -0
  274. package/src/nodes/page-hinkley/init.js +66 -0
  275. package/src/nodes/page-hinkley/introspect.js +193 -0
  276. package/src/nodes/page-hinkley/publish-to.js +28 -0
  277. package/src/nodes/page-hinkley/recompute.js +14 -0
  278. package/src/nodes/page-hinkley/reset.js +23 -0
  279. package/src/nodes/page-hinkley/update.js +105 -0
  280. package/src/nodes/pass-if/index.js +21 -0
  281. package/src/nodes/pass-if/init.js +37 -0
  282. package/src/nodes/pass-if/introspect.js +93 -0
  283. package/src/nodes/pass-if/publish-to.js +15 -0
  284. package/src/nodes/pass-if/recompute.js +15 -0
  285. package/src/nodes/pass-if/reset.js +18 -0
  286. package/src/nodes/pass-if/update.js +37 -0
  287. package/src/nodes/persist-if/helpers.js +111 -0
  288. package/src/nodes/persist-if/index.js +9 -0
  289. package/src/nodes/persist-if/init.js +98 -0
  290. package/src/nodes/persist-if/introspect.js +106 -0
  291. package/src/nodes/persist-if/publish-to.js +21 -0
  292. package/src/nodes/persist-if/recompute.js +20 -0
  293. package/src/nodes/persist-if/reset.js +35 -0
  294. package/src/nodes/persist-if/update.js +112 -0
  295. package/src/nodes/persistence-check/index.js +14 -0
  296. package/src/nodes/persistence-check/init.js +38 -0
  297. package/src/nodes/persistence-check/introspect.js +144 -0
  298. package/src/nodes/persistence-check/publish-to.js +11 -0
  299. package/src/nodes/persistence-check/recompute.js +7 -0
  300. package/src/nodes/persistence-check/reset.js +12 -0
  301. package/src/nodes/persistence-check/update.js +57 -0
  302. package/src/nodes/process-index/index.js +15 -0
  303. package/src/nodes/process-index/init.js +48 -0
  304. package/src/nodes/process-index/introspect.js +182 -0
  305. package/src/nodes/process-index/publish-to.js +29 -0
  306. package/src/nodes/process-index/recompute.js +8 -0
  307. package/src/nodes/process-index/reset.js +8 -0
  308. package/src/nodes/process-index/update.js +75 -0
  309. package/src/nodes/ratio/index.js +14 -0
  310. package/src/nodes/ratio/init.js +37 -0
  311. package/src/nodes/ratio/introspect.js +155 -0
  312. package/src/nodes/ratio/publish-to.js +15 -0
  313. package/src/nodes/ratio/recompute.js +7 -0
  314. package/src/nodes/ratio/reset.js +7 -0
  315. package/src/nodes/ratio/update.js +47 -0
  316. package/src/nodes/sanitize/index.js +82 -0
  317. package/src/nodes/sanitize/init.js +82 -0
  318. package/src/nodes/sanitize/introspect.js +194 -0
  319. package/src/nodes/sanitize/publish-to.js +15 -0
  320. package/src/nodes/sanitize/recompute.js +8 -0
  321. package/src/nodes/sanitize/reset.js +14 -0
  322. package/src/nodes/sanitize/update.js +102 -0
  323. package/src/nodes/spike-guard/index.js +14 -0
  324. package/src/nodes/spike-guard/init.js +47 -0
  325. package/src/nodes/spike-guard/introspect.js +130 -0
  326. package/src/nodes/spike-guard/publish-to.js +35 -0
  327. package/src/nodes/spike-guard/recompute.js +14 -0
  328. package/src/nodes/spike-guard/reset.js +19 -0
  329. package/src/nodes/spike-guard/update.js +89 -0
  330. package/src/nodes/sse-emitter/index.js +7 -0
  331. package/src/nodes/sse-emitter/init.js +105 -0
  332. package/src/nodes/sse-emitter/introspect.js +134 -0
  333. package/src/nodes/sse-emitter/publish-to.js +6 -0
  334. package/src/nodes/sse-emitter/recompute.js +16 -0
  335. package/src/nodes/sse-emitter/reset.js +19 -0
  336. package/src/nodes/sse-emitter/update.js +69 -0
  337. package/src/nodes/sse-emitter/utils/broadcast-heartbeat.js +36 -0
  338. package/src/nodes/sse-emitter/utils/build-message.js +31 -0
  339. package/src/nodes/sse-emitter/utils/connection-manager.js +45 -0
  340. package/src/nodes/sse-emitter/utils/endpoint-initializer.js +20 -0
  341. package/src/nodes/sse-emitter/utils/health-monitor.js +38 -0
  342. package/src/nodes/sse-emitter/utils/qos-handler.js +41 -0
  343. package/src/nodes/state-change-detector/helpers.js +35 -0
  344. package/src/nodes/state-change-detector/index.js +12 -0
  345. package/src/nodes/state-change-detector/init.js +56 -0
  346. package/src/nodes/state-change-detector/introspect.js +146 -0
  347. package/src/nodes/state-change-detector/publish-to.js +20 -0
  348. package/src/nodes/state-change-detector/recompute.js +6 -0
  349. package/src/nodes/state-change-detector/reset.js +20 -0
  350. package/src/nodes/state-change-detector/update.js +97 -0
  351. package/src/nodes/sw-stats/index.js +14 -0
  352. package/src/nodes/sw-stats/init.js +51 -0
  353. package/src/nodes/sw-stats/introspect.js +131 -0
  354. package/src/nodes/sw-stats/publish-to.js +45 -0
  355. package/src/nodes/sw-stats/recompute.js +33 -0
  356. package/src/nodes/sw-stats/reset.js +15 -0
  357. package/src/nodes/sw-stats/update.js +39 -0
  358. package/src/nodes/swing-watch/index.js +17 -0
  359. package/src/nodes/swing-watch/init.js +144 -0
  360. package/src/nodes/swing-watch/introspect.js +188 -0
  361. package/src/nodes/swing-watch/publish-to.js +53 -0
  362. package/src/nodes/swing-watch/recompute.js +22 -0
  363. package/src/nodes/swing-watch/reset.js +55 -0
  364. package/src/nodes/swing-watch/update.js +767 -0
  365. package/src/nodes/tally/index.js +15 -0
  366. package/src/nodes/tally/init.js +32 -0
  367. package/src/nodes/tally/introspect.js +116 -0
  368. package/src/nodes/tally/publish-to.js +28 -0
  369. package/src/nodes/tally/recompute.js +10 -0
  370. package/src/nodes/tally/reset.js +10 -0
  371. package/src/nodes/tally/update.js +44 -0
  372. package/src/nodes/threshold/index.js +14 -0
  373. package/src/nodes/threshold/init.js +121 -0
  374. package/src/nodes/threshold/introspect.js +192 -0
  375. package/src/nodes/threshold/publish-to.js +11 -0
  376. package/src/nodes/threshold/recompute.js +8 -0
  377. package/src/nodes/threshold/reset.js +14 -0
  378. package/src/nodes/threshold/update.js +54 -0
  379. package/src/nodes/transform/helpers.js +39 -0
  380. package/src/nodes/transform/index.js +15 -0
  381. package/src/nodes/transform/init.js +35 -0
  382. package/src/nodes/transform/introspect.js +118 -0
  383. package/src/nodes/transform/publish-to.js +23 -0
  384. package/src/nodes/transform/recompute.js +12 -0
  385. package/src/nodes/transform/reset.js +11 -0
  386. package/src/nodes/transform/update.js +32 -0
  387. package/src/nodes/trend/compute-confidence.js +112 -0
  388. package/src/nodes/trend/index.js +14 -0
  389. package/src/nodes/trend/init.js +81 -0
  390. package/src/nodes/trend/introspect.js +151 -0
  391. package/src/nodes/trend/publish-to.js +22 -0
  392. package/src/nodes/trend/recompute.js +15 -0
  393. package/src/nodes/trend/reset.js +26 -0
  394. package/src/nodes/trend/update.js +93 -0
  395. package/src/nodes/tw-stats/copy-from.js +16 -0
  396. package/src/nodes/tw-stats/flush.js +12 -0
  397. package/src/nodes/tw-stats/index.js +41 -0
  398. package/src/nodes/tw-stats/init.js +92 -0
  399. package/src/nodes/tw-stats/introspect.js +171 -0
  400. package/src/nodes/tw-stats/publish-to.js +108 -0
  401. package/src/nodes/tw-stats/recompute.js +9 -0
  402. package/src/nodes/tw-stats/reset.js +16 -0
  403. package/src/nodes/tw-stats/update.js +113 -0
  404. package/src/nodes/unbalance/index.js +15 -0
  405. package/src/nodes/unbalance/init.js +59 -0
  406. package/src/nodes/unbalance/introspect.js +167 -0
  407. package/src/nodes/unbalance/publish-to.js +52 -0
  408. package/src/nodes/unbalance/recompute.js +11 -0
  409. package/src/nodes/unbalance/reset.js +10 -0
  410. package/src/nodes/unbalance/update.js +83 -0
  411. package/src/nodes/vector-distance/index.js +14 -0
  412. package/src/nodes/vector-distance/init.js +54 -0
  413. package/src/nodes/vector-distance/introspect.js +149 -0
  414. package/src/nodes/vector-distance/publish-to.js +35 -0
  415. package/src/nodes/vector-distance/recompute.js +11 -0
  416. package/src/nodes/vector-distance/reset.js +20 -0
  417. package/src/nodes/vector-distance/update.js +139 -0
  418. package/src/nodes/winnow/index.js +12 -0
  419. package/src/nodes/winnow/init.js +82 -0
  420. package/src/nodes/winnow/introspect.js +253 -0
  421. package/src/nodes/winnow/publish-to.js +51 -0
  422. package/src/nodes/winnow/recompute.js +14 -0
  423. package/src/nodes/winnow/reset.js +34 -0
  424. package/src/nodes/winnow/update.js +203 -0
  425. package/src/tools/training/features.js +227 -0
  426. package/src/tools/training/index.js +21 -0
  427. package/src/tools/training/logistic.js +238 -0
  428. package/src/tools/training/metrics.js +126 -0
  429. package/src/tools/training/scale.js +239 -0
  430. package/src/tools/training/validate.js +103 -0
  431. package/src/windowing/count-sliding/create.js +16 -0
  432. package/src/windowing/count-sliding/get-reverse-chronological-index.js +19 -0
  433. package/src/windowing/count-sliding/index.js +6 -0
  434. package/src/windowing/count-sliding/is-not-full.js +5 -0
  435. package/src/windowing/count-sliding/push.js +15 -0
  436. package/src/windowing/count-sliding/reset.js +11 -0
  437. package/src/windowing/count-sliding/size.js +5 -0
  438. package/src/windowing/time-sliding/create.js +25 -0
  439. package/src/windowing/time-sliding/push.js +42 -0
  440. package/CODE_OF_CONDUCT.md +0 -46
  441. package/CONTRIBUTING.md +0 -132
  442. package/docs/.nojekyll +0 -0
  443. package/docs/CONTRIBUTING.md +0 -134
  444. package/docs/README.md +0 -132
  445. package/docs/_coverpage.md +0 -26
  446. package/docs/_media/bg.png +0 -0
  447. package/docs/_media/wink-logo.png +0 -0
  448. package/docs/_navbar.md +0 -14
  449. package/docs/_sidebar.md +0 -2
  450. package/docs/index.html +0 -44
  451. package/eslint.config.js +0 -283
@@ -0,0 +1,245 @@
1
+ // core/env-vars.js
2
+
3
+ /* eslint-disable no-process-env */
4
+
5
+ import os from 'os';
6
+
7
+ // ============================================================================
8
+ // ENVIRONMENT VARIABLE DEFINITIONS
9
+ // ============================================================================
10
+
11
+ // Yield threshold parses with Number(), not parseInt(): 'Infinity' must map
12
+ // to Infinity — the documented never-yield sentinel (`.yield( { threshold:
13
+ // Infinity } )` uses the same value). An empty string is forced to NaN so
14
+ // validation fails fast; Number( '' ) would silently give 0, which means
15
+ // "breathe after every message" — not what an operator who set the variable
16
+ // to nothing intended.
17
+ const rawYieldMs = ( process.env.YIELD_TIME_THRESHOLD_MS ?? '500' ).trim();
18
+
19
+ const ENV_VARS = {
20
+ // Core Configuration
21
+ edgeDeviceId: ( process.env.EDGE_DEVICE_ID ?? os.hostname() ).trim(),
22
+ nodeEnv: ( process.env.NODE_ENV ?? 'development' ).trim(),
23
+
24
+ // Partition Management (see ADR-016)
25
+ maxPartitionsAllowed: parseInt( process.env.COMPOSER_MAX_PARTITIONS_ALLOWED ?? '10000', 10 ),
26
+
27
+ // STORAGE_DIR retired 2026-07-09 — the emitter's LevelDB store
28
+ // (removed by ADR-021) was the only thing that ever wrote there.
29
+ // A future WAL adapter defines its own disk-path configuration.
30
+
31
+ // Flow lifecycle — top-level forced-shutdown timeout (process layer)
32
+ // Bounds the entire `handle.shutdown()` drain. If `shutdown-manager`
33
+ // doesn't see graceful completion within this many milliseconds, it
34
+ // logs and force-exits with code 1 (vs 0 on graceful). Matches
35
+ // Kubernetes' default `terminationGracePeriodSeconds` of 30. See
36
+ // ADR-018 (flow lifecycle and signal handling).
37
+ shutdownForceTimeoutMs: parseInt( process.env.SHUTDOWN_FORCE_TIMEOUT_MS ?? '30000', 10 ),
38
+
39
+ // Flow yield — default time threshold between deliberate event-loop
40
+ // breaths (ADR-024). Matters only to callers that wait on
41
+ // `processMessage` (file replays, testHarness, the headless driver):
42
+ // for them the yield tick is the only planned chance for sink flush
43
+ // timers and socket I/O to run. 500 ms is half the tightest background
44
+ // timer it must not starve (QUESTDB_IDLE_FLUSH_CHECK_MS, 1000).
45
+ // Per-flow override: `.yield( { threshold } )`. Infinity = never yield.
46
+ yieldTimeThresholdMs: rawYieldMs === '' ? NaN : Number( rawYieldMs ),
47
+
48
+ // MQTT Configuration
49
+ mqttBrokerUrl: ( process.env.MQTT_BROKER_URL ?? 'mqtt://127.0.0.1:1883' ).trim(),
50
+ mqttMsgExpiry: parseInt( process.env.MQTT_MSG_EXPIRY ?? '3600', 10 ),
51
+ mqttKeepalive: parseInt( process.env.MQTT_KEEPALIVE ?? '60', 10 ),
52
+ mqttReconnectMs: parseInt( process.env.MQTT_RECONNECT_MS ?? '5000', 10 ),
53
+ mqttConnectTimeoutMs: parseInt( process.env.MQTT_CONNECT_TIMEOUT_MS ?? '30000', 10 ),
54
+ // How long the MQTT emitter factory waits at startup for the
55
+ // broker's first connection acknowledgment before handing the
56
+ // flow its handle anyway (recovering posture — the wait expiring
57
+ // is not an error). 0 disables the wait. Per-emitter override:
58
+ // the `connectGraceMs` config key.
59
+ mqttConnectGraceMs: parseInt( process.env.MQTT_CONNECT_GRACE_MS ?? '500', 10 ),
60
+ mqttSessionExpiryS: parseInt( process.env.MQTT_SESSION_EXPIRY_S ?? '604800', 10 ),
61
+ mqttMaxQueueSize: parseInt( process.env.MQTT_MAX_QUEUE_SIZE ?? '10000', 10 ),
62
+ // MQTT_MAX_QUEUE_BYTES retired with the emitter's disk store
63
+ // (ADR-021) — the in-memory emitter bounds memory by count alone.
64
+ // MQTT_DEDUP_WINDOW (count-only dedup) retired 2026-07-09 by
65
+ // ADR-022 — the source dedup cache is now time-bounded AND
66
+ // count-capped, with its own two variables below.
67
+ mqttSourceDedupWindowMs: parseInt( process.env.MQTT_SOURCE_DEDUP_WINDOW_MS ?? '120000', 10 ),
68
+ mqttSourceDedupMaxEntries: parseInt( process.env.MQTT_SOURCE_DEDUP_MAX_ENTRIES ?? '65536', 10 ),
69
+
70
+ // QuestDB Configuration
71
+ questdbIlpUrl: ( process.env.QUESTDB_ILP_URL ?? 'localhost:9000' ).trim(),
72
+ questdbPgUrl: ( process.env.QUESTDB_PG_URL ?? 'localhost:8812' ).trim(),
73
+ questdbFlushMode: ( process.env.QUESTDB_FLUSH_MODE ?? 'auto' ).trim(),
74
+ questdbIdleFlushAfterMs: parseInt( process.env.QUESTDB_IDLE_FLUSH_AFTER_MS ?? '5000', 10 ),
75
+ questdbIdleFlushCheckMs: parseInt( process.env.QUESTDB_IDLE_FLUSH_CHECK_MS ?? '1000', 10 ),
76
+ // Optional QuestDB settings (undefined if not set)
77
+ questdbAutoFlushRows: process.env.QUESTDB_AUTO_FLUSH_ROWS ?
78
+ parseInt( process.env.QUESTDB_AUTO_FLUSH_ROWS, 10 ) : undefined,
79
+ questdbAutoFlushIntervalMs: process.env.QUESTDB_AUTO_FLUSH_INTERVAL_MS ?
80
+ parseInt( process.env.QUESTDB_AUTO_FLUSH_INTERVAL_MS, 10 ) : undefined,
81
+ questdbMaxBufSize: process.env.QUESTDB_MAX_BUF_SIZE ?
82
+ parseInt( process.env.QUESTDB_MAX_BUF_SIZE, 10 ) : undefined,
83
+ questdbRetryTimeout: process.env.QUESTDB_RETRY_TIMEOUT ?
84
+ parseInt( process.env.QUESTDB_RETRY_TIMEOUT, 10 ) : undefined,
85
+
86
+ // QuestDB Credentials
87
+ questdbDatabase: ( process.env.QUESTDB_DATABASE ?? 'qdb' ).trim(),
88
+ questdbUser: ( process.env.QUESTDB_USER ?? 'admin' ).trim(),
89
+ questdbPassword: process.env.QUESTDB_PASSWORD ?? 'quest'
90
+ };
91
+
92
+ // ============================================================================
93
+ // VALIDATORS
94
+ // ============================================================================
95
+ const validators = {
96
+ edgeDeviceId: function ( value ) {
97
+ if ( !value ) return 'Cannot be empty';
98
+ if ( !( /^[\w\-\/.]+$/ ).test( value ) ) {
99
+ return `Invalid characters for MQTT topic: "${value}"`;
100
+ }
101
+ return null;
102
+ },
103
+
104
+ nodeEnv: function ( value ) {
105
+ const validEnvs = [ 'development', 'production', 'test' ];
106
+ if ( !validEnvs.includes( value ) ) {
107
+ return `Must be one of ${validEnvs.join( ', ' )}, got: "${value}"`;
108
+ }
109
+ return null;
110
+ },
111
+
112
+ mqttMsgExpiry: function ( value, originalEnv ) {
113
+ if ( isNaN( value ) || value <= 0 ) {
114
+ return `Must be positive integer, got: "${originalEnv}"`;
115
+ }
116
+ return null;
117
+ },
118
+
119
+ positiveInt: function ( value, originalEnv ) {
120
+ if ( isNaN( value ) || value <= 0 ) {
121
+ return `Must be positive integer, got: "${originalEnv}"`;
122
+ }
123
+ return null;
124
+ },
125
+
126
+ hostPort: function ( value ) {
127
+ if ( !value ) return 'Cannot be empty';
128
+ // Match host:port pattern (host can be hostname or IP)
129
+ const hostPortRegex = /^[\w.\-]+:\d+$/;
130
+ if ( !hostPortRegex.test( value ) ) {
131
+ return `Must be host:port format, got: "${value}"`;
132
+ }
133
+ return null;
134
+ },
135
+
136
+ questdbFlushMode: function ( value ) {
137
+ const validModes = [ 'auto', 'manual' ];
138
+ if ( !validModes.includes( value ) ) {
139
+ return `Must be one of ${validModes.join( ', ' )}, got: "${value}"`;
140
+ }
141
+ return null;
142
+ },
143
+
144
+ positiveIntOrUndefined: function ( value, originalEnv ) {
145
+ if ( value === undefined ) return null;
146
+ if ( isNaN( value ) || value <= 0 ) {
147
+ return `Must be positive integer, got: "${originalEnv}"`;
148
+ }
149
+ return null;
150
+ },
151
+
152
+ nonNegativeInt: function ( value, originalEnv ) {
153
+ if ( isNaN( value ) || value < 0 ) {
154
+ return `Must be non-negative integer, got: "${originalEnv}"`;
155
+ }
156
+ return null;
157
+ },
158
+
159
+ nonNegativeNumberOrInfinity: function ( value, originalEnv ) {
160
+ if ( isNaN( value ) || value < 0 ) {
161
+ return `Must be a non-negative number or Infinity, got: "${originalEnv}"`;
162
+ }
163
+ return null;
164
+ },
165
+
166
+ nonEmptyString: function ( value ) {
167
+ if ( !value || value.trim() === '' ) {
168
+ return `Cannot be empty, got: "${value}"`;
169
+ }
170
+ return null;
171
+ },
172
+
173
+ mqttUrl: function ( value ) {
174
+ if ( !value ) return 'Cannot be empty';
175
+ if ( !value.startsWith( 'mqtt://' ) && !value.startsWith( 'mqtts://' ) ) {
176
+ return `Must start with 'mqtt://' or 'mqtts://', got: "${value}"`;
177
+ }
178
+ return null;
179
+ }
180
+ };
181
+
182
+ // ============================================================================
183
+ // VALIDATION CONFIGURATION
184
+ // ============================================================================
185
+ const validationConfig = [
186
+ { field: 'edgeDeviceId', validator: validators.edgeDeviceId },
187
+ { field: 'nodeEnv', validator: validators.nodeEnv },
188
+ { field: 'maxPartitionsAllowed', validator: validators.positiveInt, originalEnv: 'COMPOSER_MAX_PARTITIONS_ALLOWED' },
189
+ { field: 'yieldTimeThresholdMs', validator: validators.nonNegativeNumberOrInfinity, originalEnv: 'YIELD_TIME_THRESHOLD_MS' },
190
+ { field: 'mqttBrokerUrl', validator: validators.mqttUrl, label: 'MQTT_BROKER_URL' },
191
+ { field: 'mqttMsgExpiry', validator: validators.mqttMsgExpiry, originalEnv: 'MQTT_MSG_EXPIRY' },
192
+ { field: 'mqttKeepalive', validator: validators.positiveInt, originalEnv: 'MQTT_KEEPALIVE' },
193
+ { field: 'mqttReconnectMs', validator: validators.positiveInt, originalEnv: 'MQTT_RECONNECT_MS' },
194
+ { field: 'mqttConnectTimeoutMs', validator: validators.positiveInt, originalEnv: 'MQTT_CONNECT_TIMEOUT_MS' },
195
+ { field: 'mqttConnectGraceMs', validator: validators.nonNegativeInt, originalEnv: 'MQTT_CONNECT_GRACE_MS' },
196
+ { field: 'mqttSessionExpiryS', validator: validators.positiveInt, originalEnv: 'MQTT_SESSION_EXPIRY_S' },
197
+ { field: 'mqttMaxQueueSize', validator: validators.positiveInt, originalEnv: 'MQTT_MAX_QUEUE_SIZE' },
198
+ { field: 'mqttSourceDedupWindowMs', validator: validators.positiveInt, originalEnv: 'MQTT_SOURCE_DEDUP_WINDOW_MS' },
199
+ { field: 'mqttSourceDedupMaxEntries', validator: validators.positiveInt, originalEnv: 'MQTT_SOURCE_DEDUP_MAX_ENTRIES' },
200
+ // QuestDB Configuration
201
+ { field: 'questdbIlpUrl', validator: validators.hostPort, label: 'QUESTDB_ILP_URL' },
202
+ { field: 'questdbPgUrl', validator: validators.hostPort, label: 'QUESTDB_PG_URL' },
203
+ { field: 'questdbFlushMode', validator: validators.questdbFlushMode, label: 'QUESTDB_FLUSH_MODE' },
204
+ { field: 'questdbIdleFlushAfterMs', validator: validators.nonNegativeInt, originalEnv: 'QUESTDB_IDLE_FLUSH_AFTER_MS' },
205
+ { field: 'questdbIdleFlushCheckMs', validator: validators.positiveInt, originalEnv: 'QUESTDB_IDLE_FLUSH_CHECK_MS' },
206
+ { field: 'questdbAutoFlushRows', validator: validators.positiveIntOrUndefined, originalEnv: 'QUESTDB_AUTO_FLUSH_ROWS' },
207
+ { field: 'questdbAutoFlushIntervalMs', validator: validators.positiveIntOrUndefined, originalEnv: 'QUESTDB_AUTO_FLUSH_INTERVAL_MS' },
208
+ { field: 'questdbMaxBufSize', validator: validators.positiveIntOrUndefined, originalEnv: 'QUESTDB_MAX_BUF_SIZE' },
209
+ { field: 'questdbRetryTimeout', validator: validators.positiveIntOrUndefined, originalEnv: 'QUESTDB_RETRY_TIMEOUT' },
210
+ // QuestDB Credentials
211
+ { field: 'questdbDatabase', validator: validators.nonEmptyString, label: 'QUESTDB_DATABASE' },
212
+ { field: 'questdbUser', validator: validators.nonEmptyString, label: 'QUESTDB_USER' }
213
+ // questdbPassword: no validation — allows empty for passwordless auth
214
+ ];
215
+
216
+ // ============================================================================
217
+ // VALIDATION RUNNER
218
+ // ============================================================================
219
+ const validate = function () {
220
+ const errors = [];
221
+
222
+ // Run each validator
223
+ for ( const config of validationConfig ) {
224
+ const value = ENV_VARS[ config.field ];
225
+ const originalEnv = config.originalEnv ? process.env[ config.originalEnv ] : value;
226
+ const label = config.label || config.field.toUpperCase().replace( /([a-z])([A-Z])/g, '$1_$2' );
227
+
228
+ const error = config.validator( value, originalEnv || value, label );
229
+ if ( error ) {
230
+ errors.push( `${label}: ${error}` );
231
+ }
232
+ }
233
+
234
+ // Fail fast with clear error message
235
+ if ( errors.length > 0 ) {
236
+ console.error( 'composer: Environment variable validation failed:' );
237
+ errors.forEach( ( err ) => console.error( ` - ${err}` ) );
238
+ process.exit( 1 ); // eslint-disable-line no-process-exit
239
+ }
240
+ }; // validate()
241
+
242
+ // Run validation immediately on import
243
+ validate();
244
+
245
+ export { ENV_VARS, validators };
@@ -0,0 +1,47 @@
1
+ // core/mqtt-protocol.js
2
+
3
+ /**
4
+ * @fileoverview Shared MQTT protocol invariants for WinkComposer.
5
+ *
6
+ * These are design decisions, NOT deployment-configurable values.
7
+ * Both source and emitter depend on identical values for interoperability:
8
+ * - QoS 1 (at-least-once) is coupled to the dedup strategy
9
+ * - Protocol v5 is required for user properties and message expiry
10
+ * - WINK_NAMESPACE keys must match between publisher and subscriber
11
+ *
12
+ * Deployment-configurable MQTT settings (keepalive, reconnect, etc.)
13
+ * live in ENV_VARS and are consumed by each manager's constants.js.
14
+ */
15
+
16
+ // ============================================================================
17
+ // PROTOCOL INVARIANTS
18
+ // ============================================================================
19
+
20
+ /**
21
+ * MQTT v5 user property keys for WinkComposer metadata.
22
+ * Publisher (emitter) writes these; subscriber (source) reads them.
23
+ *
24
+ * @type {Object}
25
+ */
26
+ export const WINK_NAMESPACE = {
27
+ dedupId: 'winkDedupId',
28
+ timestamp: 'winkTimestamp',
29
+ version: 'winkVersion'
30
+ };
31
+
32
+ /**
33
+ * QoS level — fixed at 1 (at-least-once delivery).
34
+ * Duplicates handled by backend deduplication via winkDedupId UUID.
35
+ * Must match between emitter and source for session resumption.
36
+ *
37
+ * @type {number}
38
+ */
39
+ export const MQTT_QOS = 1;
40
+
41
+ /**
42
+ * MQTT protocol version — fixed at 5.
43
+ * Required for user properties (dedup metadata) and message expiry.
44
+ *
45
+ * @type {number}
46
+ */
47
+ export const MQTT_PROTOCOL_V = 5;
@@ -0,0 +1,7 @@
1
+ export { default as init } from './init.js';
2
+ export { default as update } from './update.js';
3
+ export { default as reset } from './reset.js';
4
+ export { default as recompute } from './recompute.js';
5
+ export { default as publishTo } from './publish-to.js';
6
+ // Re-export everything from introspection.js
7
+ export * from './introspect.js';
@@ -0,0 +1,44 @@
1
+ import validateFlow from './validate-flow.js';
2
+
3
+ const init = function ( flow ) {
4
+ // Fail-fast: reject malformed flow config at flow start rather than
5
+ // at first-message time. All three live callers (src/flow/run.js,
6
+ // composer-website demo-runner, benchmark-runner) pass through here.
7
+ validateFlow( flow );
8
+
9
+ const composerState = Object.create( null );
10
+
11
+ // Two-level structure: partitionId → specializationType → graph
12
+ composerState.partitionSpecializations = new Map();
13
+
14
+ composerState.flow = flow;
15
+
16
+ // Cumulative counter of partition-creation attempts (accepted or rejected).
17
+ // Pre-cap: equals partitionSpecializations.size. Post-cap: diverges by the
18
+ // rejection count. See ADR-016.
19
+ composerState.totalPartitionsCreated = 0;
20
+
21
+ // Yield state (ADR-024): update() sets `yieldPending` when the time
22
+ // threshold is crossed; the flow runtime clears it and takes the
23
+ // event-loop breath after running the message's pipeline.
24
+ composerState.partitionState = Object.create( null );
25
+ composerState.partitionState.lastYield = Date.now();
26
+ composerState.partitionState.yieldPending = false;
27
+
28
+ // Registry of sinks that expose getPressure(). Populated by `flow/run.js`
29
+ // immediately after this init returns, by querying the wire-emitters and
30
+ // wire-storages registries. The pressure-aware yield decision (ADR-020,
31
+ // Draft) will read this registry as one of its yield conditions when it
32
+ // lands; until then the registry is forward-compat plumbing.
33
+ //
34
+ // Object.create(null) keyed by 'emitter:<target>' / 'storage:<storageName>'
35
+ // — a runtime-string-keyed registry per the codebase convention. Keys are
36
+ // self-describing in debug logs ("which sink crossed the threshold?"
37
+ // becomes a one-glance answer); for-in iteration over Object.create(null)
38
+ // is allocation-free on the amortized check path.
39
+ composerState.partitionState.backpressureAwareSinks = Object.create( null );
40
+
41
+ return composerState;
42
+ }; // init()
43
+
44
+ export default init;
@@ -0,0 +1,15 @@
1
+ /** Single source of truth for what this plugin can do */
2
+ const SUPPORTED_STATS = [
3
+ ];
4
+
5
+ /** Human-readable descriptions (for introspection) */
6
+ const STAT_DESCRIPTIONS = {
7
+ };
8
+
9
+ /** The type of this node */
10
+ const NODE_TYPE = 'Partition Manager';
11
+
12
+ // Attach static metadata once, from our constants by making a shallow copy.
13
+ export const getSupportedStats = () => SUPPORTED_STATS.slice();
14
+ export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
15
+ export const getNodeType = () => NODE_TYPE.slice();
@@ -0,0 +1,6 @@
1
+ // eslint-disable-next-line no-empty-function
2
+ const publishTo = function ( _state, _msg ) {
3
+
4
+ }; // publishTo()
5
+
6
+ export default publishTo;
@@ -0,0 +1,5 @@
1
+ const recompute = function () {
2
+ return true;
3
+ }; // recompute()
4
+
5
+ export default recompute;
@@ -0,0 +1,5 @@
1
+ const reset = function () {
2
+ return true;
3
+ }; // reset()
4
+
5
+ export default reset;
@@ -0,0 +1,264 @@
1
+ import nodeTypeToModule from '../wiring/node-type-to-module.js';
2
+
3
+ import { ENV_VARS } from '../env-vars.js';
4
+
5
+ /**
6
+ * This function transforms trigger specifications that reference nodes by name
7
+ * into executable triggers with actual function references and state objects.
8
+ *
9
+ * Key Design Drivers:
10
+ * - All targets in a single trigger must be homogeneous (same node type)
11
+ * - Control methods are resolved from the target node type
12
+ * - Target resolution uses spec names for maintainable transpilation
13
+ * - Comprehensive validation prevents runtime errors
14
+ *
15
+ * @param {Array} stateStore - Initialized state store for target state resolution
16
+ * @param {Array<Object>} triggers - Trigger specifications with named targets
17
+ * @param {Object} scopedFlow - Specialization-scoped flow data containing specs and nodeModules
18
+ * @returns {Array<Object>} Resolved triggers with function and state references
19
+ *
20
+ * @example
21
+ * const triggers = [
22
+ * { control: 'reset', targets: [ 'fastEWMA', 'slowEWMA' ] }
23
+ * ];
24
+ * const resolved = resolveTriggers( stateStore, triggers, scopedFlow );
25
+ */
26
+ const resolveTriggers = function ( stateStore, triggers, scopedFlow ) {
27
+ // Handle empty or undefined triggers gracefully
28
+ if ( triggers === undefined || triggers.length === 0 ) {
29
+ return [];
30
+ }
31
+
32
+ // Pre-allocate for performance
33
+ const resolvedTriggers = new Array( triggers.length );
34
+
35
+ for ( let i = 0; i < triggers.length; i += 1 ) {
36
+ const trigger = triggers[ i ];
37
+ const targets = trigger.targets;
38
+ const control = trigger.control;
39
+
40
+ // Initialize resolved trigger object
41
+ const resolvedTrigger = Object.create( null );
42
+ const resolvedTargets = new Array( targets.length );
43
+
44
+ // Resolve first target to establish reference node type
45
+ const firstTargetName = targets[ 0 ];
46
+ if ( typeof firstTargetName !== 'string' || firstTargetName.length === 0 ) {
47
+ throw new TypeError( `Target name at trigger ${i}, target 0 must be a non-empty string; found '${firstTargetName}'` );
48
+ }
49
+
50
+ const firstTargetIndex = scopedFlow.specs.findIndex( ( spec ) => spec.name === firstTargetName );
51
+ if ( firstTargetIndex === -1 ) {
52
+ throw new Error( `Target node '${firstTargetName}' not found in flow specs for trigger ${i}` );
53
+ }
54
+
55
+ // Get reference node module and validate control method exists
56
+ const firstSpec = scopedFlow.specs[ firstTargetIndex ];
57
+ const moduleName = nodeTypeToModule( firstSpec.nodeType );
58
+ const referenceNode = scopedFlow.nodeModules[ moduleName ];
59
+
60
+ if ( !referenceNode ) {
61
+ throw new Error( `Node module '${moduleName}' not found for nodeType '${firstSpec.nodeType}' in trigger ${i}` );
62
+ }
63
+
64
+ if ( typeof referenceNode[ control ] !== 'function' ) {
65
+ throw new Error( `Control method '${control}' not found on node type '${referenceNode.getNodeType?.() || firstSpec.nodeType}' for trigger ${i}` );
66
+ }
67
+
68
+ // Store first resolved target
69
+ resolvedTargets[ 0 ] = stateStore[ firstTargetIndex ];
70
+
71
+ // Resolve remaining targets and validate homogeneity
72
+ for ( let j = 1; j < targets.length; j += 1 ) {
73
+ const targetName = targets[ j ];
74
+
75
+ if ( typeof targetName !== 'string' || targetName.length === 0 ) {
76
+ throw new TypeError( `Target name at trigger ${i}, target ${j} must be a non-empty string; found '${targetName}'` );
77
+ }
78
+
79
+ const targetIndex = scopedFlow.specs.findIndex( ( spec ) => spec.name === targetName );
80
+ if ( targetIndex === -1 ) {
81
+ throw new Error( `Target node '${targetName}' not found in flow specs for trigger ${i}, target ${j}` );
82
+ }
83
+
84
+ // Ensure all targets are homogeneous (same node type)
85
+ const targetSpec = scopedFlow.specs[ targetIndex ];
86
+ const targetModuleName = nodeTypeToModule( targetSpec.nodeType );
87
+ const targetNode = scopedFlow.nodeModules[ targetModuleName ];
88
+
89
+ if ( targetNode !== referenceNode ) {
90
+ const refNodeType = referenceNode.getNodeType?.() || firstSpec.nodeType;
91
+ const targetNodeType = targetNode.getNodeType?.() || targetSpec.nodeType;
92
+ throw new Error( `Target node '${targetName}' (type: ${targetNodeType}) is incompatible with reference node type '${refNodeType}' for trigger ${i}` );
93
+ }
94
+
95
+ resolvedTargets[ j ] = stateStore[ targetIndex ];
96
+ }
97
+
98
+ // Store resolved trigger with function reference and state objects
99
+ resolvedTrigger.control = referenceNode[ control ];
100
+ resolvedTrigger.targets = resolvedTargets;
101
+ resolvedTriggers[ i ] = resolvedTrigger;
102
+ }
103
+
104
+ return resolvedTriggers;
105
+ }; // resolveTriggers()
106
+
107
+ /**
108
+ * Partition state manager's update with integrated trigger resolution and specialization support.
109
+ * Resolved triggers are co-located with each node's state for zero hot-path overhead.
110
+ *
111
+ * Uses two-level lookup: partitionId → specializationType → graph
112
+ * Zero allocation in hot path: direct field access with default to 0.
113
+ *
114
+ * @param {Object} composerState - Contains partitionSpecializations and flow specification
115
+ * @param {Object} message - Input message for partition and specialization extraction
116
+ * @returns {Array|null} Graph (node state array) with resolved triggers, or null if specialization unknown
117
+ */
118
+ const update = function ( composerState, msg ) {
119
+ const { partitionSpecializations, flow, partitionState } = composerState;
120
+
121
+ // Zero allocation: direct field access, default to 0
122
+ const partitionId = flow.partitionField ?
123
+ msg[ flow.partitionField ] :
124
+ 0;
125
+
126
+ const specializationType = flow.specializationField ?
127
+ msg[ flow.specializationField ] :
128
+ 0;
129
+
130
+ // Level 1: Partition lookup
131
+ let specializedGraphs = partitionSpecializations.get( partitionId );
132
+
133
+ // Level 2: Specialization lookup (graph may exist or need creation)
134
+ let graph = specializedGraphs ? specializedGraphs[ specializationType ] : undefined;
135
+
136
+ if ( !graph ) {
137
+ const specs = flow.specsBySpecialization[ specializationType ];
138
+
139
+ if ( !specs ) {
140
+ // Log error and skip message - no default fallback
141
+ // Note: Do NOT create partition entry for unknown specialization
142
+ console.error( `[PartitionManager] Unknown specialization '${specializationType}' for partition '${partitionId}'. Message dropped.` );
143
+ return null;
144
+ }
145
+
146
+ // Create partition entry only after validating specialization exists.
147
+ // Cap check (ADR-016): enforced here — the sole site where
148
+ // partitionSpecializations.size increases. Increment-first,
149
+ // strict-`>` comparison: the Nth partition succeeds, the (N+1)th is
150
+ // rejected with a console.error mirroring the unknown-specialization
151
+ // drop pattern above.
152
+ if ( !specializedGraphs ) {
153
+ composerState.totalPartitionsCreated += 1;
154
+ if ( composerState.totalPartitionsCreated > ENV_VARS.maxPartitionsAllowed ) {
155
+ console.error(
156
+ `[PartitionManager] Partition creation for assetId '${partitionId}' ` +
157
+ `failed — maxPartitionsAllowed (${ENV_VARS.maxPartitionsAllowed}) reached. ` +
158
+ 'Message dropped.'
159
+ );
160
+ return null;
161
+ }
162
+ specializedGraphs = Object.create( null );
163
+ partitionSpecializations.set( partitionId, specializedGraphs );
164
+ }
165
+
166
+ // Create state array sized for this specialization
167
+ graph = new Array( specs.length );
168
+
169
+ for ( let k = 0; k < specs.length; k += 1 ) {
170
+ const spec = specs[ k ];
171
+
172
+ // Look up node module using nodeTypeToModule
173
+ const moduleName = nodeTypeToModule( spec.nodeType );
174
+ const node = flow.nodeModules[ moduleName ];
175
+
176
+ if ( !node ) {
177
+ throw new Error( `Node module '${moduleName}' not found for nodeType '${spec.nodeType}' at index ${k}` );
178
+ }
179
+
180
+ // Initialize node state
181
+ graph[ k ] = node.init( spec );
182
+
183
+ // ====================================================================
184
+ // MQTT TOPIC INJECTION IN EMIT IF NODE
185
+ // ====================================================================
186
+ // Precompute partition-specific MQTT topic at initialization to avoid
187
+ // reconstruction on every emission (performance optimization).
188
+ //
189
+ // Topic structure: edgeDeviceId/partitionId/specialization/insightType
190
+ // Example: "edge-001/sensor-42/temperature/changeDetected"
191
+ //
192
+ // The edgeDeviceId can encode ISA-95 hierarchy for Unified Namespace:
193
+ // - Simple: "edge-001"
194
+ // - UNS: "northwind/riverton/plant2/line3" (enterprise/site/area/cell)
195
+ //
196
+ // Enables powerful MQTT subscription patterns:
197
+ // - All from edge: "edge-001/#" or "northwind/riverton/#"
198
+ // - Specific sensor: "+/sensor-42/#"
199
+ if ( spec.nodeType === 'Emit If' && ( graph[ k ].target === 'mqtt' || graph[ k ].target === 'terminal' ) ) {
200
+ graph[ k ].topic = `${ENV_VARS.edgeDeviceId}/${partitionId}/${specializationType}/${graph[ k ].insightType}`;
201
+ }
202
+
203
+ // ====================================================================
204
+ // STORAGE AND PARTITION INJECTION IN PERSIST IF NODE
205
+ // ====================================================================
206
+ // Inject the storage singleton and partition ID at initialization.
207
+ // Storage reference is set during wiring (spec.storage), but partition
208
+ // ID is partition-specific and must be injected here.
209
+ //
210
+ // Zero hot-path overhead: All references are pre-resolved.
211
+ if ( spec.nodeType === 'Persist If' && spec.storage ) {
212
+ graph[ k ].storage = spec.storage;
213
+ graph[ k ].partitionId = partitionId;
214
+ }
215
+
216
+ // Resolve triggers with specialization-scoped data
217
+ graph[ k ].resolvedTriggers = resolveTriggers( graph, spec.triggers, {
218
+ specs: specs,
219
+ nodeModules: flow.nodeModules
220
+ });
221
+ } // for ( let k = 0; k < specs.length; k += 1 )
222
+
223
+ // Special handling for Controller nodes with nested triggers
224
+ // Only controller node allows FORWARD node control.
225
+ // That is why it has to be performed only after all nodes have
226
+ // been initialized.
227
+ for ( let k = 0; k < specs.length; k += 1 ) {
228
+ const spec = specs[ k ];
229
+ if ( spec.nodeType === 'Controller' && graph[ k ].logic ) {
230
+ // Resolve triggers for each condition in the logic array
231
+ for ( let j = 0; j < graph[ k ].logic.length; j += 1 ) {
232
+ graph[ k ].logic[ j ].resolvedTriggers = resolveTriggers(
233
+ graph,
234
+ graph[ k ].logic[ j ].triggers,
235
+ {
236
+ specs: specs,
237
+ nodeModules: flow.nodeModules
238
+ }
239
+ );
240
+ }
241
+ // No top-level resolvedTriggers for controller
242
+ graph[ k ].resolvedTriggers = null;
243
+ }
244
+ } // for ( let k = 0; k < specs.length; k += 1 )
245
+
246
+ specializedGraphs[ specializationType ] = graph;
247
+ }
248
+
249
+ // Yield decision (ADR-024, process-then-breathe): when the threshold is
250
+ // crossed, ask the caller to give the event loop a turn AFTER it has run
251
+ // this message's pipeline. The graph always goes back synchronously —
252
+ // returning a Promise here (the pre-ADR-024 shape) deferred this
253
+ // message's processing past later ones, reordering updates within a
254
+ // partition for callers that do not await. flow/run.js owns clearing
255
+ // the flag and taking the breath.
256
+ if ( Date.now() - partitionState.lastYield > flow.yieldThreshold ) {
257
+ partitionState.lastYield = Date.now();
258
+ partitionState.yieldPending = true;
259
+ }
260
+
261
+ return graph;
262
+ };
263
+
264
+ export default update;