@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,76 @@
1
+ /**
2
+ * @fileoverview Tunable resolution for groupBy expansion.
3
+ *
4
+ * When a tunable (function with `.semantics`) has `semantics.field` matching
5
+ * the groupBy field, we resolve it to the concrete value for that group.
6
+ *
7
+ * Example:
8
+ * lookupByField( 'rpmBand', { idle: 3.4, low: 3.2 } )
9
+ * For group 'idle' → resolves to 3.4
10
+ * For group 'low' → resolves to 3.2
11
+ *
12
+ * Tunables with different fields are preserved for runtime evaluation:
13
+ * lookupByField( 'tempRegime', { warm: 0.02, hot: 0.05 } )
14
+ * Preserved as-is regardless of groupBy field
15
+ *
16
+ * @see ADR-006: Tunable Pattern for Dynamic Parameters
17
+ * @see ADR-007: groupBy DSL Construct
18
+ */
19
+
20
+ /**
21
+ * Recursively resolves tunables in a value structure.
22
+ *
23
+ * For `lookupByField` tunables where `semantics.field === groupByField`:
24
+ * - Resolves to the mapped value for the group
25
+ * - Falls back to semantics.default if group value not in map
26
+ *
27
+ * All other values pass through unchanged (including non-matching tunables).
28
+ *
29
+ * @param {*} value - Value to process (may contain tunables)
30
+ * @param {string} groupByField - The field used for grouping (e.g., 'rpmBand')
31
+ * @param {string|number} groupValue - The group value to resolve for (e.g., 'idle')
32
+ * @returns {*} Value with matching tunables resolved
33
+ */
34
+ const resolveTunablesInValue = function ( value, groupByField, groupValue ) {
35
+ // Check if this is a resolvable tunable
36
+ if ( typeof value === 'function' && value.semantics ) {
37
+ const { type, field, map } = value.semantics;
38
+ if ( type === 'lookupByField' && field === groupByField ) {
39
+ // Resolve: map[groupValue] or default
40
+ return map[ groupValue ] ?? value.semantics.default;
41
+ }
42
+ // Non-matching tunable - preserve for runtime
43
+ return value;
44
+ }
45
+
46
+ // Functions without semantics - preserve for runtime
47
+ if ( typeof value === 'function' ) {
48
+ return value;
49
+ }
50
+
51
+ // Null/undefined/primitives pass through
52
+ if ( value === null || value === undefined || typeof value !== 'object' ) {
53
+ return value;
54
+ }
55
+
56
+ // Arrays - recurse into each element
57
+ if ( Array.isArray( value ) ) {
58
+ const result = [];
59
+ for ( let i = 0; i < value.length; i += 1 ) {
60
+ result.push( resolveTunablesInValue( value[ i ], groupByField, groupValue ) );
61
+ }
62
+ return result;
63
+ }
64
+
65
+ // Objects - recurse into each property
66
+ const result = Object.create( null );
67
+ const keys = Object.keys( value );
68
+ for ( let i = 0; i < keys.length; i += 1 ) {
69
+ const key = keys[ i ];
70
+ result[ key ] = resolveTunablesInValue( value[ key ], groupByField, groupValue );
71
+ }
72
+
73
+ return result;
74
+ };
75
+
76
+ export { resolveTunablesInValue };
@@ -0,0 +1,392 @@
1
+ // flow/run.js
2
+
3
+ /**
4
+ * @fileoverview Runtime bootstrap for direct pipeline execution.
5
+ *
6
+ * Orchestrates: module loading, emitter wiring, partition manager setup,
7
+ * source initialization, and graceful shutdown coordination.
8
+ *
9
+ * Supports two modes:
10
+ * - Single-pipeline: Traditional flow with specs array (backward compatible)
11
+ * - Multi-specialization: Switch/case flow with specsByCase object
12
+ */
13
+
14
+ import { loadNodeModules } from './load-node-module.js';
15
+ import { validateFlowOrThrow } from './validate.js';
16
+ import * as partitionManager from '../core/partition-manager/index.js';
17
+ import { wireLinearGraph, emitters, storages, assertModuleDurability } from '../core/wiring/index.js';
18
+ import shutdownManager from '../core/shutdown-manager/index.js';
19
+ import { ENV_VARS } from '../core/env-vars.js';
20
+
21
+ /**
22
+ * Wires and starts the pipeline for direct execution.
23
+ * Handles both single-pipeline and multi-specialization modes.
24
+ *
25
+ * @param {string} flowName - Name of the flow
26
+ * @param {Array|Object} specsOrSpecsByCase - Node specs array OR specsByCase object
27
+ * @param {Set} importSet - Set of node names used
28
+ * @param {Object} runtime - Runtime configuration
29
+ * @param {Array} [caseOrder] - Order of case keys (only for multi-specialization)
30
+ * @returns {Promise<Object>} Pipeline handle with shutdown method
31
+ */
32
+ export const runFlow = async function ( flowName, specsOrSpecsByCase, importSet, runtime, caseOrder ) {
33
+ // Determine if this is multi-specialization mode
34
+ const isMultiSpec = caseOrder !== undefined && !Array.isArray( specsOrSpecsByCase );
35
+
36
+ // 1. Load node modules dynamically
37
+ const nodeNames = Array.from( importSet );
38
+ const nodeModules = await loadNodeModules( nodeNames );
39
+
40
+ // Convert Map to plain object for compatibility
41
+ const nodeModulesObj = Object.create( null );
42
+ for ( const [ name, mod ] of nodeModules ) {
43
+ nodeModulesObj[ name ] = mod;
44
+ }
45
+
46
+ // 2. Build emitter configs and modules for wiring
47
+ const emitterConfigs = Object.create( null );
48
+ const emitterModules = Object.create( null );
49
+ for ( const [ id, { adapter, config } ] of Object.entries( runtime.emitters ) ) {
50
+ emitterConfigs[ id ] = config;
51
+ emitterModules[ id ] = adapter;
52
+ }
53
+
54
+ // 3. Build storage configs and modules for wiring (optional)
55
+ const storageConfigs = Object.create( null );
56
+ const storageModules = Object.create( null );
57
+ const runtimeStorages = runtime.storages || Object.create( null );
58
+ for ( const [ id, { adapter, config } ] of Object.entries( runtimeStorages ) ) {
59
+ storageConfigs[ id ] = config;
60
+ storageModules[ id ] = adapter;
61
+ }
62
+
63
+ let specsBySpecialization;
64
+ let graphs;
65
+
66
+ // emitterModules / storageModules are already `{ id: adapterModule }`
67
+ // maps (built above from runtime.emitters / runtime.storages). They
68
+ // serve double duty: cross-flow target validation (existence check
69
+ // via `id in modules`) and capability-driven semanticsRequirement
70
+ // checks (read `module.semanticsRequirement`).
71
+ // validateFlowOrThrow takes these maps directly.
72
+
73
+ if ( isMultiSpec ) {
74
+ // Multi-specialization mode
75
+ specsBySpecialization = specsOrSpecsByCase;
76
+
77
+ // Validate all specializations
78
+ for ( let i = 0; i < caseOrder.length; i += 1 ) {
79
+ const key = caseOrder[ i ];
80
+ validateFlowOrThrow( `${flowName}:${key}`, specsBySpecialization[ key ], nodeModulesObj, emitterModules, storageModules, runtime.assetClass );
81
+ }
82
+
83
+ // Wire emitters and storages across all specializations
84
+ for ( let i = 0; i < caseOrder.length; i += 1 ) {
85
+ const key = caseOrder[ i ];
86
+ await emitters.wire( specsBySpecialization[ key ], emitterConfigs, emitterModules, runtime.assetClass ); // eslint-disable-line no-await-in-loop
87
+ await storages.wire( specsBySpecialization[ key ], storageConfigs, storageModules, runtime.assetClass ); // eslint-disable-line no-await-in-loop
88
+ }
89
+
90
+ // Wire graphs for each specialization
91
+ graphs = Object.create( null );
92
+ for ( let i = 0; i < caseOrder.length; i += 1 ) {
93
+ const key = caseOrder[ i ];
94
+ graphs[ key ] = wireLinearGraph( specsBySpecialization[ key ], nodeModulesObj );
95
+ }
96
+ } else {
97
+ // Single-pipeline mode (backward compatible)
98
+ const specs = specsOrSpecsByCase;
99
+
100
+ // Validate flow
101
+ validateFlowOrThrow( flowName, specs, nodeModulesObj, emitterModules, storageModules, runtime.assetClass );
102
+
103
+ // Wire emitters and storages
104
+ await emitters.wire( specs, emitterConfigs, emitterModules, runtime.assetClass );
105
+ await storages.wire( specs, storageConfigs, storageModules, runtime.assetClass );
106
+
107
+ // Build specsBySpecialization with single entry at key 0
108
+ specsBySpecialization = { 0: specs };
109
+
110
+ // Wire single graph
111
+ graphs = { 0: wireLinearGraph( specs, nodeModulesObj ) };
112
+ }
113
+
114
+ // 3. Build flow config for partition manager
115
+ const flow = {
116
+ specsBySpecialization,
117
+ nodeModules: nodeModulesObj,
118
+ partitionField: runtime.partitionField || null,
119
+ specializationField: runtime.specializationField || null,
120
+ // Default comes from YIELD_TIME_THRESHOLD_MS (500 ms, ADR-024);
121
+ // `.yield( { threshold } )` overrides it per flow.
122
+ yieldThreshold: runtime.yieldThreshold ?? ENV_VARS.yieldTimeThresholdMs
123
+ };
124
+
125
+ // 4. Initialize partition manager
126
+ const composerState = partitionManager.init( flow );
127
+
128
+ // 4a. Populate the backpressure-aware sinks registry. Wire-emitters
129
+ // and wire-storages each return the subset of their singletons that
130
+ // expose `getPressure()`, keyed 'emitter:<target>' /
131
+ // 'storage:<storageName>'. Nothing reads the registry yet — the
132
+ // pressure-aware yield decision (ADR-020, Draft) will iterate it
133
+ // when it lands; the assembly here keeps the partition manager's
134
+ // view ready for that.
135
+ Object.assign(
136
+ composerState.partitionState.backpressureAwareSinks,
137
+ emitters.getBackpressureAwareSinks(),
138
+ storages.getBackpressureAwareSinks()
139
+ );
140
+
141
+ // ========================================================================
142
+ // 5. MESSAGE DISPATCH — SYNC-FIRST HOT PATH (ADR-013, ADR-024)
143
+ // ========================================================================
144
+ //
145
+ // processMessage is deliberately NOT an `async function`. Wrapping it in
146
+ // `async/await` would allocate a Promise and schedule two microtask hops
147
+ // on EVERY message (~155 ns/msg measured overhead — ADR-013).
148
+ //
149
+ // Every message is processed synchronously, in arrival order. When the
150
+ // yield threshold fires, pmUpdate sets `partitionState.yieldPending`
151
+ // after handing back the graph — the message is processed first, the
152
+ // breath comes after (ADR-024). processMessage then returns a Promise
153
+ // that resolves on the next setImmediate turn, so an awaiting caller
154
+ // gives the event loop a chance to run background I/O (MQTT drain,
155
+ // QuestDB flush timers, stdout). Processing before breathing means a
156
+ // caller that ignores the Promise (a push source such as the MQTT
157
+ // subscriber) can never see messages update a partition out of order.
158
+ //
159
+ // The caller contract (ADR-013) is unchanged: `undefined` on the hot
160
+ // path, a Promise on the yield tick; `await` works with both, and the
161
+ // Promise never rejects — a pipeline fault throws synchronously here,
162
+ // the same as on every other message.
163
+ //
164
+ // The default threshold comes from YIELD_TIME_THRESHOLD_MS (500 ms);
165
+ // `.yield( { threshold } )` overrides it per flow, and Infinity turns
166
+ // yielding off. The yield only matters to callers that wait on this
167
+ // function — a push source breathes with the event loop on its own.
168
+ //
169
+ // This extends ADR-004's zero-allocation hot-path principle to the flow
170
+ // wrapper layer: one boolean read per message; the Promise is allocated
171
+ // only on the yield tick.
172
+ // ========================================================================
173
+
174
+ // Graph selection uses same field as partition manager for consistency.
175
+ const getSpecializationType = runtime.specializationField ?
176
+ ( msg ) => msg[ runtime.specializationField ] :
177
+ () => 0;
178
+
179
+ // Pipeline dispatch. Defined once at wire time, reused every message.
180
+ const runPipeline = function ( graph, msg ) {
181
+ const type = getSpecializationType( msg );
182
+ const pipeline = graphs[ type ];
183
+ if ( pipeline ) {
184
+ pipeline( graph, msg );
185
+ }
186
+ };
187
+
188
+ const partitionState = composerState.partitionState;
189
+
190
+ const processMessage = function ( msg ) {
191
+ const graph = partitionManager.update( composerState, msg );
192
+
193
+ // Process first — pmUpdate always returns the graph synchronously
194
+ // (or null for a dropped message, which never sets the flag).
195
+ if ( graph ) {
196
+ runPipeline( graph, msg );
197
+ }
198
+
199
+ // Breathe after (yield tick, rare): hand the awaiting caller a
200
+ // Promise that resolves once the event loop has had a full turn.
201
+ if ( partitionState.yieldPending ) {
202
+ partitionState.yieldPending = false;
203
+ return new Promise( ( resolve ) => {
204
+ setImmediate( resolve );
205
+ } );
206
+ }
207
+
208
+ return undefined;
209
+ };
210
+
211
+ // 6. Setup the flow's shutdown closure.
212
+ //
213
+ // Per ADR-018, the flow layer owns the orchestrated drain
214
+ // (source → emitters → storages).
215
+ // OS signals (SIGINT/SIGTERM) and the top-level forced-shutdown
216
+ // timeout live in the process layer (`shutdown-manager`); we
217
+ // auto-register this handle there a few lines down so signal
218
+ // routing works without the caller wiring anything up.
219
+ //
220
+ // Concurrent-safe: a second `shutdown()` call while the first is
221
+ // still draining returns the same in-flight Promise. Callers that
222
+ // `await handle.shutdown()` after a concurrent caller has already
223
+ // started see the actual drain finish, not a premature resolve.
224
+ // The `unregister()` call also detaches this handle from
225
+ // shutdown-manager so a SIGINT after a direct `handle.shutdown()`
226
+ // doesn't re-invoke a torn-down flow.
227
+ //
228
+ // Natural-completion signal: `handle.whenComplete()` returns a
229
+ // Promise that resolves when either:
230
+ // - the source emits a `phase: 'complete'` status (finite
231
+ // sources reach their natural end), or
232
+ // - shutdown is called (forced exit).
233
+ // For infinite sources (the MQTT subscriber), only the second
234
+ // path resolves it. Test pattern:
235
+ //
236
+ // const handle = await flow(...).source(...).run();
237
+ // await handle.whenComplete(); // wait for the source to finish
238
+ // await handle.shutdown(); // drain the sinks
239
+ //
240
+ // Auto-shutdown on natural completion is owned by the runtime, not
241
+ // the source. When the source emits `phase: 'complete'` we trigger
242
+ // `shutdown()` here — fire-and-forget, since shutdown is
243
+ // concurrent-safe. The source's own `shutdownOnComplete` config is
244
+ // overridden to `false` for in-flow use so the source never invokes
245
+ // `onShutdown` itself; that prevents the recursion where the
246
+ // source's run() awaits onShutdown which awaits stopSource which
247
+ // awaits the source's run(). Source-level `shutdownOnComplete`
248
+ // remains a feature for direct (non-flow) callers.
249
+ let stopSource = null;
250
+ let unregister = null;
251
+ let shutdownPromise = null;
252
+ // Time budget for each shutdown stage (source, then emitters, then
253
+ // storages). The shutdown-manager wraps the whole flow shutdown in
254
+ // its own outer timeout; this per-stage value keeps any single
255
+ // stuck stage from using up that whole budget. Each stage uses
256
+ // `{ timeout }` per ADR-018 — this is the middle of its three
257
+ // nested shutdown timeouts.
258
+ const STAGE_TIMEOUT_MS = 5000;
259
+
260
+ let resolveWhenComplete;
261
+ const whenCompletePromise = new Promise( function ( resolve ) {
262
+ resolveWhenComplete = resolve;
263
+ } );
264
+
265
+ // Runs one drain stage and absorbs its failure into `stageErrors`,
266
+ // so a failing stage cannot skip the stages after it — a source
267
+ // that will not stop must not strand buffered emitter and
268
+ // storage data. The failure is logged here, naming the stage, and
269
+ // the first one is rethrown by drainAll below.
270
+ const runDrainStage = async function ( label, stageErrors, stage ) {
271
+ try {
272
+ await stage();
273
+ } catch ( err ) {
274
+ stageErrors.push( err );
275
+ console.error(
276
+ `WinkComposer/flow: ${label} drain stage failed [${( err && err.code ) || 'UNKNOWN'}]: ` +
277
+ `${( err && err.message ) || String( err )} — later drain stages still ran`
278
+ );
279
+ }
280
+ }; // runDrainStage()
281
+
282
+ const drainAll = async function () {
283
+ const stageErrors = [];
284
+ try {
285
+ if ( unregister ) unregister();
286
+ if ( stopSource ) {
287
+ await runDrainStage( 'source-stop', stageErrors,
288
+ () => stopSource( { timeout: STAGE_TIMEOUT_MS } ) );
289
+ }
290
+ await runDrainStage( 'emitter', stageErrors,
291
+ () => emitters.shutdown( { timeout: STAGE_TIMEOUT_MS } ) );
292
+ await runDrainStage( 'storage', stageErrors,
293
+ () => storages.shutdown( { timeout: STAGE_TIMEOUT_MS } ) );
294
+ } finally {
295
+ // Whatever the path here — clean drain or a stage that threw —
296
+ // the source has stopped producing, so unblock any
297
+ // whenComplete() waiter. The error itself still propagates to
298
+ // shutdown()'s caller; it must not also hang bystanders.
299
+ resolveWhenComplete();
300
+ }
301
+ // Every stage ran; the caller is still owed a rejection when any
302
+ // failed. First error wins — it is the earliest loss.
303
+ if ( stageErrors.length > 0 ) {
304
+ throw stageErrors[ 0 ];
305
+ }
306
+ };
307
+ const shutdown = function () {
308
+ if ( !shutdownPromise ) {
309
+ shutdownPromise = drainAll();
310
+ }
311
+ return shutdownPromise;
312
+ };
313
+
314
+ // 7. Start source (singleton).
315
+ if ( runtime.source ) {
316
+ const { adapter, config } = runtime.source;
317
+
318
+ // ADR-018 module-surface check: the source module must say what
319
+ // a crash costs — before start() runs. Placed here
320
+ // rather than in flow.js so direct runFlow() callers are covered
321
+ // too (they bypass the DSL entirely).
322
+ assertModuleDurability( adapter.id || 'source', adapter );
323
+
324
+ // Wrap user's onStatus so the runtime can spot the natural-
325
+ // completion signal without taking it away from the user.
326
+ // When the source signals `phase: 'complete'`:
327
+ // 1. Resolve the whenComplete Promise so callers can stop
328
+ // waiting for natural completion.
329
+ // 2. Trigger the drain (`shutdown()` is fire-and-forget —
330
+ // it's concurrent-safe and stages have their own timeouts).
331
+ const userOnStatus = config.onStatus;
332
+ const wrappedOnStatus = function ( s ) {
333
+ if ( userOnStatus ) {
334
+ userOnStatus( s );
335
+ } else if ( s && s.status === 'red' ) {
336
+ // No user handler: log the failure here, classified. The
337
+ // source's own console fallback fires only when NO handler
338
+ // exists — and this wrapper IS a handler, so the source
339
+ // stays quiet. Without this branch a red status inside
340
+ // a flow would vanish unreported.
341
+ const code = ( s.error && s.error.code ) || 'UNKNOWN';
342
+ const message = ( s.error && s.error.message ) ||
343
+ 'source reported status red with no error detail';
344
+ console.error( `WinkComposer/flow '${flowName}': source error [${code}]: ${message}` );
345
+ }
346
+ if ( s && s.phase === 'complete' ) {
347
+ resolveWhenComplete();
348
+ shutdown();
349
+ }
350
+ };
351
+ stopSource = adapter.start( {
352
+ ...config,
353
+ // Override any source-level auto-shutdown setting. The
354
+ // runtime owns the trigger when the source runs inside a
355
+ // flow; sources that auto-shutdown themselves would call
356
+ // back into our `shutdown()` while their run loop awaited
357
+ // it — a circular wait that takes the source-level force
358
+ // timer to break.
359
+ shutdownOnComplete: false,
360
+ onStatus: wrappedOnStatus,
361
+ onMessage: processMessage,
362
+ onShutdown: shutdown
363
+ } );
364
+ } else {
365
+
366
+ /* c8 ignore next 3 -- defensive: the flow API rejects empty flows before run() is called, so a flow without a source cannot reach this branch via the public API. Kept as a safety net for direct runFlow() callers. */
367
+ // No source means "nothing to wait for" — resolve immediately
368
+ // so any `await handle.whenComplete()` is a no-op.
369
+ resolveWhenComplete();
370
+ }
371
+
372
+ // 8. Auto-register with shutdown-manager so OS signals are routed
373
+ // here. The flow layer no longer attaches its own SIGINT/SIGTERM
374
+ // handlers — that's the process layer's exclusive
375
+ // responsibility. Caller experience is unchanged: Ctrl-C still
376
+ // shuts the flow down gracefully, and `handle.shutdown()` still
377
+ // works directly for callers that want explicit control.
378
+ unregister = shutdownManager.register( { shutdown } );
379
+ shutdownManager.attachHandlers();
380
+
381
+ // Return pipeline handle.
382
+ return {
383
+ flowName,
384
+ composerState,
385
+ shutdown,
386
+ processMessage,
387
+ whenComplete: function () {
388
+ return whenCompletePromise;
389
+ }
390
+ };
391
+ };
392
+
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Function-aware, deterministic serializer for generated modules.
3
+ * - Preserves functions verbatim
4
+ * - Single quotes for strings
5
+ * - Stable key order
6
+ * - Identifier keys unquoted when safe
7
+ * - 4-space indents
8
+ */
9
+
10
+ const INDENT = 4;
11
+
12
+ const repeat = function ( n ) {
13
+ return ' '.repeat( n );
14
+ }; // repeat()
15
+
16
+ const isIdentifier = function ( key ) {
17
+ return ( /^[A-Za-z_$][A-Za-z0-9_$]*$/ ).test( key );
18
+ }; // isIdentifier()
19
+
20
+ const escapeString = function ( str ) {
21
+ return str
22
+ .replace( /\\/g, '\\\\' ) // Backslashes first
23
+ .replace( /'/g, '\\\'' ) // Single quotes
24
+ .replace( /\n/g, '\\n' ) // Newlines
25
+ .replace( /\r/g, '\\r' ) // Carriage returns
26
+ .replace( /\t/g, '\\t' ); // Tabs
27
+ };
28
+
29
+ const serializeValue = function ( value, indent ) {
30
+ const pad = repeat( indent );
31
+ const next = indent + INDENT;
32
+
33
+ // Handle undefined, null & RegExp explicitly
34
+ if ( value === undefined ) {
35
+ return 'undefined';
36
+ } else if ( value === null ) {
37
+ return 'null';
38
+ } else if ( value instanceof RegExp ) {
39
+ return value.toString();
40
+ }
41
+
42
+ const t = typeof value;
43
+
44
+ if ( t === 'string' ) {
45
+ return `'${escapeString( value )}'`;
46
+ }
47
+
48
+ if ( t === 'number' ) {
49
+ // Handle special numbers
50
+ if ( Number.isNaN( value ) ) return 'NaN';
51
+ if ( value === Infinity ) return 'Infinity';
52
+ if ( value === -Infinity ) return '-Infinity';
53
+ return String( value );
54
+ }
55
+
56
+ if ( t === 'boolean' ) {
57
+ return String( value );
58
+ }
59
+
60
+ if ( t === 'function' ) {
61
+ const funcStr = value.toString();
62
+ if ( funcStr.includes( '[native code]' ) ) throw Error( 'WinkComposer/flow: only regular or arrow functions are supported.' );
63
+ return funcStr;
64
+ }
65
+
66
+ if ( Array.isArray( value ) ) {
67
+ if ( value.length === 0 ) {
68
+ return '[]';
69
+ }
70
+ const parts = [];
71
+ for ( let i = 0; i < value.length; i += 1 ) {
72
+ parts.push( `${repeat( next )}${serializeValue( value[ i ], next )}` );
73
+ }
74
+ return `[\n${parts.join( ',\n' )}\n${pad}]`;
75
+ }
76
+
77
+ const keys = Object.keys( value ).sort();
78
+ if ( keys.length === 0 ) {
79
+ return '{}';
80
+ }
81
+
82
+ const lines = [];
83
+ for ( let i = 0; i < keys.length; i += 1 ) {
84
+ const k = keys[ i ];
85
+ const v = value[ k ];
86
+ const key = isIdentifier( k ) ? k : `'${k.replace( /'/g, '\\\'' )}'`;
87
+ lines.push( `${repeat( next )}${key}: ${serializeValue( v, next )}` );
88
+ }
89
+
90
+ return `{\n${lines.join( ',\n' )}\n${pad}}`;
91
+ }; // serializeValue()
92
+
93
+ /**
94
+ * Serializes a case key for code generation.
95
+ * String keys become quoted, numeric keys remain unquoted.
96
+ *
97
+ * @param {string|number} key - The case key
98
+ * @returns {string} Serialized key for code output
99
+ */
100
+ const serializeCaseKey = function ( key ) {
101
+ if ( typeof key === 'string' ) {
102
+ return `'${escapeString( key )}'`;
103
+ }
104
+ return String( key );
105
+ }; // serializeCaseKey()
106
+
107
+ /**
108
+ * Generates a JS module from flow specifications.
109
+ * Supports both single-pipeline (backward compatible) and multi-specialization modes.
110
+ *
111
+ * @param {Object} ctx - Serialization context
112
+ * @param {Array} ctx.imports - List of node modules to import
113
+ * @param {string} ctx.flowName - Name of the flow
114
+ * @param {Array} [ctx.specs] - Single-pipeline specs (backward compatible)
115
+ * @param {Object} [ctx.specsByCase] - Multi-specialization specs keyed by case
116
+ * @param {Array} [ctx.caseOrder] - Order of case keys for deterministic output
117
+ * @returns {string} Generated JS module source code
118
+ */
119
+ const serializeModule = function ( ctx ) {
120
+ const imports = ctx.imports || [];
121
+
122
+ const importLine = ( imports.length > 0 ) ?
123
+ `import { ${imports.join( ', ' )} } from '../src/nodes/index.js';\n\n` : '';
124
+
125
+ let body;
126
+
127
+ if ( ctx.specsByCase && ctx.caseOrder ) {
128
+ // Multi-specialization mode: generate entry per case
129
+ const entries = [];
130
+ for ( let i = 0; i < ctx.caseOrder.length; i += 1 ) {
131
+ const key = ctx.caseOrder[ i ];
132
+ const specs = ctx.specsByCase[ key ];
133
+ const serializedKey = serializeCaseKey( key );
134
+ const specsBody = serializeValue( specs, INDENT );
135
+ entries.push( `flowBySpecialization[ ${serializedKey} ] = ${specsBody};` );
136
+ }
137
+
138
+ body =
139
+ `${importLine}const flowBySpecialization = Object.create( null );
140
+
141
+ ${entries.join( '\n\n' )}
142
+
143
+ export default flowBySpecialization;
144
+ `;
145
+ } else {
146
+ // Single-pipeline mode (backward compatible)
147
+ const specs = ctx.specs || [];
148
+ const specsBody = serializeValue( specs, INDENT );
149
+
150
+ body =
151
+ `${importLine}const flowBySpecialization = Object.create( null );
152
+ flowBySpecialization[ 0 ] = ${specsBody};
153
+
154
+ export default flowBySpecialization;
155
+ `;
156
+ }
157
+
158
+ return body;
159
+ }; // serializeModule()
160
+
161
+ export { serializeModule };