@winkjs/composer 0.0.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +42 -103
  3. package/package.json +53 -29
  4. package/src/composer.js +75 -0
  5. package/src/core/batch/aoo-encoder.js +148 -0
  6. package/src/core/batch/index.js +12 -0
  7. package/src/core/batch/sliding-window.js +91 -0
  8. package/src/core/codec/index.js +137 -0
  9. package/src/core/emitter-manager/mqtt/constants.js +102 -0
  10. package/src/core/emitter-manager/mqtt/emitter.js +780 -0
  11. package/src/core/emitter-manager/mqtt/index.js +190 -0
  12. package/src/core/emitter-manager/mqtt/mqtt-store.js +538 -0
  13. package/src/core/emitter-manager/terminal/assert-columns.js +98 -0
  14. package/src/core/emitter-manager/terminal/formatters.js +160 -0
  15. package/src/core/emitter-manager/terminal/index.js +341 -0
  16. package/src/core/env-vars.js +245 -0
  17. package/src/core/mqtt-protocol.js +47 -0
  18. package/src/core/partition-manager/index.js +7 -0
  19. package/src/core/partition-manager/init.js +44 -0
  20. package/src/core/partition-manager/introspect.js +15 -0
  21. package/src/core/partition-manager/publish-to.js +6 -0
  22. package/src/core/partition-manager/recompute.js +5 -0
  23. package/src/core/partition-manager/reset.js +5 -0
  24. package/src/core/partition-manager/update.js +264 -0
  25. package/src/core/partition-manager/validate-flow.js +157 -0
  26. package/src/core/semantics/digest.js +128 -0
  27. package/src/core/semantics/index.js +83 -0
  28. package/src/core/semantics/loader.js +571 -0
  29. package/src/core/semantics/schemas/asset-class-schema.js +218 -0
  30. package/src/core/semantics/schemas/column-schema.js +651 -0
  31. package/src/core/semantics/schemas/enum-schema.js +153 -0
  32. package/src/core/semantics/schemas/index.js +51 -0
  33. package/src/core/semantics/warnings.js +141 -0
  34. package/src/core/shutdown-manager/index.js +289 -0
  35. package/src/core/source-manager/csv/index.js +171 -0
  36. package/src/core/source-manager/csv/start.js +456 -0
  37. package/src/core/source-manager/mqtt/client.js +406 -0
  38. package/src/core/source-manager/mqtt/constants.js +147 -0
  39. package/src/core/source-manager/mqtt/dedup.js +244 -0
  40. package/src/core/source-manager/mqtt/index.js +260 -0
  41. package/src/core/source-manager/mqtt/status.js +546 -0
  42. package/src/core/source-manager/test-harness/comparator.js +226 -0
  43. package/src/core/source-manager/test-harness/field-generator.js +85 -0
  44. package/src/core/source-manager/test-harness/fuzz.js +87 -0
  45. package/src/core/source-manager/test-harness/index.js +115 -0
  46. package/src/core/source-manager/test-harness/prng.js +57 -0
  47. package/src/core/source-manager/test-harness/start.js +232 -0
  48. package/src/core/source-manager/test-harness/validate.js +213 -0
  49. package/src/core/storage-manager/questdb/assert-columns.js +120 -0
  50. package/src/core/storage-manager/questdb/ensure-tables.js +187 -0
  51. package/src/core/storage-manager/questdb/index.js +1251 -0
  52. package/src/core/storage-manager/questdb/persist-plan.js +488 -0
  53. package/src/core/storage-manager/questdb/writers.js +142 -0
  54. package/src/core/test-utils/sealed-record.js +80 -0
  55. package/src/core/test-utils/soak-signature.js +149 -0
  56. package/src/core/test-utils/tcp-proxy.js +106 -0
  57. package/src/core/tunable/helpers.js +254 -0
  58. package/src/core/tunable/index.js +87 -0
  59. package/src/core/utils/console-formatter/index.js +207 -0
  60. package/src/core/utils/create-smart-store-as/index.js +98 -0
  61. package/src/core/utils/create-smart-store-as/sanitize-field.js +46 -0
  62. package/src/core/utils/create-smart-store-as/smart-truncate.js +93 -0
  63. package/src/core/utils/flow/index.js +5 -0
  64. package/src/core/utils/flow/make-collision-checker.js +23 -0
  65. package/src/core/utils/flow/to-kebab.js +20 -0
  66. package/src/core/utils/half-life/index.js +111 -0
  67. package/src/core/utils/node/disable.js +11 -0
  68. package/src/core/utils/node/enable.js +11 -0
  69. package/src/core/utils/node/execute-triggers.js +40 -0
  70. package/src/core/utils/node/index.js +10 -0
  71. package/src/core/utils/node/pause.js +11 -0
  72. package/src/core/utils/node/populate-predicate-inputs.js +14 -0
  73. package/src/core/utils/node/publish-nan.js +19 -0
  74. package/src/core/utils/node/reset-predicate-inputs.js +11 -0
  75. package/src/core/utils/node/unpause.js +11 -0
  76. package/src/core/utils/node/validate-spec.js +22 -0
  77. package/src/core/utils/options/resolve-field-keyed.js +115 -0
  78. package/src/core/utils/quantize/index.js +75 -0
  79. package/src/core/utils/stats/formulas.js +67 -0
  80. package/src/core/utils/stats/index.js +3 -0
  81. package/src/core/utils/validate/composer-validators.js +141 -0
  82. package/src/core/utils/validate/helpers.js +206 -0
  83. package/src/core/utils/validate/index.js +12 -0
  84. package/src/core/utils/validate/type-validators.js +538 -0
  85. package/src/core/utils/validate/validate.js +107 -0
  86. package/src/core/utils/validate/validators.js +177 -0
  87. package/src/core/wiring/assert-handle.js +57 -0
  88. package/src/core/wiring/assert-module.js +63 -0
  89. package/src/core/wiring/index.js +5 -0
  90. package/src/core/wiring/node-type-to-module.js +17 -0
  91. package/src/core/wiring/wire-emitters.js +256 -0
  92. package/src/core/wiring/wire-linear-graph.js +25 -0
  93. package/src/core/wiring/wire-node.js +157 -0
  94. package/src/core/wiring/wire-semantics.js +173 -0
  95. package/src/core/wiring/wire-storages.js +344 -0
  96. package/src/flow/check-output-collisions.js +93 -0
  97. package/src/flow/consts.js +9 -0
  98. package/src/flow/deep-clone-spec.js +64 -0
  99. package/src/flow/driver.js +171 -0
  100. package/src/flow/expand-single-input-array.js +76 -0
  101. package/src/flow/expand-template-spec.js +93 -0
  102. package/src/flow/flow-api-schemas.js +300 -0
  103. package/src/flow/flow.js +658 -0
  104. package/src/flow/for-each.js +225 -0
  105. package/src/flow/get-signature-pattern.js +31 -0
  106. package/src/flow/get-target-array.js +46 -0
  107. package/src/flow/inspect.js +67 -0
  108. package/src/flow/load-node-module.js +81 -0
  109. package/src/flow/make-node-method.js +112 -0
  110. package/src/flow/resolve-pick-by-field.js +77 -0
  111. package/src/flow/resolve-tunables.js +76 -0
  112. package/src/flow/run.js +392 -0
  113. package/src/flow/serialize.js +161 -0
  114. package/src/flow/signatures-schema.js +222 -0
  115. package/src/flow/spec-builder.js +65 -0
  116. package/src/flow/validate-triggers.js +250 -0
  117. package/src/flow/validate.js +261 -0
  118. package/src/nodes/accumulate/index.js +21 -0
  119. package/src/nodes/accumulate/init.js +48 -0
  120. package/src/nodes/accumulate/introspect.js +123 -0
  121. package/src/nodes/accumulate/publish-to.js +34 -0
  122. package/src/nodes/accumulate/recompute.js +27 -0
  123. package/src/nodes/accumulate/reset.js +25 -0
  124. package/src/nodes/accumulate/update.js +42 -0
  125. package/src/nodes/appraise/calibrate.js +64 -0
  126. package/src/nodes/appraise/decision.js +66 -0
  127. package/src/nodes/appraise/deviation.js +140 -0
  128. package/src/nodes/appraise/index.js +23 -0
  129. package/src/nodes/appraise/init.js +165 -0
  130. package/src/nodes/appraise/integrate.js +45 -0
  131. package/src/nodes/appraise/introspect.js +352 -0
  132. package/src/nodes/appraise/publish-to.js +93 -0
  133. package/src/nodes/appraise/receptor.js +69 -0
  134. package/src/nodes/appraise/recompute.js +79 -0
  135. package/src/nodes/appraise/reset.js +53 -0
  136. package/src/nodes/appraise/update.js +130 -0
  137. package/src/nodes/archive/csv/index.js +6 -0
  138. package/src/nodes/archive/csv/init.js +36 -0
  139. package/src/nodes/archive/csv/load.js +70 -0
  140. package/src/nodes/archive/csv/reset.js +14 -0
  141. package/src/nodes/archive/csv/simple-parser.js +216 -0
  142. package/src/nodes/archive/csv/test.js +173 -0
  143. package/src/nodes/archive/csv/update.js +63 -0
  144. package/src/nodes/butterworth-filter/index.js +18 -0
  145. package/src/nodes/butterworth-filter/init.js +184 -0
  146. package/src/nodes/butterworth-filter/introspect.js +228 -0
  147. package/src/nodes/butterworth-filter/publish-to.js +21 -0
  148. package/src/nodes/butterworth-filter/recompute.js +13 -0
  149. package/src/nodes/butterworth-filter/reset.js +30 -0
  150. package/src/nodes/butterworth-filter/update.js +38 -0
  151. package/src/nodes/categorize/index.js +19 -0
  152. package/src/nodes/categorize/init.js +58 -0
  153. package/src/nodes/categorize/introspect.js +177 -0
  154. package/src/nodes/categorize/publish-to.js +29 -0
  155. package/src/nodes/categorize/recompute.js +14 -0
  156. package/src/nodes/categorize/reset.js +16 -0
  157. package/src/nodes/categorize/update.js +53 -0
  158. package/src/nodes/controller/index.js +9 -0
  159. package/src/nodes/controller/init.js +55 -0
  160. package/src/nodes/controller/introspect.js +91 -0
  161. package/src/nodes/controller/publish-to.js +8 -0
  162. package/src/nodes/controller/recompute.js +9 -0
  163. package/src/nodes/controller/reset.js +17 -0
  164. package/src/nodes/controller/update.js +63 -0
  165. package/src/nodes/diff/index.js +19 -0
  166. package/src/nodes/diff/init.js +37 -0
  167. package/src/nodes/diff/introspect.js +132 -0
  168. package/src/nodes/diff/publish-to.js +21 -0
  169. package/src/nodes/diff/recompute.js +12 -0
  170. package/src/nodes/diff/reset.js +12 -0
  171. package/src/nodes/diff/update.js +37 -0
  172. package/src/nodes/digest-moments/formulas.js +12 -0
  173. package/src/nodes/digest-moments/index.js +14 -0
  174. package/src/nodes/digest-moments/init.js +67 -0
  175. package/src/nodes/digest-moments/introspect.js +154 -0
  176. package/src/nodes/digest-moments/publish-to.js +34 -0
  177. package/src/nodes/digest-moments/recompute.js +14 -0
  178. package/src/nodes/digest-moments/reset.js +14 -0
  179. package/src/nodes/digest-moments/update.js +120 -0
  180. package/src/nodes/dwell-time-tracker/index.js +14 -0
  181. package/src/nodes/dwell-time-tracker/init.js +59 -0
  182. package/src/nodes/dwell-time-tracker/introspect.js +137 -0
  183. package/src/nodes/dwell-time-tracker/publish-to.js +22 -0
  184. package/src/nodes/dwell-time-tracker/recompute.js +8 -0
  185. package/src/nodes/dwell-time-tracker/reset.js +23 -0
  186. package/src/nodes/dwell-time-tracker/update.js +103 -0
  187. package/src/nodes/emit-if/helpers.js +94 -0
  188. package/src/nodes/emit-if/index.js +16 -0
  189. package/src/nodes/emit-if/init.js +67 -0
  190. package/src/nodes/emit-if/introspect.js +102 -0
  191. package/src/nodes/emit-if/publish-to.js +13 -0
  192. package/src/nodes/emit-if/recompute.js +12 -0
  193. package/src/nodes/emit-if/reset.js +27 -0
  194. package/src/nodes/emit-if/update.js +93 -0
  195. package/src/nodes/es-correlation/index.js +19 -0
  196. package/src/nodes/es-correlation/init.js +73 -0
  197. package/src/nodes/es-correlation/introspect.js +176 -0
  198. package/src/nodes/es-correlation/publish-to.js +32 -0
  199. package/src/nodes/es-correlation/recompute.js +56 -0
  200. package/src/nodes/es-correlation/reset.js +27 -0
  201. package/src/nodes/es-correlation/update.js +110 -0
  202. package/src/nodes/es-mean/index.js +20 -0
  203. package/src/nodes/es-mean/init.js +58 -0
  204. package/src/nodes/es-mean/introspect.js +129 -0
  205. package/src/nodes/es-mean/publish-to.js +19 -0
  206. package/src/nodes/es-mean/recompute.js +16 -0
  207. package/src/nodes/es-mean/reset.js +24 -0
  208. package/src/nodes/es-mean/update.js +69 -0
  209. package/src/nodes/es-pairwise-correlation/index.js +19 -0
  210. package/src/nodes/es-pairwise-correlation/init.js +98 -0
  211. package/src/nodes/es-pairwise-correlation/introspect.js +187 -0
  212. package/src/nodes/es-pairwise-correlation/publish-to.js +31 -0
  213. package/src/nodes/es-pairwise-correlation/recompute.js +69 -0
  214. package/src/nodes/es-pairwise-correlation/reset.js +29 -0
  215. package/src/nodes/es-pairwise-correlation/update.js +89 -0
  216. package/src/nodes/es-stats/index.js +21 -0
  217. package/src/nodes/es-stats/init.js +105 -0
  218. package/src/nodes/es-stats/introspect.js +157 -0
  219. package/src/nodes/es-stats/publish-to.js +42 -0
  220. package/src/nodes/es-stats/recompute.js +74 -0
  221. package/src/nodes/es-stats/reset.js +49 -0
  222. package/src/nodes/es-stats/update-envelope.js +44 -0
  223. package/src/nodes/es-stats/update-welford.js +64 -0
  224. package/src/nodes/es-stats/update.js +59 -0
  225. package/src/nodes/index.js +83 -0
  226. package/src/nodes/invert-flag/index.js +20 -0
  227. package/src/nodes/invert-flag/init.js +36 -0
  228. package/src/nodes/invert-flag/introspect.js +115 -0
  229. package/src/nodes/invert-flag/publish-to.js +23 -0
  230. package/src/nodes/invert-flag/recompute.js +12 -0
  231. package/src/nodes/invert-flag/reset.js +12 -0
  232. package/src/nodes/invert-flag/update.js +32 -0
  233. package/src/nodes/kalman1d/index.js +20 -0
  234. package/src/nodes/kalman1d/init.js +103 -0
  235. package/src/nodes/kalman1d/introspect.js +250 -0
  236. package/src/nodes/kalman1d/publish-to.js +34 -0
  237. package/src/nodes/kalman1d/recompute.js +23 -0
  238. package/src/nodes/kalman1d/reset.js +29 -0
  239. package/src/nodes/kalman1d/update.js +144 -0
  240. package/src/nodes/kernel/index.js +18 -0
  241. package/src/nodes/kernel/init.js +66 -0
  242. package/src/nodes/kernel/introspect.js +163 -0
  243. package/src/nodes/kernel/presets.js +74 -0
  244. package/src/nodes/kernel/publish-to.js +25 -0
  245. package/src/nodes/kernel/recompute.js +14 -0
  246. package/src/nodes/kernel/reset.js +20 -0
  247. package/src/nodes/kernel/update.js +69 -0
  248. package/src/nodes/lag/index.js +21 -0
  249. package/src/nodes/lag/init.js +87 -0
  250. package/src/nodes/lag/introspect.js +202 -0
  251. package/src/nodes/lag/publish-to.js +64 -0
  252. package/src/nodes/lag/recompute.js +33 -0
  253. package/src/nodes/lag/reset.js +49 -0
  254. package/src/nodes/lag/update.js +133 -0
  255. package/src/nodes/median3/index.js +19 -0
  256. package/src/nodes/median3/init.js +40 -0
  257. package/src/nodes/median3/introspect.js +116 -0
  258. package/src/nodes/median3/publish-to.js +21 -0
  259. package/src/nodes/median3/recompute.js +12 -0
  260. package/src/nodes/median3/reset.js +15 -0
  261. package/src/nodes/median3/update.js +58 -0
  262. package/src/nodes/moments-digest/copy-from.js +12 -0
  263. package/src/nodes/moments-digest/flush.js +17 -0
  264. package/src/nodes/moments-digest/index.js +132 -0
  265. package/src/nodes/moments-digest/init.js +84 -0
  266. package/src/nodes/moments-digest/introspect.js +122 -0
  267. package/src/nodes/moments-digest/prelude.js +28 -0
  268. package/src/nodes/moments-digest/publish-to.js +31 -0
  269. package/src/nodes/moments-digest/recompute.js +9 -0
  270. package/src/nodes/moments-digest/reset.js +20 -0
  271. package/src/nodes/moments-digest/update.js +182 -0
  272. package/src/nodes/node-names.js +67 -0
  273. package/src/nodes/page-hinkley/index.js +20 -0
  274. package/src/nodes/page-hinkley/init.js +66 -0
  275. package/src/nodes/page-hinkley/introspect.js +193 -0
  276. package/src/nodes/page-hinkley/publish-to.js +28 -0
  277. package/src/nodes/page-hinkley/recompute.js +14 -0
  278. package/src/nodes/page-hinkley/reset.js +23 -0
  279. package/src/nodes/page-hinkley/update.js +105 -0
  280. package/src/nodes/pass-if/index.js +21 -0
  281. package/src/nodes/pass-if/init.js +37 -0
  282. package/src/nodes/pass-if/introspect.js +93 -0
  283. package/src/nodes/pass-if/publish-to.js +15 -0
  284. package/src/nodes/pass-if/recompute.js +15 -0
  285. package/src/nodes/pass-if/reset.js +18 -0
  286. package/src/nodes/pass-if/update.js +37 -0
  287. package/src/nodes/persist-if/helpers.js +111 -0
  288. package/src/nodes/persist-if/index.js +9 -0
  289. package/src/nodes/persist-if/init.js +98 -0
  290. package/src/nodes/persist-if/introspect.js +106 -0
  291. package/src/nodes/persist-if/publish-to.js +21 -0
  292. package/src/nodes/persist-if/recompute.js +20 -0
  293. package/src/nodes/persist-if/reset.js +35 -0
  294. package/src/nodes/persist-if/update.js +112 -0
  295. package/src/nodes/persistence-check/index.js +14 -0
  296. package/src/nodes/persistence-check/init.js +38 -0
  297. package/src/nodes/persistence-check/introspect.js +144 -0
  298. package/src/nodes/persistence-check/publish-to.js +11 -0
  299. package/src/nodes/persistence-check/recompute.js +7 -0
  300. package/src/nodes/persistence-check/reset.js +12 -0
  301. package/src/nodes/persistence-check/update.js +57 -0
  302. package/src/nodes/process-index/index.js +15 -0
  303. package/src/nodes/process-index/init.js +48 -0
  304. package/src/nodes/process-index/introspect.js +182 -0
  305. package/src/nodes/process-index/publish-to.js +29 -0
  306. package/src/nodes/process-index/recompute.js +8 -0
  307. package/src/nodes/process-index/reset.js +8 -0
  308. package/src/nodes/process-index/update.js +75 -0
  309. package/src/nodes/ratio/index.js +14 -0
  310. package/src/nodes/ratio/init.js +37 -0
  311. package/src/nodes/ratio/introspect.js +155 -0
  312. package/src/nodes/ratio/publish-to.js +15 -0
  313. package/src/nodes/ratio/recompute.js +7 -0
  314. package/src/nodes/ratio/reset.js +7 -0
  315. package/src/nodes/ratio/update.js +47 -0
  316. package/src/nodes/sanitize/index.js +82 -0
  317. package/src/nodes/sanitize/init.js +82 -0
  318. package/src/nodes/sanitize/introspect.js +194 -0
  319. package/src/nodes/sanitize/publish-to.js +15 -0
  320. package/src/nodes/sanitize/recompute.js +8 -0
  321. package/src/nodes/sanitize/reset.js +14 -0
  322. package/src/nodes/sanitize/update.js +102 -0
  323. package/src/nodes/spike-guard/index.js +14 -0
  324. package/src/nodes/spike-guard/init.js +47 -0
  325. package/src/nodes/spike-guard/introspect.js +130 -0
  326. package/src/nodes/spike-guard/publish-to.js +35 -0
  327. package/src/nodes/spike-guard/recompute.js +14 -0
  328. package/src/nodes/spike-guard/reset.js +19 -0
  329. package/src/nodes/spike-guard/update.js +89 -0
  330. package/src/nodes/sse-emitter/index.js +7 -0
  331. package/src/nodes/sse-emitter/init.js +105 -0
  332. package/src/nodes/sse-emitter/introspect.js +134 -0
  333. package/src/nodes/sse-emitter/publish-to.js +6 -0
  334. package/src/nodes/sse-emitter/recompute.js +16 -0
  335. package/src/nodes/sse-emitter/reset.js +19 -0
  336. package/src/nodes/sse-emitter/update.js +69 -0
  337. package/src/nodes/sse-emitter/utils/broadcast-heartbeat.js +36 -0
  338. package/src/nodes/sse-emitter/utils/build-message.js +31 -0
  339. package/src/nodes/sse-emitter/utils/connection-manager.js +45 -0
  340. package/src/nodes/sse-emitter/utils/endpoint-initializer.js +20 -0
  341. package/src/nodes/sse-emitter/utils/health-monitor.js +38 -0
  342. package/src/nodes/sse-emitter/utils/qos-handler.js +41 -0
  343. package/src/nodes/state-change-detector/helpers.js +35 -0
  344. package/src/nodes/state-change-detector/index.js +12 -0
  345. package/src/nodes/state-change-detector/init.js +56 -0
  346. package/src/nodes/state-change-detector/introspect.js +146 -0
  347. package/src/nodes/state-change-detector/publish-to.js +20 -0
  348. package/src/nodes/state-change-detector/recompute.js +6 -0
  349. package/src/nodes/state-change-detector/reset.js +20 -0
  350. package/src/nodes/state-change-detector/update.js +97 -0
  351. package/src/nodes/sw-stats/index.js +14 -0
  352. package/src/nodes/sw-stats/init.js +51 -0
  353. package/src/nodes/sw-stats/introspect.js +131 -0
  354. package/src/nodes/sw-stats/publish-to.js +45 -0
  355. package/src/nodes/sw-stats/recompute.js +33 -0
  356. package/src/nodes/sw-stats/reset.js +15 -0
  357. package/src/nodes/sw-stats/update.js +39 -0
  358. package/src/nodes/swing-watch/index.js +17 -0
  359. package/src/nodes/swing-watch/init.js +144 -0
  360. package/src/nodes/swing-watch/introspect.js +188 -0
  361. package/src/nodes/swing-watch/publish-to.js +53 -0
  362. package/src/nodes/swing-watch/recompute.js +22 -0
  363. package/src/nodes/swing-watch/reset.js +55 -0
  364. package/src/nodes/swing-watch/update.js +767 -0
  365. package/src/nodes/tally/index.js +15 -0
  366. package/src/nodes/tally/init.js +32 -0
  367. package/src/nodes/tally/introspect.js +116 -0
  368. package/src/nodes/tally/publish-to.js +28 -0
  369. package/src/nodes/tally/recompute.js +10 -0
  370. package/src/nodes/tally/reset.js +10 -0
  371. package/src/nodes/tally/update.js +44 -0
  372. package/src/nodes/threshold/index.js +14 -0
  373. package/src/nodes/threshold/init.js +121 -0
  374. package/src/nodes/threshold/introspect.js +192 -0
  375. package/src/nodes/threshold/publish-to.js +11 -0
  376. package/src/nodes/threshold/recompute.js +8 -0
  377. package/src/nodes/threshold/reset.js +14 -0
  378. package/src/nodes/threshold/update.js +54 -0
  379. package/src/nodes/transform/helpers.js +39 -0
  380. package/src/nodes/transform/index.js +15 -0
  381. package/src/nodes/transform/init.js +35 -0
  382. package/src/nodes/transform/introspect.js +118 -0
  383. package/src/nodes/transform/publish-to.js +23 -0
  384. package/src/nodes/transform/recompute.js +12 -0
  385. package/src/nodes/transform/reset.js +11 -0
  386. package/src/nodes/transform/update.js +32 -0
  387. package/src/nodes/trend/compute-confidence.js +112 -0
  388. package/src/nodes/trend/index.js +14 -0
  389. package/src/nodes/trend/init.js +81 -0
  390. package/src/nodes/trend/introspect.js +151 -0
  391. package/src/nodes/trend/publish-to.js +22 -0
  392. package/src/nodes/trend/recompute.js +15 -0
  393. package/src/nodes/trend/reset.js +26 -0
  394. package/src/nodes/trend/update.js +93 -0
  395. package/src/nodes/tw-stats/copy-from.js +16 -0
  396. package/src/nodes/tw-stats/flush.js +12 -0
  397. package/src/nodes/tw-stats/index.js +41 -0
  398. package/src/nodes/tw-stats/init.js +92 -0
  399. package/src/nodes/tw-stats/introspect.js +171 -0
  400. package/src/nodes/tw-stats/publish-to.js +108 -0
  401. package/src/nodes/tw-stats/recompute.js +9 -0
  402. package/src/nodes/tw-stats/reset.js +16 -0
  403. package/src/nodes/tw-stats/update.js +113 -0
  404. package/src/nodes/unbalance/index.js +15 -0
  405. package/src/nodes/unbalance/init.js +59 -0
  406. package/src/nodes/unbalance/introspect.js +167 -0
  407. package/src/nodes/unbalance/publish-to.js +52 -0
  408. package/src/nodes/unbalance/recompute.js +11 -0
  409. package/src/nodes/unbalance/reset.js +10 -0
  410. package/src/nodes/unbalance/update.js +83 -0
  411. package/src/nodes/vector-distance/index.js +14 -0
  412. package/src/nodes/vector-distance/init.js +54 -0
  413. package/src/nodes/vector-distance/introspect.js +149 -0
  414. package/src/nodes/vector-distance/publish-to.js +35 -0
  415. package/src/nodes/vector-distance/recompute.js +11 -0
  416. package/src/nodes/vector-distance/reset.js +20 -0
  417. package/src/nodes/vector-distance/update.js +139 -0
  418. package/src/nodes/winnow/index.js +12 -0
  419. package/src/nodes/winnow/init.js +82 -0
  420. package/src/nodes/winnow/introspect.js +253 -0
  421. package/src/nodes/winnow/publish-to.js +51 -0
  422. package/src/nodes/winnow/recompute.js +14 -0
  423. package/src/nodes/winnow/reset.js +34 -0
  424. package/src/nodes/winnow/update.js +203 -0
  425. package/src/tools/training/features.js +227 -0
  426. package/src/tools/training/index.js +21 -0
  427. package/src/tools/training/logistic.js +238 -0
  428. package/src/tools/training/metrics.js +126 -0
  429. package/src/tools/training/scale.js +239 -0
  430. package/src/tools/training/validate.js +103 -0
  431. package/src/windowing/count-sliding/create.js +16 -0
  432. package/src/windowing/count-sliding/get-reverse-chronological-index.js +19 -0
  433. package/src/windowing/count-sliding/index.js +6 -0
  434. package/src/windowing/count-sliding/is-not-full.js +5 -0
  435. package/src/windowing/count-sliding/push.js +15 -0
  436. package/src/windowing/count-sliding/reset.js +11 -0
  437. package/src/windowing/count-sliding/size.js +5 -0
  438. package/src/windowing/time-sliding/create.js +25 -0
  439. package/src/windowing/time-sliding/push.js +42 -0
  440. package/CODE_OF_CONDUCT.md +0 -46
  441. package/CONTRIBUTING.md +0 -132
  442. package/docs/.nojekyll +0 -0
  443. package/docs/CONTRIBUTING.md +0 -134
  444. package/docs/README.md +0 -132
  445. package/docs/_coverpage.md +0 -26
  446. package/docs/_media/bg.png +0 -0
  447. package/docs/_media/wink-logo.png +0 -0
  448. package/docs/_navbar.md +0 -14
  449. package/docs/_sidebar.md +0 -2
  450. package/docs/index.html +0 -44
  451. package/eslint.config.js +0 -283
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License
2
2
 
