@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,171 @@
1
+ // core/source-manager/csv/index.js
2
+
3
+ /**
4
+ * @fileoverview CSV Source Adapter.
5
+ *
6
+ * Streams a CSV file row by row to the pipeline. Each row becomes one
7
+ * message. Useful for replaying recorded data through a flow under test
8
+ * conditions, or for one-shot batch jobs that consume a file and stop.
9
+ *
10
+ * Adapter contract (ADR-018):
11
+ * - Module-level exports: `id`, `configSchema`, `start`,
12
+ * `durabilityClass`, and the default aggregate referencing the same
13
+ * constants.
14
+ * - `start( config )` returns a `stopFn({ timeout })`.
15
+ * - Lifecycle and error contract details: see `start.js` file header.
16
+ *
17
+ * Durability (ADR-018): `'best-effort'` — the source keeps no recovery state
18
+ * of its own. The file itself is replayable, so a caller can re-run the
19
+ * stream after a crash, but the adapter does not resume mid-file.
20
+ *
21
+ * `configSchema` declares the user-supplied config fields. The flow
22
+ * runtime calls `validateWithSchema( csv.configSchema, sourceConfig )`
23
+ * at DSL time (in `flow.source()`), so typos and bad types are caught
24
+ * before the flow ever runs. Per ADR-018 the schema is authoritative
25
+ * for the fields it covers; `start.js` does not re-enforce them.
26
+ *
27
+ * Runtime-injected callbacks (`onMessage`, `onShutdown`) are not part
28
+ * of `configSchema` — they are added to the config by the wiring layer
29
+ * at start time, not by the user.
30
+ *
31
+ * @example
32
+ * import { flow, csv } from '@winkjs/composer';
33
+ *
34
+ * flow( 'demo' )
35
+ * .source( csv, {
36
+ * path: './sensor-data.csv',
37
+ * delayMs: 100
38
+ * } )
39
+ * .run();
40
+ */
41
+
42
+ import { validators } from '../../utils/validate/index.js';
43
+ import { start } from './start.js';
44
+
45
+ /**
46
+ * Source identifier — used by the flow registry and error messages.
47
+ * @type {string}
48
+ */
49
+ export const id = 'csv';
50
+
51
+ /**
52
+ * Crash-survival class per ADR-018. For a source the value describes
53
+ * the input it can recover after a disconnect: CSV recovers nothing
54
+ * itself — the caller replays the file.
55
+ * @type {string}
56
+ */
57
+ export const durabilityClass = 'best-effort';
58
+
59
+ /**
60
+ * Validates that a value is either a number or a non-empty string.
61
+ * Used by `startMsgId` and `endMsgId`, which accept either an index
62
+ * (number) or a row-id field value (typically a string).
63
+ *
64
+ * @param {*} value
65
+ * @returns {boolean}
66
+ */
67
+ const isNumberOrNonEmptyString = function ( value ) {
68
+ if ( typeof value === 'number' && Number.isFinite( value ) ) return true;
69
+ if ( typeof value === 'string' && value.length > 0 ) return true;
70
+ return false;
71
+ };
72
+
73
+ /**
74
+ * Schema for the CSV source's user-supplied config. Consumed by the
75
+ * flow runtime (`flow.source()` → `validateWithSchema`) at DSL time.
76
+ *
77
+ * Required:
78
+ * - `path` — file system path to the CSV file.
79
+ *
80
+ * Optional:
81
+ * - `delayMs` — pause between messages, in milliseconds. Default 0.
82
+ * - `dynamicTyping` — auto-cast values to numbers / booleans / null.
83
+ * Default true.
84
+ * - `transform` — function( row ) → row, applied before `onMessage`;
85
+ * returning null/undefined drops the row (counted in `skipped`).
86
+ * A throw skips that one row with a per-record CALLBACK_FAILED
87
+ * report and the stream continues. Uniform with the MQTT source.
88
+ * - `onStatus` — observability callback (the ADR-018 status shape).
89
+ * Completion arrives here as `{phase: 'complete', count, skipped}` —
90
+ * the retired `onComplete` key is rejected as unknown (completion
91
+ * travels onStatus per ADR-018).
92
+ * - `shutdownOnComplete` — whether the source signals end-of-stream by
93
+ * triggering pipeline shutdown. Default true. (Inside a flow, the
94
+ * runtime overrides this to false and orchestrates the ordered drain
95
+ * itself; the option serves direct callers — ADR-018's flow-layer split.)
96
+ * - `idField` — column name to use for `startMsgId` / `endMsgId`
97
+ * matching. When omitted, the row index (0-based) is used.
98
+ * - `startMsgId`, `endMsgId` — inclusive range bounds. Number when
99
+ * matching against the row index; string when matching against an
100
+ * `idField` value.
101
+ *
102
+ * @type {Object}
103
+ */
104
+ export const configSchema = {
105
+ // Unknown keys throw at DSL time — the only unknown-key rejection
106
+ // mechanism the validator has (validate.js:68-77). onMessage and
107
+ // onShutdown are runtime-injected, so they are deliberately absent.
108
+ _propertyNames: [
109
+ 'path',
110
+ 'delayMs',
111
+ 'dynamicTyping',
112
+ 'transform',
113
+ 'onStatus',
114
+ 'shutdownOnComplete',
115
+ 'idField',
116
+ 'startMsgId',
117
+ 'endMsgId'
118
+ ],
119
+ path: {
120
+ type: 'string',
121
+ required: true,
122
+ validator: validators.nonEmptyString,
123
+ error: 'path must be a non-empty string (file system path to the CSV file)'
124
+ },
125
+ delayMs: {
126
+ type: 'number',
127
+ required: false,
128
+ validator: validators.nonNegativeFinite,
129
+ error: 'delayMs must be a non-negative finite number'
130
+ },
131
+ dynamicTyping: {
132
+ type: 'boolean',
133
+ required: false,
134
+ error: 'dynamicTyping must be a boolean'
135
+ },
136
+ transform: {
137
+ type: 'function',
138
+ required: false,
139
+ error: 'transform must be a function( row ) returning a row (or null/undefined to drop)'
140
+ },
141
+ onStatus: {
142
+ type: 'function',
143
+ required: false,
144
+ error: 'onStatus must be a function'
145
+ },
146
+ shutdownOnComplete: {
147
+ type: 'boolean',
148
+ required: false,
149
+ error: 'shutdownOnComplete must be a boolean'
150
+ },
151
+ idField: {
152
+ type: 'string',
153
+ required: false,
154
+ validator: validators.nonEmptyString,
155
+ error: 'idField must be a non-empty string (CSV column name)'
156
+ },
157
+ startMsgId: {
158
+ required: false,
159
+ validator: isNumberOrNonEmptyString,
160
+ error: 'startMsgId must be a finite number or a non-empty string'
161
+ },
162
+ endMsgId: {
163
+ required: false,
164
+ validator: isNumberOrNonEmptyString,
165
+ error: 'endMsgId must be a finite number or a non-empty string'
166
+ }
167
+ };
168
+
169
+ export { start };
170
+
171
+ export default { id, configSchema, durabilityClass, start };
@@ -0,0 +1,456 @@
1
+ // core/source-manager/csv/start.js
2
+
3
+ /**
4
+ * @fileoverview CSV streaming source implementation.
5
+ *
6
+ * Reads CSV file line-by-line using Node.js streams for memory efficiency.
7
+ * Each row is parsed and emitted as a message to the pipeline.
8
+ *
9
+ * Adapter contract (ADR-018, source role):
10
+ * - Every `onStatus` payload carries the uniform structured shape per
11
+ * ADR-018: `{status: 'green' | 'yellow' | 'red', connected, phase}`;
12
+ * the error path adds an `error: {code, message}` field. One payload
13
+ * rule, no exceptions (uniformity sweep, 2026-07-09). Callers without
14
+ * an `onStatus` handler still see error-path failures via a classified
15
+ * `console.error` line (fallback).
16
+ * - Lifecycle phases emitted (`status: 'green'` for all, since the file
17
+ * stream is healthy until it errors):
18
+ * `phase: 'starting'` — file open, includes `path`.
19
+ * `phase: 'headers'` — header line parsed, includes `headers` array.
20
+ * `phase: 'complete'` — file fully consumed; `connected: false` (stream
21
+ * closed). Includes the uniform `count` of
22
+ * produced messages (per ADR-018 there is no
23
+ * onComplete callback) and the `skipped` count
24
+ * of rows read but not delivered: structurally
25
+ * malformed, transform-dropped, or
26
+ * transform-failed. For in-range rows,
27
+ * count + skipped covers every data row read.
28
+ * `phase: 'errored'` — terminal red: the run loop failed and the
29
+ * stream is dead (the ADR-018 two-tier rule).
30
+ * `phase: 'stopped'` — forced stop exceeded its time budget
31
+ * (`status: 'yellow'`, with a `note`).
32
+ *
33
+ * `err.code` vocabulary (per-adapter; ADR-018 requires it documented here):
34
+ * - `SOURCE_UNREACHABLE` — file open failed (Node fs error code ENOENT,
35
+ * EACCES, or EISDIR). Classified at error time so consumers can route
36
+ * on code without parsing fs error strings.
37
+ * - `DECODE_ERROR` — one data row could not be parsed (field count
38
+ * does not match the header, or an unterminated quoted field). The row
39
+ * is skipped, the skip is signalled with `status: 'yellow'`, and the
40
+ * stream continues — per-record skip-classify-continue (ADR-018).
41
+ * A bad field VALUE in a parseable row is NOT a decode error;
42
+ * it passes through for the pipeline (`sanitize`) to judge.
43
+ * - `CALLBACK_FAILED` — the user's `transform` threw on one row. The
44
+ * row is skipped (counted in `skipped`), the throw is reported per
45
+ * record with `status: 'yellow'`, and the stream continues — user
46
+ * code is never reported as a stream failure. Fix the transform
47
+ * function; the report names the data row. Uniform with the MQTT
48
+ * source (transform contract, 2026-07-11).
49
+ * - `READ_ERROR` — stream read failed mid-stream (encoding error,
50
+ * truncation, decoder failure, etc.). Catch-all for non-open failures.
51
+ */
52
+
53
+ import fs from 'node:fs';
54
+ import readline from 'node:readline';
55
+
56
+ /**
57
+ * Detects delimiter from header line.
58
+ * @param {string} headerLine - First line of CSV
59
+ * @returns {string} Detected delimiter
60
+ */
61
+ const detectDelimiter = function ( headerLine ) {
62
+ if ( headerLine.includes( '\t' ) ) return '\t';
63
+ if ( headerLine.includes( ';' ) ) return ';';
64
+ return ',';
65
+ };
66
+
67
+ /**
68
+ * Parses a CSV line into values.
69
+ *
70
+ * Also reports whether the line ended inside an open quote — a
71
+ * structural fault the caller classifies as `DECODE_ERROR` (ADR-018).
72
+ *
73
+ * @param {string} line - CSV line
74
+ * @param {string} delimiter - Field delimiter
75
+ * @returns {{values: string[], unterminated: boolean}} Parsed values
76
+ * and the unterminated-quote flag
77
+ */
78
+ const parseLine = function ( line, delimiter ) {
79
+ const values = [];
80
+ let current = '';
81
+ let inQuotes = false;
82
+
83
+ for ( let i = 0; i < line.length; i += 1 ) {
84
+ const char = line[ i ];
85
+
86
+ if ( char === '"' ) {
87
+ inQuotes = !inQuotes;
88
+ } else if ( char === delimiter && !inQuotes ) {
89
+ values.push( current.trim() );
90
+ current = '';
91
+ } else {
92
+ current += char;
93
+ }
94
+ }
95
+ values.push( current.trim() );
96
+
97
+ return { values, unterminated: inQuotes };
98
+ };
99
+
100
+ /**
101
+ * Names the structural fault in a data row, or returns null when the
102
+ * row is structurally sound (the ADR-018 boundary: field-count
103
+ * mismatch and an unterminated quote are the source's DECODE_ERROR;
104
+ * a bad field VALUE inside a parseable row is the pipeline's concern
105
+ * and passes through).
106
+ *
107
+ * @param {{values: string[], unterminated: boolean}} parsed - parseLine result
108
+ * @param {string[]} headers - Column headers
109
+ * @returns {string|null} Fault description, or null when sound
110
+ */
111
+ const structuralFault = function ( parsed, headers ) {
112
+ if ( parsed.unterminated ) {
113
+ return 'unterminated quoted field';
114
+ }
115
+ if ( parsed.values.length !== headers.length ) {
116
+ return `expected ${headers.length} fields, got ${parsed.values.length}`;
117
+ }
118
+ return null;
119
+ };
120
+
121
+ /**
122
+ * Casts string value to appropriate type.
123
+ * @param {string} value - String value
124
+ * @returns {*} Typed value
125
+ */
126
+ const castValue = function ( value ) {
127
+ if ( value === '' ) return null;
128
+ if ( value.toLowerCase() === 'true' ) return true;
129
+ if ( value.toLowerCase() === 'false' ) return false;
130
+
131
+ const num = Number( value );
132
+ if ( !Number.isNaN( num ) ) return num;
133
+
134
+ return value;
135
+ };
136
+
137
+ /**
138
+ * Builds a row object from parsed CSV values.
139
+ * @param {string[]} headers - Column headers
140
+ * @param {string[]} values - Parsed values for this row
141
+ * @param {boolean} dynamicTyping - Whether to auto-cast values
142
+ * @returns {Object} Row object with header keys
143
+ */
144
+ const buildRow = function ( headers, values, dynamicTyping ) {
145
+ const row = Object.create( null );
146
+
147
+ for ( let i = 0; i < headers.length; i += 1 ) {
148
+ let value = values[ i ];
149
+ if ( dynamicTyping ) {
150
+ value = castValue( value );
151
+ }
152
+ row[ headers[ i ] ] = value;
153
+ }
154
+
155
+ return row;
156
+ };
157
+
158
+ /**
159
+ * Checks range filter status for a row.
160
+ * @param {*} rangeKey - Current row's range key (field value or index)
161
+ * @param {boolean} wasInRange - Previous inRange state
162
+ * @param {*} startMsgId - Start of range (inclusive)
163
+ * @param {*} endMsgId - End of range (inclusive)
164
+ * @returns {{inRange: boolean, shouldStop: boolean}} Range status
165
+ */
166
+ const checkRange = function ( rangeKey, wasInRange, startMsgId, endMsgId ) {
167
+ let inRange = wasInRange;
168
+
169
+ // Check if we should start processing
170
+ if ( !inRange && startMsgId !== null && rangeKey >= startMsgId ) {
171
+ inRange = true;
172
+ }
173
+
174
+ // Check if we should stop after this row
175
+ const shouldStop = endMsgId !== null && rangeKey >= endMsgId;
176
+
177
+ return { inRange, shouldStop };
178
+ };
179
+
180
+ /**
181
+ * Starts streaming CSV source.
182
+ *
183
+ * @param {Object} config - Source configuration
184
+ * @param {string} config.path - Path to CSV file
185
+ * @param {Function} config.onMessage - Message handler
186
+ * @param {number} [config.delayMs=0] - Delay between messages (ms)
187
+ * @param {boolean} [config.dynamicTyping=true] - Auto-cast values
188
+ * @param {Function} [config.transform] - Optional row transform:
189
+ * ( row ) => row; return null/undefined to drop (counted in skipped);
190
+ * a throw skips the row (CALLBACK_FAILED) and the stream continues
191
+ * @param {Function} [config.onStatus] - Status callback; completion arrives
192
+ * here as `{phase: 'complete', count, skipped}` (per ADR-018 there is
193
+ * no onComplete callback)
194
+ * @param {Function} [config.onShutdown] - Shutdown handler (injected by runtime)
195
+ * @param {boolean} [config.shutdownOnComplete=true] - Auto-shutdown pipeline when CSV ends
196
+ * @param {string} [config.idField] - Field to use for range matching (row index if omitted)
197
+ * @param {number|string} [config.startMsgId] - Start processing at this id/row (inclusive)
198
+ * @param {number|string} [config.endMsgId] - Stop processing after this id/row (inclusive)
199
+ * @returns {Function} Stop function
200
+ */
201
+ export const start = function ( config ) {
202
+ const {
203
+ path,
204
+ onMessage,
205
+ delayMs = 0,
206
+ dynamicTyping = true,
207
+ transform = null,
208
+ onStatus = null,
209
+ onShutdown = null,
210
+ shutdownOnComplete = true,
211
+ idField = null,
212
+ startMsgId = null,
213
+ endMsgId = null
214
+ } = config;
215
+
216
+ let stopped = false;
217
+ let rowCount = 0;
218
+ let rowIndex = 0;
219
+ let skippedCount = 0;
220
+ let inRange = ( startMsgId === null ); // Start immediately if no startMsgId
221
+
222
+ // Per-record skip-classify-continue (ADR-018): a structurally
223
+ // malformed row is skipped and signalled — never a silent drop. When
224
+ // the caller owns reporting (onStatus supplied) the framework stays
225
+ // quiet; without one, the classified console.error keeps the skip
226
+ // visible (ADR-018's two-party rule).
227
+ const reportDecodeError = function ( dataRowIndex, fault ) {
228
+ const message = `data row ${dataRowIndex}: ${fault} — row skipped`;
229
+ if ( onStatus ) {
230
+ onStatus( {
231
+ status: 'yellow',
232
+ connected: true,
233
+ phase: 'running',
234
+ error: { code: 'DECODE_ERROR', message }
235
+ } );
236
+ } else {
237
+ console.error( `CSV source error [DECODE_ERROR]: ${message}` );
238
+ }
239
+ };
240
+
241
+ // A throwing transform is user code, never a stream failure: the
242
+ // row is skipped with a per-record CALLBACK_FAILED report and the
243
+ // stream continues (transform contract, uniform with the MQTT
244
+ // source, 2026-07-11). Same two-party reporting rule as decode
245
+ // errors above.
246
+ const reportCallbackError = function ( dataRowIndex, err ) {
247
+ const message = `data row ${dataRowIndex}: transform threw: ${err.message} — row skipped`;
248
+ if ( onStatus ) {
249
+ onStatus( {
250
+ status: 'yellow',
251
+ connected: true,
252
+ phase: 'running',
253
+ error: { code: 'CALLBACK_FAILED', message }
254
+ } );
255
+ } else {
256
+ console.error( `CSV source error [CALLBACK_FAILED]: ${message}` );
257
+ }
258
+ };
259
+
260
+ // Run the user's transform under guard. Returns the transformed
261
+ // row; null when the transform threw (reported above) — the caller
262
+ // counts every null/undefined result as skipped.
263
+ const applyTransform = function ( row, dataRowIndex ) {
264
+ if ( !transform ) {
265
+ return row;
266
+ }
267
+ try {
268
+ return transform( row );
269
+ } catch ( err ) {
270
+ reportCallbackError( dataRowIndex, err );
271
+ return null;
272
+ }
273
+ };
274
+
275
+ // Holds the file stream so the stop function can close it if the
276
+ // read loop runs out of time. `finished` is a Promise that turns
277
+ // ready as soon as the loop exits, success or error. Together they
278
+ // give the stop function its time-budget behaviour, the same shape
279
+ // sinks use per ADR-018.
280
+ let activeStream = null;
281
+ let resolveFinished;
282
+ const finished = new Promise( ( resolve ) => {
283
+ resolveFinished = resolve;
284
+ } );
285
+
286
+ const run = async function () {
287
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- `path` comes from the caller-supplied CSV source config (`config.path`). This adapter does not validate path provenance; per composer's adapter contract the caller (flow runtime + its configuration source) owns that. Sufficient under zone=internal (SECURITY-MODEL.md §2); re-evaluate if CSV source is ever exposed to untrusted configuration.
288
+ const fileStream = fs.createReadStream( path, { encoding: 'utf8' } );
289
+ activeStream = fileStream;
290
+ const rl = readline.createInterface( { input: fileStream, crlfDelay: Infinity } );
291
+
292
+ let headers = null;
293
+ let delimiter = ',';
294
+
295
+ if ( onStatus ) onStatus( {
296
+ status: 'green',
297
+ connected: true,
298
+ phase: 'starting',
299
+ path
300
+ } );
301
+
302
+ try {
303
+ for await ( const line of rl ) {
304
+ if ( stopped ) break;
305
+ if ( !line.trim() ) continue; // eslint-disable-line no-continue
306
+
307
+ // First line is header
308
+ if ( !headers ) {
309
+ delimiter = detectDelimiter( line );
310
+ headers = parseLine( line, delimiter ).values;
311
+ if ( onStatus ) onStatus( {
312
+ status: 'green',
313
+ connected: true,
314
+ phase: 'headers',
315
+ headers
316
+ } );
317
+ continue; // eslint-disable-line no-continue
318
+ }
319
+
320
+ // Parse the data row; a structurally malformed row is
321
+ // skipped, classified, and the stream continues (ADR-018).
322
+ // The skipped row still occupies its position — rowIndex
323
+ // advances so positional range filtering stays stable.
324
+ const parsed = parseLine( line, delimiter );
325
+ const fault = structuralFault( parsed, headers );
326
+ if ( fault ) {
327
+ skippedCount += 1;
328
+ reportDecodeError( rowIndex, fault );
329
+ rowIndex += 1;
330
+ continue; // eslint-disable-line no-continue
331
+ }
332
+ const row = buildRow( headers, parsed.values, dynamicTyping );
333
+
334
+ // Determine range key and check filter
335
+ const rangeKey = idField ? row[ idField ] : rowIndex;
336
+ const rangeStatus = checkRange( rangeKey, inRange, startMsgId, endMsgId );
337
+ inRange = rangeStatus.inRange;
338
+
339
+ // Process only if in range
340
+ if ( inRange ) {
341
+ const msg = applyTransform( row, rowIndex );
342
+
343
+ // Only null/undefined mean drop — any other return,
344
+ // however falsy, is delivered (transform contract,
345
+ // uniform with the MQTT source, 2026-07-11).
346
+ if ( msg !== null && msg !== undefined ) {
347
+ await onMessage( msg );
348
+ rowCount += 1;
349
+ } else {
350
+ skippedCount += 1;
351
+ }
352
+
353
+ if ( delayMs > 0 && !stopped ) {
354
+ await new Promise( ( resolve ) => setTimeout( resolve, delayMs ) );
355
+ }
356
+
357
+ if ( rangeStatus.shouldStop ) break;
358
+ }
359
+
360
+ rowIndex += 1;
361
+ }
362
+ } finally {
363
+ activeStream = null;
364
+ }
365
+
366
+ // Completion travels onStatus with the uniform `count` field
367
+ // (per ADR-018 there is no onComplete callback).
368
+ if ( onStatus ) onStatus( {
369
+ status: 'green',
370
+ connected: false,
371
+ phase: 'complete',
372
+ count: rowCount,
373
+ skipped: skippedCount
374
+ } );
375
+
376
+ // Auto-shutdown pipeline if requested
377
+ if ( shutdownOnComplete && onShutdown ) {
378
+ await onShutdown();
379
+ }
380
+ };
381
+
382
+ // Start async processing. Failures inside the run loop are classified
383
+ // and routed through onStatus per ADR-018; callers without an
384
+ // onStatus handler still see the failure via a classified console.error
385
+ // (fallback) so a misconfigured pipeline is never silently swallowed.
386
+ // The `finished` Promise resolves once the run loop exits (success or
387
+ // error) so `stopFn({ timeout })` can wait on it.
388
+ run().catch( ( err ) => {
389
+ const fsCode = err && err.code;
390
+ const code = ( fsCode === 'ENOENT' || fsCode === 'EACCES' || fsCode === 'EISDIR' ) ?
391
+ 'SOURCE_UNREACHABLE' :
392
+ 'READ_ERROR';
393
+ const message = ( err && err.message ) ? err.message : String( err );
394
+ if ( onStatus ) {
395
+ // Terminal red: the stream is dead. Uniform payload with
396
+ // phase 'errored' per the ADR-018 two-tier rule.
397
+ onStatus( {
398
+ status: 'red',
399
+ connected: false,
400
+ phase: 'errored',
401
+ error: { code, message }
402
+ } );
403
+ } else {
404
+ console.error( `CSV source error [${code}]: ${message}` );
405
+ }
406
+ } ).finally( () => {
407
+ resolveFinished();
408
+ } );
409
+
410
+ // Stop the source, with a time budget. Per ADR-018.
411
+ //
412
+ // What it does:
413
+ // - Asks the read loop to stop at the next row by setting `stopped`.
414
+ // - Waits for the loop to finish reading. If it finishes within
415
+ // `timeout` ms, the stop returns right away.
416
+ // - If the loop is stuck (for example, waiting forever inside
417
+ // `onMessage(msg)`), the time budget runs out. We then close
418
+ // the file and let the stop return anyway, so the rest of the
419
+ // shutdown can proceed.
420
+ // - When the budget runs out, we send a yellow status so callers
421
+ // know the stop was forced rather than clean.
422
+ //
423
+ // The timer is `unref()`ed so it does not keep Node alive while
424
+ // the loop is finishing in time.
425
+ //
426
+ // Default time budget (5000 ms) matches what sinks use, so the
427
+ // whole pipeline shutdown has consistent timing.
428
+ return function ( { timeout = 5000 } = {} ) {
429
+ stopped = true;
430
+ return new Promise( ( resolve ) => {
431
+ let settled = false;
432
+ let forceTimer = null;
433
+ const settle = function () {
434
+ if ( settled ) return;
435
+ settled = true;
436
+ if ( forceTimer ) clearTimeout( forceTimer );
437
+ resolve();
438
+ };
439
+ forceTimer = setTimeout( () => {
440
+ if ( activeStream ) activeStream.destroy();
441
+ if ( onStatus ) {
442
+ onStatus( {
443
+ status: 'yellow',
444
+ connected: false,
445
+ phase: 'stopped',
446
+ note: `Stop took longer than ${timeout}ms — forced.`
447
+ } );
448
+ }
449
+ settle();
450
+ }, timeout );
451
+ forceTimer.unref();
452
+ finished.then( settle, settle );
453
+ } );
454
+ };
455
+ };
456
+