@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,130 @@
1
+ // nodes/appraise/update.js
2
+
3
+ /**
4
+ * @fileoverview Hot path for two-layer SNN appraise node message processing.
5
+ *
6
+ * Pipeline per message:
7
+ * 1. Guard / validate timestamp
8
+ * 2. Compute decay factors (L1 uniform or per-source, L2 single)
9
+ * 3. L1 receptor loop: deviation -> normalise -> processReceptor (LIF+BLI+Rate)
10
+ * 4. L2 decision: synaptic current -> membrane update
11
+ * 5. Calibration check (burn-in Theta derivation)
12
+ * 6. MM readout -> conviction
13
+ * 7. Threshold classification
14
+ *
15
+ * Zero allocation. All arrays and objects are pre-allocated in init.
16
+ * Missing source fields cause pure decay (no injection) on that source.
17
+ *
18
+ * @see ADR-004
19
+ */
20
+
21
+ import { computeDeviation } from './deviation.js';
22
+ import { normalise } from './integrate.js';
23
+ import { processReceptor, decayReceptor } from './receptor.js';
24
+ import { computeSynapticCurrent, updateMembrane, readout } from './decision.js';
25
+ import { checkCalibration } from './calibrate.js';
26
+
27
+ /**
28
+ * Processes a message through the two-layer SNN pipeline.
29
+ *
30
+ * @param {Object} state - Node state from init()
31
+ * @param {Object} msg - Incoming message with field values and timestamp
32
+ * @returns {Object} Updated state
33
+ */
34
+ const update = function ( state, msg ) {
35
+ // Guard: skip if disabled
36
+ if ( state.disable || state.pause ) return state;
37
+
38
+ // Extract timestamp
39
+ const timestamp = msg.timestamp;
40
+
41
+ // Reset health flag
42
+ state.inputValidationFailed = false;
43
+
44
+ // Validate timestamp
45
+ if ( !Number.isFinite( timestamp ) ) {
46
+ state.inputValidationFailed = true;
47
+ return state;
48
+ }
49
+
50
+ // ── Compute Decay ───────────────────────────────────────────────────────
51
+ const isFirst = !state.hasReceivedMessage;
52
+ const dt = timestamp - state.lastTimestamp;
53
+ state.lastTimestamp = timestamp;
54
+ state.hasReceivedMessage = true;
55
+ state.messageCount += 1;
56
+
57
+ // ── L1 Per-Source Receptor Loop ─────────────────────────────────────────
58
+ const thetas = state.thetas;
59
+ const fields = state.sourceFields;
60
+ const types = state.deviationTypes;
61
+ const dp1 = state.deviationP1;
62
+ const dp2 = state.deviationP2;
63
+ const n = state.sourceCount;
64
+
65
+ if ( state.uniformDecay ) {
66
+ // Fast path: single decay factor for all L1 sources
67
+ const decayFactor = isFirst ? 1 : Math.exp( -dt / state.taus[ 0 ] );
68
+
69
+ for ( let i = 0; i < n; i += 1 ) {
70
+ const raw = msg[ fields[ i ] ];
71
+ if ( Number.isFinite( raw ) ) {
72
+ const norm = normalise( computeDeviation( types[ i ], raw, dp1[ i ], dp2[ i ] ), thetas[ i ] );
73
+ processReceptor( state, i, norm, decayFactor );
74
+ } else {
75
+ decayReceptor( state, i, decayFactor );
76
+ }
77
+ }
78
+ } else {
79
+ // Per-source path: each source decays at its own rate
80
+ const taus = state.taus;
81
+ const decayFactors = state.decayFactors;
82
+
83
+ if ( isFirst ) {
84
+ decayFactors.fill( 1 );
85
+ } else {
86
+ for ( let i = 0; i < n; i += 1 ) {
87
+ decayFactors[ i ] = Math.exp( -dt / taus[ i ] );
88
+ }
89
+ }
90
+
91
+ for ( let i = 0; i < n; i += 1 ) {
92
+ const raw = msg[ fields[ i ] ];
93
+ if ( Number.isFinite( raw ) ) {
94
+ const norm = normalise( computeDeviation( types[ i ], raw, dp1[ i ], dp2[ i ] ), thetas[ i ] );
95
+ processReceptor( state, i, norm, decayFactors[ i ] );
96
+ } else {
97
+ decayReceptor( state, i, decayFactors[ i ] );
98
+ }
99
+ }
100
+ }
101
+
102
+ // ── L2 Decision Neuron ──────────────────────────────────────────────────
103
+ const l2DecayFactor = isFirst ? 1 : Math.exp( -dt / state.l2Tau );
104
+ const current = computeSynapticCurrent(
105
+ state.spikes, state.weights, state.totalAbsWeight, n
106
+ );
107
+ state.l2Membrane = updateMembrane( state.l2Membrane, current, l2DecayFactor );
108
+
109
+ // ── Calibration Check ───────────────────────────────────────────────────
110
+ if ( state.calibrating ) {
111
+ checkCalibration( state );
112
+ }
113
+
114
+ // ── Conviction Readout ──────────────────────────────────────────────────
115
+ state.combined = readout( state.l2Membrane, state.l2Theta );
116
+
117
+ // ── Threshold Classification ────────────────────────────────────────────
118
+ const levels = state.thresholdLevels;
119
+ state.stateName = 'Normal';
120
+ for ( let i = levels.length - 1; i >= 0; i -= 1 ) {
121
+ if ( state.combined >= levels[ i ].at ) {
122
+ state.stateName = levels[ i ].name;
123
+ break;
124
+ }
125
+ }
126
+
127
+ return state;
128
+ }; // update()
129
+
130
+ export default update;
@@ -0,0 +1,6 @@
1
+ export { default as init } from './init.js';
2
+ export { default as update } from './update.js';
3
+ export { default as reset } from './reset.js';
4
+ // export { default as recompute } from './recompute.js';
5
+ // export { default as publishTo } from './publish-to.js';
6
+ // export * from './introspect.js';
@@ -0,0 +1,36 @@
1
+ // import { getNodeType } from './introspect.js';
2
+
3
+ const init = function ( spec ) {
4
+ const state = Object.create( null );
5
+
6
+ // CSV source configuration
7
+ state.source = spec.source; // URL or file path
8
+ state.mode = spec.mode || 'stream'; // 'stream' or 'batch'
9
+ state.rate = spec.rate || 100; // Messages per second (for stream mode)
10
+ state.loop = spec.loop !== false; // Loop when reaching end
11
+ state.columns = spec.columns || 'auto'; // Column mapping
12
+
13
+ // Parsing options for Papaparse
14
+ state.parseOptions = {
15
+ header: true,
16
+ dynamicTyping: true,
17
+ skipEmptyLines: true,
18
+ ...( spec.parseOptions || {} )
19
+ };
20
+
21
+ // Runtime state
22
+ state.data = null; // Parsed CSV data
23
+ state.currentIndex = 0;
24
+ state.lastEmitTime = 0;
25
+ state.isLoaded = false;
26
+ state.isPaused = false;
27
+
28
+ // For browser environment - store fetch promise
29
+ state.loadPromise = null;
30
+
31
+ state.nodeType = 'CSV'; // getNodeType();
32
+
33
+ return state;
34
+ }; // init()
35
+
36
+ export default init;
@@ -0,0 +1,70 @@
1
+ // nodes/csv-source/load.js
2
+ import SimpleCSV from './simple-parser.js';
3
+
4
+ const loadCSV = async function ( state ) {
5
+ try {
6
+ let csvText;
7
+
8
+ // Check if source looks like CSV data (has newlines or commas)
9
+ const isEmbeddedData = state.source.includes('\n') ||
10
+ (state.source.includes(',') && !state.source.endsWith('.csv'));
11
+
12
+ // Determine environment and load CSV text
13
+ if ( typeof window === 'object' ) {
14
+ // Browser environment
15
+ if ( isEmbeddedData ) {
16
+ // Embedded data
17
+ csvText = state.source;
18
+ } else if ( state.source.startsWith( 'http' ) || state.source.startsWith( '/' ) ) {
19
+ // Fetch from URL
20
+ const response = await fetch( state.source );
21
+ csvText = await response.text();
22
+ } else {
23
+ // Assume it's embedded data if not a URL
24
+ csvText = state.source;
25
+ }
26
+ } else if ( typeof process === 'object' && process.versions && process.versions.node ) {
27
+ // Node.js environment
28
+ if ( isEmbeddedData ) {
29
+ // Embedded data
30
+ csvText = state.source;
31
+ } else if ( state.source.startsWith( 'http' ) ) {
32
+ // Fetch from URL
33
+ const response = await fetch( state.source );
34
+ csvText = await response.text();
35
+ } else {
36
+ // Read from file
37
+ const fs = await import( 'fs/promises' );
38
+ csvText = await fs.readFile( state.source, 'utf8' );
39
+ }
40
+ } else {
41
+ throw new Error( 'Unable to determine environment for CSV loading' );
42
+ }
43
+
44
+ // Parse CSV using SimpleCSV
45
+ const parsed = SimpleCSV.parse( csvText, state.parseOptions );
46
+
47
+ // Handle any parsing errors
48
+ if ( parsed.errors && parsed.errors.length > 0 ) {
49
+ console.warn( 'CSV parsing warnings:', parsed.errors );
50
+ }
51
+
52
+ // Store parsed data
53
+ state.data = parsed.data;
54
+ state.isLoaded = true;
55
+ state.currentIndex = 0;
56
+
57
+ // Log summary
58
+ console.log( `Loaded ${state.data.length} rows from CSV` );
59
+ if ( parsed.meta && parsed.meta.fields ) {
60
+ console.log( `Columns: ${parsed.meta.fields.join( ', ' )}` );
61
+ }
62
+
63
+ } catch ( error ) {
64
+ console.error( 'Failed to load CSV:', error );
65
+ state.data = [];
66
+ state.isLoaded = false;
67
+ }
68
+ };
69
+
70
+ export default loadCSV;
@@ -0,0 +1,14 @@
1
+ // nodes/csv-source/reset.js
2
+ const reset = function ( state ) {
3
+ // Reset to initial state
4
+ state.currentIndex = 0;
5
+ state.lastEmitTime = 0;
6
+ state.isPaused = false;
7
+
8
+ // Don't clear the data - just reset the reading position
9
+ // This allows resuming from the beginning without reloading
10
+
11
+ return true;
12
+ }; // reset()
13
+
14
+ export default reset;
@@ -0,0 +1,216 @@
1
+ // nodes/csv-source/simple-csv-parser.js
2
+
3
+ const SimpleCSV = {
4
+ parse: function ( csvText, config = {} ) {
5
+ // Default configuration
6
+ const options = {
7
+ delimiter: config.delimiter || ',',
8
+ header: config.header !== false,
9
+ dynamicTyping: config.dynamicTyping !== false,
10
+ skipEmptyLines: config.skipEmptyLines !== false,
11
+ transformHeader: config.transformHeader || null,
12
+ transform: config.transform || null,
13
+ comments: config.comments || false,
14
+ quoteChar: config.quoteChar || '"',
15
+ parseDates: config.parseDates || false
16
+ };
17
+
18
+ const result = {
19
+ data: [],
20
+ errors: [],
21
+ meta: {
22
+ delimiter: options.delimiter,
23
+ linebreak: '\n',
24
+ aborted: false,
25
+ fields: []
26
+ }
27
+ };
28
+
29
+ try {
30
+ // Normalize line breaks
31
+ csvText = csvText.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
32
+
33
+ // Split into lines (handling quoted newlines)
34
+ const lines = this.splitLines( csvText, options.quoteChar );
35
+
36
+ // Filter empty lines and comments
37
+ const processedLines = lines.filter( (line, index) => {
38
+ if ( options.skipEmptyLines && line.trim() === '' ) return false;
39
+ if ( options.comments && line.trim().startsWith( options.comments ) ) return false;
40
+ return true;
41
+ });
42
+
43
+ if ( processedLines.length === 0 ) {
44
+ return result;
45
+ }
46
+
47
+ // Parse header if present
48
+ let headers = [];
49
+ let dataStartIndex = 0;
50
+
51
+ if ( options.header ) {
52
+ headers = this.parseLine( processedLines[0], options );
53
+
54
+ // Transform headers if function provided
55
+ if ( options.transformHeader ) {
56
+ headers = headers.map( (h) => options.transformHeader( h ) );
57
+ }
58
+
59
+ result.meta.fields = headers;
60
+ dataStartIndex = 1;
61
+ }
62
+
63
+ // Parse data rows
64
+ for ( let i = dataStartIndex; i < processedLines.length; i += 1 ) {
65
+ const values = this.parseLine( processedLines[i], options );
66
+
67
+ if ( values.length === 0 ) continue;
68
+
69
+ // Create row object or array
70
+ let row;
71
+ if ( options.header && headers.length > 0 ) {
72
+ row = {};
73
+ headers.forEach( (header, index) => {
74
+ let value = values[index];
75
+
76
+ // Apply dynamic typing
77
+ if ( options.dynamicTyping ) {
78
+ value = this.castValue( value, options );
79
+ }
80
+
81
+ // Apply transform if provided
82
+ if ( options.transform ) {
83
+ value = options.transform( value, header );
84
+ }
85
+
86
+ row[header] = value;
87
+ });
88
+ } else {
89
+ row = values.map( (value) => {
90
+ if ( options.dynamicTyping ) {
91
+ return this.castValue( value );
92
+ }
93
+ return value;
94
+ });
95
+ }
96
+
97
+ result.data.push( row );
98
+ }
99
+
100
+ } catch ( error ) {
101
+ result.errors.push({
102
+ type: 'ParseError',
103
+ code: 'ParseError',
104
+ message: error.message,
105
+ row: result.data.length
106
+ });
107
+ }
108
+
109
+ return result;
110
+ },
111
+
112
+ // Split CSV into lines, respecting quoted newlines
113
+ splitLines: function ( text, quoteChar ) {
114
+ const lines = [];
115
+ let currentLine = '';
116
+ let inQuotes = false;
117
+
118
+ for ( let i = 0; i < text.length; i += 1 ) {
119
+ const char = text[i];
120
+ const nextChar = text[i + 1];
121
+
122
+ if ( char === quoteChar ) {
123
+ // Check if it's an escaped quote
124
+ if ( nextChar === quoteChar && inQuotes ) {
125
+ currentLine += quoteChar;
126
+ i += 1; // Skip next quote
127
+ } else {
128
+ inQuotes = !inQuotes;
129
+ currentLine += char;
130
+ }
131
+ } else if ( char === '\n' && !inQuotes ) {
132
+ lines.push( currentLine );
133
+ currentLine = '';
134
+ } else {
135
+ currentLine += char;
136
+ }
137
+ }
138
+
139
+ // Don't forget the last line
140
+ if ( currentLine !== '' ) {
141
+ lines.push( currentLine );
142
+ }
143
+
144
+ return lines;
145
+ },
146
+
147
+ // Parse a single line into values
148
+ parseLine: function ( line, options ) {
149
+ const values = [];
150
+ let currentValue = '';
151
+ let inQuotes = false;
152
+ const delimiter = options.delimiter;
153
+ const quoteChar = options.quoteChar;
154
+
155
+ for ( let i = 0; i < line.length; i += 1 ) {
156
+ const char = line[i];
157
+ const nextChar = line[i + 1];
158
+
159
+ if ( char === quoteChar ) {
160
+ if ( inQuotes && nextChar === quoteChar ) {
161
+ // Escaped quote
162
+ currentValue += quoteChar;
163
+ i += 1; // Skip next quote
164
+ } else if ( inQuotes && ( nextChar === delimiter || nextChar === undefined || i === line.length - 1 ) ) {
165
+ // End of quoted value
166
+ inQuotes = false;
167
+ } else if ( !inQuotes && ( currentValue === '' || currentValue === ' '.repeat( currentValue.length ) ) ) {
168
+ // Start of quoted value
169
+ inQuotes = true;
170
+ } else {
171
+ currentValue += char;
172
+ }
173
+ } else if ( char === delimiter && !inQuotes ) {
174
+ // End of value
175
+ values.push( currentValue.trim() );
176
+ currentValue = '';
177
+ } else {
178
+ currentValue += char;
179
+ }
180
+ }
181
+
182
+ // Don't forget the last value
183
+ values.push( currentValue.trim() );
184
+
185
+ return values;
186
+ },
187
+
188
+ // Convert string values to appropriate types
189
+ castValue: function ( value, options ) {
190
+ if ( value === '' || value === null || value === undefined ) {
191
+ return value;
192
+ }
193
+
194
+ // Boolean
195
+ if ( value.toLowerCase() === 'true' ) return true;
196
+ if ( value.toLowerCase() === 'false' ) return false;
197
+
198
+ // Number
199
+ if ( !isNaN( value ) && !isNaN( parseFloat( value ) ) ) {
200
+ return value.indexOf( '.' ) > -1 ? parseFloat( value ) : parseInt( value, 10 );
201
+ }
202
+
203
+ // Date (only if parseDates is true)
204
+ if ( options.parseDates && (/^\d{4}-\d{2}-\d{2}/).test( value ) ) {
205
+ const date = new Date( value );
206
+ if ( !isNaN( date.getTime() ) ) {
207
+ return date;
208
+ }
209
+ }
210
+
211
+ // String (default)
212
+ return value;
213
+ }
214
+ };
215
+
216
+ export default SimpleCSV;
@@ -0,0 +1,173 @@
1
+ // nodes/csv-source/test.js
2
+ import * as csvSource from './index.js';
3
+ import loadCSV from './load.js';
4
+
5
+ // Sample CSV data for testing
6
+ const sampleCSV = `timestamp,temperature,pressure,humidity,location
7
+ 2024-01-01T00:00:00,23.5,1013.25,65,sensor_01
8
+ 2024-01-01T00:00:10,23.7,1013.20,64,sensor_01
9
+ 2024-01-01T00:00:20,24.1,1013.15,63,sensor_01
10
+ 2024-01-01T00:00:30,24.5,1013.10,62,sensor_01
11
+ 2024-01-01T00:00:40,25.2,1013.05,61,sensor_01`;
12
+
13
+ // Test 1: Basic initialization and loading
14
+ console.log('=== Test 1: Basic CSV Loading ===');
15
+ const basicSpec = {
16
+ source: sampleCSV, // Embedded CSV data
17
+ mode: 'batch', // Get all at once
18
+ parseOptions: {
19
+ header: true,
20
+ dynamicTyping: true,
21
+ skipEmptyLines: true
22
+ }
23
+ };
24
+
25
+ const basicState = csvSource.init( basicSpec );
26
+ console.log( 'Initial state:', {
27
+ mode: basicState.mode,
28
+ isLoaded: basicState.isLoaded,
29
+ currentIndex: basicState.currentIndex
30
+ });
31
+
32
+ // Load the CSV data
33
+ await loadCSV( basicState );
34
+ console.log( `Loaded ${basicState.data.length} rows` );
35
+ console.log( 'First row:', basicState.data[0] );
36
+ console.log( 'Data types:', Object.entries( basicState.data[0] ).map(
37
+ ([ k, v ]) => `${k}: ${typeof v}`
38
+ ));
39
+
40
+ // Test 2: Message generation
41
+ console.log( '\n=== Test 2: Message Generation ===');
42
+ const msg1 = csvSource.update( basicState, {} );
43
+ console.log( 'Message 1:', msg1 );
44
+
45
+ const msg2 = csvSource.update( basicState, {} );
46
+ console.log( 'Message 2:', msg2 );
47
+
48
+ // Test 3: Stream mode with rate limiting
49
+ console.log( '\n=== Test 3: Stream Mode ===');
50
+ const streamSpec = {
51
+ source: sampleCSV,
52
+ mode: 'stream',
53
+ rate: 100, // 100 messages per second
54
+ loop: true,
55
+ parseOptions: {
56
+ header: true,
57
+ dynamicTyping: true
58
+ }
59
+ };
60
+
61
+ const streamState = csvSource.init( streamSpec );
62
+ await loadCSV( streamState );
63
+
64
+ // Simulate rapid updates - should be rate limited
65
+ const startTime = Date.now();
66
+ let messageCount = 0;
67
+
68
+ for ( let i = 0; i < 10; i += 1 ) {
69
+ const msg = csvSource.update( streamState, {} );
70
+ if ( msg ) {
71
+ messageCount += 1;
72
+ console.log( `Stream message ${messageCount}:`, msg._source );
73
+ }
74
+ // Small delay to show rate limiting
75
+ await new Promise( (resolve) => setTimeout( resolve, 5 ) );
76
+ }
77
+
78
+ const elapsed = Date.now() - startTime;
79
+ console.log( `Generated ${messageCount} messages in ${elapsed}ms` );
80
+ console.log( `Effective rate: ${(messageCount / elapsed * 1000).toFixed(1)} msg/sec` );
81
+
82
+ // Test 4: Loop behavior
83
+ console.log( '\n=== Test 4: Loop Behavior ===');
84
+ const loopState = csvSource.init({
85
+ source: sampleCSV,
86
+ mode: 'batch',
87
+ loop: true
88
+ });
89
+ await loadCSV( loopState );
90
+
91
+ // Read all messages twice to test looping
92
+ console.log( 'First pass:' );
93
+ for ( let i = 0; i < 5; i += 1 ) {
94
+ const msg = csvSource.update( loopState, {} );
95
+ console.log( ` Row ${i + 1}: temp=${msg.temperature}°C` );
96
+ }
97
+
98
+ console.log( 'Second pass (should loop):' );
99
+ for ( let i = 0; i < 3; i += 1 ) {
100
+ const msg = csvSource.update( loopState, {} );
101
+ console.log( ` Row ${i + 1}: temp=${msg.temperature}°C` );
102
+ }
103
+
104
+ // Test 5: Column mapping
105
+ console.log( '\n=== Test 5: Column Mapping ===');
106
+ const mappedState = csvSource.init({
107
+ source: sampleCSV,
108
+ columns: {
109
+ 'temperature': 'temp',
110
+ 'pressure': 'press',
111
+ 'location': 'id'
112
+ }
113
+ });
114
+ await loadCSV( mappedState );
115
+
116
+ const mappedMsg = csvSource.update( mappedState, {} );
117
+ console.log( 'Mapped message:', mappedMsg );
118
+ console.log( 'Should have: temp, press, id fields' );
119
+
120
+ // Test 6: CSV with different delimiter
121
+ console.log( '\n=== Test 6: Tab-Delimited CSV ===');
122
+ const tabCSV = `name age score
123
+ John Doe 30 95.5
124
+ Jane Smith 25 87.3`;
125
+
126
+ const tabState = csvSource.init({
127
+ source: tabCSV,
128
+ parseOptions: {
129
+ delimiter: '\t',
130
+ header: true,
131
+ dynamicTyping: true
132
+ }
133
+ });
134
+ await loadCSV( tabState );
135
+
136
+ const tabMsg = csvSource.update( tabState, {} );
137
+ console.log( 'Tab-delimited data:', tabMsg );
138
+
139
+ // Test 7: File loading simulation (Node.js only)
140
+ if ( typeof process === 'object' ) {
141
+ console.log( '\n=== Test 7: File Loading (Node.js) ===');
142
+
143
+ // Create a temporary CSV file
144
+ const fs = await import( 'fs/promises' );
145
+ const tempFile = './test-data.csv';
146
+ await fs.writeFile( tempFile, sampleCSV );
147
+
148
+ const fileState = csvSource.init({
149
+ source: tempFile,
150
+ mode: 'batch'
151
+ });
152
+
153
+ await loadCSV( fileState );
154
+ console.log( `Loaded ${fileState.data.length} rows from file` );
155
+
156
+ // Clean up
157
+ await fs.unlink( tempFile );
158
+ }
159
+
160
+ // Test 8: Reset functionality
161
+ console.log( '\n=== Test 8: Reset ===');
162
+ console.log( 'Before reset:', {
163
+ currentIndex: basicState.currentIndex,
164
+ isLoaded: basicState.isLoaded
165
+ });
166
+
167
+ csvSource.reset( basicState );
168
+ console.log( 'After reset:', {
169
+ currentIndex: basicState.currentIndex,
170
+ isLoaded: basicState.isLoaded
171
+ });
172
+
173
+ console.log( '\n✅ All tests completed!' );