3
- Copyright (c) 2024-25 GRAYPE Systems Private Limited
3
+ Copyright (c) 2024-26 GRAYPE Systems Private Limited
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
package/README.md CHANGED
@@ -1,132 +1,71 @@
1
- # wink-composer ♫
1
+ # winkComposer
2
2
 
3
- ### [![Stability](https://img.shields.io/badge/stability-1--experimental-orange.svg)](https://nodejs.org/api/documentation.html#documentation_stability_index)
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
 
5
- ## ✨ Real-time analytics that sparks joy
5
+ ## Composable Streaming Intelligence
6
+ [<img align="right" src="https://decisively.github.io/wink-logos/logo-title.png" width="100px" >](https://winkjs.org/)
6
7
 
7
- [<img align="right" src="https://decisively.github.io/wink-logos/logo-title.png" width="100px" >](http://winkjs.org/)
8
+ A sensor spikes. An engine runs hot. A building's energy use surges. A server degrades. A cold chain breaks.
8
9
 
9
- WinkComposer is an upcoming open-source framework that makes building streaming intelligence playfully simple. Connect intuitive nodes, experiment freely, perfect it in an iteration or two. From edge-ready AI algorithms to built-in resilience, everything you need for production comes standard. No complexity, no black boxes - just the joy of creating exactly what you need.
10
+ winkComposer turns live data streams into clear, actionable insights by composing small, focused building blocks — signal conditioning, anomaly detection, and health assessment — into pipelines underpinned by neural-network intelligence.
10
11
 
11
- ## Why Composability Matters
12
+ A high-performance JavaScript framework for IIoT and beyond. Runs on a Raspberry Pi or a production server or k8 cluster on cloud. Purpose-built for SMBs and MSMEs. Integrates with QuestDB, Grafana, and Mosquitto — open source, end to end.
12
13
 
13
- **🎯 Create What You Need** — With composability, you're the inventor: Correlate vibration with weather? Done. Detect micro-trends before they become macro? Built.
14
+ winkComposer calls its building blocks nodes — each with a single responsibility, wired through a declarative flow language. Small vocabulary, unbounded composition — the same nodes that detect bearing wear also catch server latency degradation and process yield drift.
14
15
 
15
- **💫 Countless Possibilities** — Dozens of nodes → Endless pipelines. Like few music notes creating captivating symphonies.
16
+ > [!NOTE]
17
+ > The [documentation site](https://composer.winkjs.org) is live and growing — interactive demos run real winkComposer nodes in your browser, no installation or sign-up required. The full source now lives in this repository, MIT-licensed. MCP integration for AI-driven queries over pre-computed insights is in active development.
16
18
 
17
- **🌱 Evolve Naturally** — Start: `sensor → threshold → alert`. Grow: Add intelligence: `sensor → normalize → smooth → detect → confirm → alert`. Unconstrained by vendor feature lists.
18
19
 
19
- **🔍 Total Transparency** — See which node triggered, what threshold crossed, why it alerted. Every decision is explainable.
20
+ ## Explore Possibilities
20
21
 
21
- ## Build Exactly What You Need
22
+ Real data. Real insight. Each demo runs the same winkComposer core that powers edge-to-cloud deployments — right here in your browser.
22
23
 
23
- Build precise analytics for your unique patterns:
24
+ | [![Bearing Health](https://composer.winkjs.org/readme-bearing-health.png)](https://composer.winkjs.org/docs/use-cases/bearing-health) | [![Server Health](https://composer.winkjs.org/readme-server-health.png)](https://composer.winkjs.org/docs/use-cases/server-health) | [![Process Quality](https://composer.winkjs.org/readme-process-quality.png)](https://composer.winkjs.org/docs/use-cases/process-quality) |
25
+ |:---:|:---:|:---:|
26
+ | [**Detecting Bearing Failure**](https://composer.winkjs.org/docs/use-cases/bearing-health) | [**Detecting Server Latency Degradation**](https://composer.winkjs.org/docs/use-cases/server-health) | [**Catching Process Drift**](https://composer.winkjs.org/docs/use-cases/process-quality) |
27
+ | Predictive Maintenance | AIOps | Process Control |
24
28
 
25
- ```javascript
26
- // Detect bearing failure in rotating equipment
27
- vibration → isolateSignal → extractPeaks → measureIntensity →
28
- trackChanges → triggerAlert
29
29
 
30
- // Monitor chemical process quality
31
- temperature → removeNoise → smoothFast → smoothSlow → findDivergence →
32
- detectChange → waitForPersistence
30
+ ## Documentation
33
31
 
34
- // Find correlated sensor behaviors
35
- [sensor1, sensor2] → trackCorrelation → compareToNormal → raiseFlag
32
+ | Resource | What it covers |
33
+ |---|---|
34
+ | [**Hello Flow!**](https://composer.winkjs.org/docs/playground/hello-flow) | Build a 4-node temperature monitor from scratch — smooth, detect, confirm, broadcast — with an interactive demo running real nodes in your browser. The natural starting point. |
35
+ | [**Recipes**](https://composer.winkjs.org/docs/playground/recipes) | Focused, runnable patterns for common detection problems: gradual drift (fast/slow esMean crossover with Page-Hinkley), sudden shifts (Kalman filter), sensor freeze (collapsed standard deviation), and subtle process shifts (Western Electric run rules). Each runs in the browser. |
36
+ | [**Explore Nodes**](https://composer.winkjs.org/docs/playground/explore-nodes) | Single-node sandboxes — drag a slider, watch the node respond in real time. Covers the Kalman 1D filter and the kernel convolution node, with more to come. |
37
+ | [**Under the Hood**](https://composer.winkjs.org/docs/concepts/under-the-hood) | How messages flow and get enriched node by node, how bad data and throwing functions are handled without crashing the pipeline, how per-asset isolation works, and timestamp requirements. |
38
+ | [**Flow Language**](https://composer.winkjs.org/docs/concepts/flow-language) | The complete DSL reference — flow anatomy, node call signatures, dynamic options (tunables), single vs. multi-field processing, naming policies, and node processing types. |
39
+ | [**Composition Patterns**](https://composer.winkjs.org/docs/concepts/composition-patterns) | Proven node combinations for recurring problems: noise-tolerant alarms, drift detection, adaptive diagnostics, layered flows, and downsampling for storage. Includes clear guidance on when to use passIf, emitIf, or controller/disable. |
40
+ | [**Semantics**](https://composer.winkjs.org/docs/concepts/semantics) | How to define what computed values mean — types, units, physical ranges, operational limits — as a single source of truth shared by storage, dashboards, and query engines. Covers the facts-vs-decisions design principle. |
41
+ | [**Node Index**](https://composer.winkjs.org/docs/reference/node-index) | Every node grouped by category — Signal Conditioning, Detection, Feature Extraction, Intelligence, and more — with what each computes and what it adds to the message. |
36
42
 
37
- // Calculate real-time OEE
38
- machine → trackState → sumTime → countOutput →
39
- checkQuality → calculateScore
40
- ```
41
43
 
42
- Each node is simple:
43
- - `removeNoise`: Median of last few values (removes sensor spikes).
44
- - `smoothFast`: Average of last 10 values (recent trend).
45
- - `smoothSlow`: Average of last 100 values (baseline trend).
46
- - `findDivergence`: Fast trend - slow trend (unusual behavior?).
47
- - `trackCorrelation`: Smart correlation (learns relationships between sensors)
44
+ ## Performance
48
45
 
49
- _Node names and details are illustrative - actual implementations may vary._
46
+ A pure compute benchmark — every step a live message takes, from arrival through all 8 nodes to final computed output, with no I/O. Asset pipelines are created dynamically as each new asset is first encountered; the benchmark runs 10 such pipelines concurrently, interleaved in random order to reflect real multi-asset deployment. Measured with `process.hrtime.bigint()` across 4.5 million messages (10 pipelines × 900 data points × 500 iterations). Storage and MQTT I/O are excluded.
50
47
 
51
- Every arrow is a conscious choice. Every node has a purpose. No magic, just simple intuitive math.
48
+ | Configuration | Throughput |
49
+ |----------------------|-------------------------|
50
+ | Raspberry Pi 5 | ~100K messages/second |
51
+ | Modern server | ~1.2M messages/second |
52
+ | Tracking 200K assets | ~300K messages/second |
52
53
 
54
+ The same pipeline [runs in your browser](https://composer.winkjs.org/docs/benchmark) — browser results are typically 30–60% of native Node.js throughput due to JIT differences.
53
55
 
54
- ## Your Analytics Journey
55
56
 
56
- **Start Simple**
57
- `sensor → threshold → alert`
57
+ ## Built for the Real World
58
58
 
59
- **Evolve Naturally**
60
- `sensor → validate → normalize → threshold → debounce → alert`
59
+ Each asset runs in its own isolated state — a fault in one never affects another. Messages queue locally when the network drops and drain cleanly on reconnect. The same pipeline code runs unchanged from edge to cloud. Shutdown is ordered and deterministic: sources close first, storage last, with no data corruption.
61
60
 
62
- **Scale Intelligently**
63
- `sensor → [parallel: statistics | patterns | anomalies] → correlate → decide → act`
64
61
 
65
- Your analytics grow with your understanding, and are never limited by any tool's feature list.
66
-
67
- ## Designed for the Real World
68
-
69
- ### Industrial IoT
70
- From simple threshold monitoring to complex predictive maintenance - compose what fits your equipment, your patterns, your physics. **Move intelligence to the edge** with analytics that run on industrial Raspberry Pis. Works alongside your existing systems to handle your unique requirements.
71
-
72
- ### Financial Services
73
- Build fraud detection you can explain to auditors. Every decision traceable, every threshold justified.
74
-
75
- ### Smart Infrastructure
76
- Energy optimization with transparent logic. See why the system made each decision.
77
-
78
- ## Production-Grade Resilience
79
- Real streams have noise. Sensors fail. Networks hiccup. WinkComposer handles it all:
80
-
81
- - **Node-level fault isolation** - One bad reading doesn't crash your pipeline
82
- - **Automatic state recovery** - Snapshot and restore without missing a beat
83
- - **Built-in observability** - Know what's happening in every node, every partition
84
- - **Adaptive execution** - Maintains responsiveness under varying load
85
-
86
-
87
- ## Core Principles
88
-
89
- | Principle | What it means |
90
- |-----------|---------------|
91
- | **Atomic Operations** | Each node does one thing perfectly |
92
- | **Pure Functions** | Predictable, testable, composable |
93
- | **Transparent Logic** | Understand every decision |
94
- | **Production Ready** | Built for high-throughput streaming with microsecond-scale latency |
95
- | **AI-Native Intelligence** | From statistical learning nodes to LLM reasoning - intelligence at every level |
96
- | **Built for Reality** | Node-level fault isolation, automatic recovery, and comprehensive observability |
97
-
98
- ## What's Coming
99
-
100
- ### 🎉 **Opening Our Doors in 2025**
101
- WinkComposer is transitioning to open source development in 2025. Join the [early conversation](https://github.com/winkjs/composer/discussions) and help shape the future of composable streaming analytics. You can even reach out directly at wink@graype.in.
102
-
103
- **Together, we're building:**
104
-
105
- - **Rich Node Library**: Statistical, filtering, detection, transformation, and control nodes
106
- - **Edge-Ready Intelligence**: Advanced algorithms like smart correlation, change point detection, and regression inference - AI-like capabilities without heavyweight frameworks
107
- - **Pipeline Builder**: Define your analytics flows declaratively (visual tools planned)
108
- - **Edge-Ready Performance**: Process data efficiently, even on resource-constrained devices (**tested on Raspberry Pi**)
109
- - **Knowledge Graph Integration**: Build digital twins with Neo4j/ArangoDB for contextual intelligence
110
- - **AI-Native Reasoning**: When patterns emerge, LLMs explain why. When anomalies occur, get actionable recommendations. Open-source models keep intelligence at the edge.
111
-
112
-
113
- ## The Vision
114
-
115
- Finally, build solutions that fit your exact problems, not the other way around.
116
-
117
- Every industry has unique patterns. Every process has specific physics. Every business has particular rules. WinkComposer gives you the building blocks to create exactly what you need.
118
-
119
- Domain-specific platforms excel at standard use cases. WinkComposer shines when you need something unique, beyond standard use cases. Standard use cases always included - they're just the beginning of what's possible!
62
+ ## Get Involved
120
63
 
64
+ | [**Star winkComposer**](https://github.com/winkjs/composer) | [**Follow @winkjs**](https://github.com/winkjs) | [**Discussions**](https://github.com/winkjs/composer/discussions) |
65
+ |:---:|:---:|:---:|
66
+ | Support open-source streaming intelligence. | Stay updated on releases and ecosystem developments. | Questions, ideas, or feedback — all welcome. |
121
67
 
122
68
 
123
69
  ## About winkJS
124
70
 
125
- WinkComposer joins the winkJS family - production-grade open-source packages for Natural Language Processing, Machine Learning, and Statistical Analysis in JavaScript. Built for reliability with ~100% test coverage.
126
-
127
-
128
- ## Copyright & License
129
-
130
- **Wink Composer** is copyright 2017-25 [GRAYPE Systems Private Limited](https://graype.in/).
131
-
132
- It is licensed under the terms of the MIT License.
71
+ [winkJS](https://winkjs.org) is the open-source home of two high-performance, production-grade tools — built from first principles, tested to near-perfection, and trusted by thousands of projects worldwide.
package/package.json CHANGED
@@ -1,30 +1,32 @@
1
1
  {
2
2
  "name": "@winkjs/composer",
3
- "version": "0.0.2",
4
- "description": "Composable Streaming Intelligence.",
3
+ "version": "0.4.0",
4
+ "description": "Composable Streaming Intelligence — turn streaming data into actionable insights, from edge to cloud",
5
5
  "keywords": [
6
+ "streaming",
7
+ "streaming-intelligence",
8
+ "real-time",
9
+ "edge",
6
10
  "iot",
7
- "statistical-learning",
8
- "stream-processing",
9
- "statistical-analysis",
10
- "event-driven",
11
- "composable",
12
- "real-time-analytics",
13
- "anomaly-detection",
14
- "edge-computing",
15
- "edgi-ai",
16
- "streaming-analytics",
17
- "industrial-iot"
11
+ "time-series",
12
+ "signal-processing",
13
+ "mqtt",
14
+ "questdb",
15
+ "wink-composer",
16
+ "winkjs",
17
+ "wink"
18
18
  ],
19
19
  "main": "src/composer.js",
20
+ "module": "src/composer.js",
21
+ "exports": {
22
+ ".": "./src/composer.js"
23
+ },
20
24
  "scripts": {
21
25
  "pretest": "npm run lint",
22
- "test": "nyc --reporter=html --reporter=lcov --reporter=text mocha ./test/",
23
- "coverage": "nyc report --reporter=text-lcov | coveralls",
24
- "sourcedocs": "docker -i src -o ./sourcedocs --sidebar yes",
25
- "lint": "eslint ./src/*.js ./test/*.js",
26
- "perf": "node benchmark/run.js",
27
- "dtslint": "dtslint types",
26
+ "test": "mocha --recursive 'src/**/*.specs.js' --ignore 'src/**/slow-*.specs.js'",
27
+ "test:hardening": "node --expose-gc node_modules/.bin/mocha --recursive 'src/**/slow-*.specs.js' --timeout 300000 --exit",
28
+ "test:coverage": "c8 --reporter=html mocha --recursive 'src/**/*.specs.js' --ignore 'src/**/slow-*.specs.js'",
29
+ "lint": "eslint 'src/**/*.js'",
28
30
  "docs-serve": "docsify serve ./docs"
29
31
  },
30
32
  "repository": {
@@ -33,19 +35,41 @@
33
35
  },
34
36
  "author": "Sanjaya Kumar Saxena",
35
37
  "license": "MIT",
38
+ "type": "module",
39
+ "engines": {
40
+ "node": ">=22"
41
+ },
42
+ "files": [
43
+ "src/composer.js",
44
+ "src/core/**/*.js",
45
+ "src/flow/**/*.js",
46
+ "src/nodes/**/*.js",
47
+ "src/tools/**/*.js",
48
+ "src/windowing/**/*.js",
49
+ "!src/**/*.specs.js",
50
+ "!src/**/test/"
51
+ ],
36
52
  "bugs": {
37
53
  "url": "https://github.com/winkjs/composer/issues"
38
54
  },
39
- "homepage": "http://winkjs.org/composer/#/",
55
+ "homepage": "https://composer.winkjs.org/",
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
40
59
  "devDependencies": {
41
- "benchmark": "^2.1.4",
42
- "chai": "^4.3.7",
43
- "coveralls": "^3.1.1",
44
- "docker": "^1.0.0",
45
- "docsify-cli": "^4.4.4",
46
- "dtslint": "^4.2.1",
47
- "eslint": "^8.36.0",
48
- "mocha": "^10.2.0",
49
- "nyc": "^15.1.0"
60
+ "c8": "^10.1.3",
61
+ "chai": "^5.2.0",
62
+ "eslint": "^9.21.0",
63
+ "eslint-plugin-security": "^4.0.0",
64
+ "mocha": "^11.1.0",
65
+ "sinon": "^19.0.2"
66
+ },
67
+ "dependencies": {
68
+ "@questdb/nodejs-client": "^4.2.0",
69
+ "classic-level": "^3.0.0",
70
+ "glob": "^13.0.6",
71
+ "mqtt": "^5.15.1",
72
+ "msgpackr": "^1.11.10",
73
+ "pg": "^8.20.0"
50
74
  }
51
75
  }
@@ -0,0 +1,75 @@
1
+ // src/composer.js
2
+
3
+ /**
4
+ * @fileoverview Main entry point for @winkjs/composer.
5
+ *
6
+ * Provides flat exports for the DSL, source adapters, emitter adapters,
7
+ * codecs, and tunable helpers.
8
+ *
9
+ * @example
10
+ * import { flow, csv, mqttEmitter } from '@winkjs/composer';
11
+ *
12
+ * flow('myPipeline')
13
+ * .sanitize(...)
14
+ * .threshold(...)
15
+ * .source(csv, { path: './data.csv' })
16
+ * .emitter(mqttEmitter, { brokerUrl: 'mqtt://localhost' })
17
+ * .run();
18
+ */
19
+
20
+ // DSL
21
+ export { flow } from './flow/flow.js';
22
+
23
+ // Headless driver — feed a flow that has no source (see docs/handbook/headless-flow.md)
24
+ export { headlessDriver } from './flow/driver.js';
25
+
26
+ // Emitters
27
+ export * as mqttEmitter from './core/emitter-manager/mqtt/index.js';
28
+ export * as terminal from './core/emitter-manager/terminal/index.js';
29
+
30
+ // Sources
31
+ export * as csv from './core/source-manager/csv/index.js';
32
+ export * as mqttSource from './core/source-manager/mqtt/index.js';
33
+
34
+ // Storage
35
+ export { default as questdbAdapter } from './core/storage-manager/questdb/index.js';
36
+
37
+ // Semantics
38
+ export { loadSemantics } from './core/semantics/loader.js';
39
+
40
+ // Codecs
41
+ export { jsonCodec, msgpackCodec } from './core/codec/index.js';
42
+
43
+ // Tunable helpers
44
+ export {
45
+ lookupByField,
46
+ pickByField,
47
+ scaleBy,
48
+ chooseWhen,
49
+ clampTo,
50
+ fromField,
51
+ offsetBy
52
+ } from './core/tunable/helpers.js';
53
+
54
+ // Transform helpers
55
+ export {
56
+ square,
57
+ abs,
58
+ sqrt,
59
+ log,
60
+ log10,
61
+ reciprocal,
62
+ negate
63
+ } from './nodes/transform/helpers.js';
64
+
65
+ // Training utilities
66
+ export {
67
+ computeStandardParams,
68
+ computeMinMaxParams,
69
+ computeRobustParams,
70
+ scale,
71
+ standardize
72
+ } from './tools/training/scale.js';
73
+ export { sigmoid, train, predict, classify } from './tools/training/logistic.js';
74
+ export { confusionMatrix, classificationMetrics, sweepThresholds } from './tools/training/metrics.js';
75
+
@@ -0,0 +1,148 @@
1
+ // core/batch/aoo-encoder/index.js
2
+ // Purpose:
3
+ // Array-of-Objects (AoO) encoder for efficient batch transmission of tabular data.
4
+ // Optimized for downsampled time-series data where rows have consistent shape.
5
+ // Designed to work with msgpackr's structure learning for maximum compression.
6
+ //
7
+ // Design Philosophy:
8
+ // - Zero allocations in hot path - reuse all objects and arrays
9
+ // - Stable object shapes - enables msgpackr structure learning
10
+ // - Predictable field order - consistent compression ratios
11
+ // - Null preservation - maintains shape even with missing data
12
+ //
13
+ // Use Case:
14
+ // Downsampled sensor data often arrives as rows of readings:
15
+ // [{ timestamp: 1234, temp: 23.5, pressure: 101.3 }, ...]
16
+ // This encoder batches these rows efficiently for MQTT transmission.
17
+ //
18
+ // Compression Strategy:
19
+ // By maintaining stable object shapes and field order, msgpackr learns the
20
+ // structure after ~5 messages. Subsequent messages only send structure ID + values,
21
+ // achieving 80-90% size reduction compared to JSON.
22
+
23
+ /**
24
+ * Create an Array-of-Objects encoder for batch data transmission.
25
+ *
26
+ * @param {Object} options - Encoder configuration
27
+ * @param {number} options.maxRows - Maximum rows per batch (pre-allocation size)
28
+ * @param {string[]} options.fieldOrder - Field names in consistent order
29
+ * @param {Object} options.codec - Codec instance from createCodec()
30
+ *
31
+ * @returns {Object} Encoder with encode() method and fieldOrder reference
32
+ *
33
+ * @example
34
+ * const encoder = createAooEncoder({
35
+ * maxRows: 100,
36
+ * fieldOrder: ['timestamp', 'temperature', 'pressure'],
37
+ * codec: msgpackCodec
38
+ * });
39
+ *
40
+ * const { payload, props } = encoder.encode('sensor-01', 1, readings, 50);
41
+ * mqttClient.publish('telemetry/batch', payload, { properties: props });
42
+ */
43
+ export const createAooEncoder = function ( { maxRows, fieldOrder, codec } ) {
44
+ // Input validation - fail fast on misconfiguration
45
+ if ( !Array.isArray( fieldOrder ) || fieldOrder.length === 0 ) {
46
+ throw new Error( 'fieldOrder must be non-empty array of field names' );
47
+ }
48
+ if ( !maxRows || maxRows <= 0 ) {
49
+ throw new Error( 'maxRows must be positive integer' );
50
+ }
51
+ if ( !codec || typeof codec.pack !== 'function' ) {
52
+ throw new Error( 'codec must have pack() method');
53
+ }
54
+
55
+ // Pre-allocate row objects with stable shape.
56
+ // Critical for msgpackr structure learning:
57
+ // - Same fields in same order = same structure ID
58
+ // - null instead of undefined = consistent serialization
59
+ // - Object.create(null) = no prototype pollution
60
+ const rows = new Array( maxRows );
61
+ for ( let i = 0; i < maxRows; i += 1 ) {
62
+ const r = Object.create( null );
63
+ // Initialize all fields to null (not undefined!)
64
+ // This ensures consistent object shape even when values are missing
65
+ for ( let k = 0; k < fieldOrder.length; k += 1 ) {
66
+ r[ fieldOrder[ k ] ] = null;
67
+ }
68
+ rows[ i ] = r;
69
+ }
70
+
71
+ // Pre-allocate batch array that references row objects.
72
+ // We'll adjust its length per batch but never reallocate.
73
+ // slice() here is a one-time shallow copy of references.
74
+ const batch = rows.slice();
75
+
76
+ // Cache MQTT properties to avoid repeated calls.
77
+ // These properties hint the wire format to subscribers.
78
+ const props = codec.properties || {};
79
+
80
+ /**
81
+ * Encode source rows into a compressed batch payload.
82
+ *
83
+ * Design decisions:
84
+ * - Copy values field-by-field to maintain stable shape
85
+ * - Preserve null but normalize undefined to null
86
+ * - Reuse pre-allocated objects to avoid GC
87
+ * - Temporarily adjust batch.length without reallocation
88
+ *
89
+ * @param {string} producerId - Identifies the data source (e.g., device ID)
90
+ * @param {number} seq - Sequence number for gap detection on receiver side
91
+ * @param {Array} srcRows - Source rows to encode (not modified)
92
+ * @param {number} count - Number of rows to encode (may be < srcRows.length)
93
+ *
94
+ * @returns {Object} { payload: Uint8Array|Buffer, props: Object }
95
+ * payload: Compressed binary data
96
+ * props: MQTT v5 properties for content type hint
97
+ */
98
+ const encode = function ( producerId, seq, srcRows, count ) {
99
+ // Validate count doesn't exceed pre-allocated capacity
100
+ if ( count > maxRows ) {
101
+ throw new Error( `Cannot encode ${count} rows, max capacity is ${maxRows}` );
102
+ }
103
+
104
+ // Copy source data into pre-allocated row objects
105
+ for ( let i = 0; i < count; i += 1 ) {
106
+ const src = srcRows[ i ];
107
+ const dst = rows[ i ];
108
+
109
+ // Copy only declared fields in consistent order
110
+ for ( let k = 0; k < fieldOrder.length; k += 1 ) {
111
+ const key = fieldOrder[ k ];
112
+ const v = src[ key ];
113
+ // FIXED: Normalize undefined to null for consistent serialization
114
+ dst[ key ] = ( v === undefined ) ? null : v;
115
+ }
116
+
117
+ // Point batch array to this row (reference, not copy)
118
+ batch[ i ] = dst;
119
+ }
120
+
121
+ // Temporarily adjust batch length for this encoding
122
+ const cap = batch.length;
123
+ batch.length = count;
124
+
125
+ // Pack the message with consistent structure
126
+ const payload = codec.pack( {
127
+ producerId: producerId,
128
+ seq: seq,
129
+ items: batch
130
+ } );
131
+
132
+ // Restore batch array to full capacity for next call
133
+ batch.length = cap;
134
+
135
+ return {
136
+ payload: payload,
137
+ props: props
138
+ };
139
+ }; // encode()
140
+
141
+ // Public API
142
+ // Expose encode function and field order for reference.
143
+ // slice() returns a defensive copy of fieldOrder.
144
+ return {
145
+ encode: encode,
146
+ fieldOrder: fieldOrder.slice()
147
+ };
148
+ };
@@ -0,0 +1,12 @@
1
+ // core/batch/index.js
2
+ // Purpose:
3
+ // Batch data handling subsystem for efficient transmission of tabular data.
4
+ // Provides sliding windows for history retention and encoders for compression.
5
+
6
+ export { createAooEncoder } from './aoo-encoder.js';
7
+ export { createWindow } from './sliding-window.js';
8
+
9
+ // These work together:
10
+ // 1. Window maintains recent history (sliding-window.js)
11
+ // 2. Encoder compresses for transmission (aoo-encoder.js)
12
+ // 3. Both use same field order for structure stability
@@ -0,0 +1,91 @@
1
+ // core/batch/sliding-window.js
2
+ // Purpose:
3
+ // Fixed-capacity sliding window for retaining recent data snapshots.
4
+ // Designed for "first paint" scenarios where new subscribers need recent history.
5
+ // Maintains a circular buffer of rows with stable object shapes.
6
+
7
+ export const createWindow = function ( capacity, fieldOrder ) {
8
+ // Input validation
9
+ if ( !capacity || capacity <= 0 ) {
10
+ throw new Error( 'capacity must be positive integer' );
11
+ }
12
+ if ( !Array.isArray( fieldOrder ) || fieldOrder.length === 0 ) {
13
+ throw new Error( 'fieldOrder must be non-empty array' );
14
+ }
15
+
16
+ // Pre-allocate all row objects with stable shape
17
+ const rows = new Array( capacity );
18
+ for ( let i = 0; i < capacity; i += 1 ) {
19
+ const r = Object.create( null );
20
+ // Initialize all fields to null for consistent shape
21
+ for ( let k = 0; k < fieldOrder.length; k += 1 ) {
22
+ r[ fieldOrder[ k ] ] = null;
23
+ }
24
+ rows[ i ] = r;
25
+ }
26
+
27
+ // Circular buffer state:
28
+ // - head: next write position [0, capacity)
29
+ // - count: total items in window (capped at capacity)
30
+ let head = 0;
31
+ let count = 0;
32
+
33
+ /**
34
+ * Add a new row to the window, evicting oldest if at capacity.
35
+ */
36
+ const put = function ( src ) {
37
+ // Get pre-allocated row at current position
38
+ const dst = rows[ head ];
39
+
40
+ // Copy only declared fields in order
41
+ for ( let k = 0; k < fieldOrder.length; k += 1 ) {
42
+ const key = fieldOrder[ k ];
43
+ const v = src[ key ];
44
+ // FIXED: Normalize undefined to null for consistent serialization
45
+ dst[ key ] = ( v === undefined ) ? null : v;
46
+ }
47
+
48
+ // Advance head with wraparound
49
+ head = ( head + 1 ) % capacity;
50
+
51
+ // Track fill level (stops growing at capacity)
52
+ if ( count < capacity ) count += 1;
53
+ };
54
+
55
+ /**
56
+ * Extract recent rows from window in FIFO order (oldest to newest).
57
+ * Returns the most recent 'max' rows or all available rows if less than max.
58
+ */
59
+ const toRows = function ( out, max ) {
60
+ // Determine how many rows we can actually provide
61
+ const take = Math.min( count, max );
62
+
63
+ if ( take === 0 ) return 0;
64
+
65
+ // Calculate starting position for oldest data we want to return
66
+ let idx;
67
+ if ( count < capacity ) {
68
+ // Window not full: we have 'count' items starting from 0
69
+ // To get the most recent 'take' items, start from (count - take)
70
+ idx = Math.max( 0, count - take );
71
+ } else {
72
+ // Window full: head points to next write position (oldest data)
73
+ // To get the most recent 'take' items, go back 'take' positions
74
+ idx = ( head - take + capacity ) % capacity;
75
+ }
76
+
77
+ // Copy row references to output array in chronological order
78
+ for ( let i = 0; i < take; i += 1 ) {
79
+ out[ i ] = rows[ idx ];
80
+ idx = ( idx + 1 ) % capacity;
81
+ }
82
+
83
+ return take;
84
+ };
85
+
86
+ // Public API
87
+ return {
88
+ put: put,
89
+ toRows: toRows
90
+ };
91
+ };