@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
@@ -0,0 +1,538 @@
1
+ // core/emitter-manager/mqtt/mqtt-store.js
2
+
3
+ /**
4
+ * @fileoverview MQTT.js-Compatible Store with Pressure Monitoring
5
+ *
6
+ * DORMANT (ADR-021, 2026-07-09): not wired into the emitter. mqtt.js
7
+ * loses QoS-1 messages on every connection acceptance when its outgoing
8
+ * store is asynchronous (the erase-then-rebuild gap — ADR-021
9
+ * has the diagnosis and the measured loss-vs-latency curve), so the
10
+ * emitter runs the client's default synchronous memory store instead.
11
+ * This module and its specs are kept intact as the starting point for
12
+ * the composer-owned WAL successor that restores crash durability
13
+ * truthfully — outside mqtt.js, where the connack race cannot reach it.
14
+ *
15
+ * DESIGN DECISIONS:
16
+ *
17
+ * 1. DUAL KEY STORAGE
18
+ * - Metadata (JSON) and payload (Buffer) stored separately
19
+ * - Prevents Buffer corruption through JSON serialization
20
+ * - Enables efficient metrics calculation without loading payloads
21
+ *
22
+ * 2. CIRCUIT BREAKER PATTERN
23
+ * - Opens after 5 consecutive failures
24
+ * - Auto-recovery attempt after 60 seconds
25
+ * - Prevents cascade failures from corrupted storage
26
+ *
27
+ * 3. PRESSURE MONITORING
28
+ * - Two limits: message count (10,000) and bytes (50MB)
29
+ * - Configurable per deployment environment
30
+ * - Triggers backpressure at 80% for early warning
31
+ *
32
+ * 4. EVENTUALLY CONSISTENT METRICS
33
+ * - Metrics lag behind actual state by milliseconds
34
+ * - Acceptable for monitoring, NOT for control decisions
35
+ * - Trade-off: Simpler async model over perfect accuracy
36
+ *
37
+ * 5. ITERATOR SAFETY
38
+ * - Always close iterators in finally blocks
39
+ * - Prevents LevelDB handle leaks
40
+ * - Critical for long-running edge deployments
41
+ *
42
+ * 6. DRIVER-AGNOSTIC MISSING-KEY HANDLING
43
+ * - `abstract-level`'s contract for missing keys has shifted across
44
+ * versions: some throw `LEVEL_NOT_FOUND`, some resolve with
45
+ * `undefined`. Both are valid per the spec; a single store
46
+ * implementation must handle either.
47
+ * - Each `db.get()` call site normalises **both** forms inline —
48
+ * `undefined` from the resolve path AND `LEVEL_NOT_FOUND` from
49
+ * the catch path are treated as "not present". Inline (rather
50
+ * than helper-wrapped) to avoid an extra Promise allocation per
51
+ * call on the hot path (every PUBACK fires `del()`; ~10 k/s on
52
+ * RPi-class hardware would otherwise add ~30 KB/s of Promise
53
+ * churn for no functional benefit).
54
+ * - Without this normalisation, a missing-key `get()` would
55
+ * TypeError inside the `.then()` handler (`undefined.payload =
56
+ * ...`), trip the circuit breaker after 5 misses, and surface as
57
+ * a `TypeError` to the caller instead of the documented
58
+ * `(null, null)`.
59
+ *
60
+ * LIMITATIONS:
61
+ * - Metrics are not real-time (fire-and-forget consequence)
62
+ * - No automatic eviction when full (by design - preserve data)
63
+ *
64
+ * @module mqtt-store
65
+ */
66
+
67
+ import { ClassicLevel } from 'classic-level';
68
+ import { Readable } from 'stream';
69
+ import {
70
+ STORE_CONFIG,
71
+ DEFAULT_MAX_QUEUE_SIZE,
72
+ DEFAULT_MAX_QUEUE_BYTES,
73
+ QUEUE_CRITICAL_THRESHOLD,
74
+ MQTT_INFLIGHT_ID_LIMIT
75
+ } from './constants.js';
76
+
77
+ /**
78
+ * `LEVEL_NOT_FOUND` err.code as a module-level constant — referenced from
79
+ * each `db.get()` call site's catch handler when normalising the driver's
80
+ * missing-key shape. See file-header design decision #6.
81
+ */
82
+ const ERR_LEVEL_NOT_FOUND = 'LEVEL_NOT_FOUND';
83
+
84
+ /**
85
+ * Singleton empty payload reused on every orphan-meta replay (createStream
86
+ * fallback when the PAYL row is missing). Avoids `Buffer.alloc(0)` per
87
+ * push on the replay path. Buffers from `Buffer.alloc(0)` are immutable
88
+ * for practical purposes (zero length); safe to share.
89
+ */
90
+ const EMPTY_PAYLOAD = Buffer.alloc( 0 );
91
+
92
+ /**
93
+ * Create MQTT.js-compatible store with metrics
94
+ *
95
+ * @param {string} path - Storage directory path
96
+ * @param {Object} [options] - Optional configuration
97
+ * @returns {Object} Store interface for MQTT.js
98
+ */
99
+ export const createMQTTStore = function ( path, options = {} ) {
100
+ // Configurable limits with defaults. maxQueueSize is clamped to the
101
+ // MQTT id-space ceiling — see MQTT_INFLIGHT_ID_LIMIT in constants.js
102
+ // for the full rationale. Clamped loudly, not silently: the operator
103
+ // asked for capacity the protocol cannot provide.
104
+ const requestedQueueSize = options.maxQueueSize || DEFAULT_MAX_QUEUE_SIZE;
105
+ if ( requestedQueueSize > MQTT_INFLIGHT_ID_LIMIT ) {
106
+ console.warn(
107
+ `WinkComposer/mqtt-store: maxQueueSize ${requestedQueueSize} exceeds the MQTT ` +
108
+ `16-bit packet-id space; clamped to ${MQTT_INFLIGHT_ID_LIMIT}`
109
+ );
110
+ }
111
+ const limits = {
112
+ maxQueueSize: Math.min( requestedQueueSize, MQTT_INFLIGHT_ID_LIMIT ),
113
+ maxQueueBytes: options.maxQueueBytes || DEFAULT_MAX_QUEUE_BYTES
114
+ };
115
+
116
+ // Open without global encoding
117
+ const db = new ClassicLevel( path, {
118
+ compression: STORE_CONFIG.compression,
119
+ cacheSize: STORE_CONFIG.cacheSize
120
+ } );
121
+
122
+ // Key prefixes for metadata and payloads
123
+ const META = 'pkt:m:'; // JSON metadata
124
+ const PAYL = 'pkt:p:'; // Buffer payload
125
+
126
+ // Closed-once latch — see `close()` for rationale.
127
+ let closed = false;
128
+
129
+ // Metrics for pressure monitoring
130
+ const metrics = {
131
+ messageCount: 0,
132
+ totalBytes: 0,
133
+ errors: 0,
134
+ lastError: null
135
+ };
136
+
137
+ // Circuit breaker for store failures
138
+ const circuitBreaker = {
139
+ failures: 0,
140
+ lastFailure: 0,
141
+ state: 'CLOSED', // CLOSED, OPEN, HALF_OPEN
142
+
143
+ recordSuccess: function () {
144
+ this.failures = 0;
145
+ this.state = 'CLOSED';
146
+ },
147
+
148
+ recordFailure: function () {
149
+ this.failures += 1;
150
+ this.lastFailure = Date.now();
151
+
152
+ if ( this.failures >= 5 ) {
153
+ this.state = 'OPEN';
154
+ // Use unref() so this timer doesn't keep the process alive
155
+ setTimeout( () => {
156
+ this.state = 'HALF_OPEN';
157
+ }, 60000 ).unref(); // Try again after 1 minute
158
+ }
159
+ },
160
+
161
+ canAttempt: function () {
162
+ return this.state !== 'OPEN';
163
+ }
164
+ };
165
+
166
+ /**
167
+ * Initialize metrics from existing store contents.
168
+ *
169
+ * Race-aware: the put()/del() paths now do **optimistic increment**
170
+ * (so `getPressure()` is accurate at the call site for backpressure).
171
+ * If a put() fires during this scan, our snapshot is stale by the
172
+ * time it lands. Detect that by comparing `metrics.messageCount`
173
+ * before and after the iteration: if it changed, the optimistic
174
+ * counter has already been moved by concurrent ops, so we ADD what
175
+ * we found rather than overwrite.
176
+ */
177
+ const initMetrics = async function () {
178
+ let count = 0;
179
+ let bytes = 0;
180
+ let iter = null;
181
+
182
+ const beforeCount = metrics.messageCount;
183
+ const beforeBytes = metrics.totalBytes;
184
+
185
+ try {
186
+ iter = db.iterator( {
187
+ gte: META,
188
+ lt: META + '\xFF',
189
+ valueEncoding: 'json'
190
+ } );
191
+
192
+ for await ( const [ key, meta ] of iter ) { // eslint-disable-line no-unused-vars
193
+ count += 1;
194
+ bytes += meta.size || 0;
195
+ }
196
+
197
+ // Concurrent put detection — if the optimistic counter
198
+ // moved while we scanned, accumulate rather than overwrite.
199
+ // Slight overestimate (concurrent puts may also be visible
200
+ // in the scan and double-counted), but that biases toward
201
+ // earlier pre-flight reject — safer than under-counting.
202
+ if ( metrics.messageCount === beforeCount && metrics.totalBytes === beforeBytes ) {
203
+ metrics.messageCount = count;
204
+ metrics.totalBytes = bytes;
205
+ } else {
206
+ metrics.messageCount += count;
207
+ metrics.totalBytes += bytes;
208
+ }
209
+
210
+ if ( options.debug && count > 0 ) {
211
+ console.log( `Store initialized with ${count} messages (${bytes} bytes)` );
212
+ }
213
+ } catch ( err ) {
214
+ if ( err.code !== 'LEVEL_NOT_FOUND' && options.debug ) {
215
+ console.error( 'Failed to initialize metrics:', err.message );
216
+ }
217
+ } finally {
218
+ if ( iter ) {
219
+ await iter.close();
220
+ }
221
+ }
222
+ };
223
+
224
+ // Initialize metrics on startup
225
+ initMetrics().catch( () => { /* Noop */ } );
226
+
227
+ /**
228
+ * MQTT.js store interface
229
+ */
230
+ const store = {
231
+ put: ( packet, cb ) => {
232
+ if ( !packet || packet.qos === 0 ) {
233
+ setImmediate( cb );
234
+ return;
235
+ }
236
+
237
+ // Check circuit breaker
238
+ if ( !circuitBreaker.canAttempt() ) {
239
+ const err = new Error( 'Store circuit breaker OPEN' );
240
+ err.code = 'CIRCUIT_OPEN';
241
+ setImmediate( cb, err );
242
+ return;
243
+ }
244
+
245
+ const mid = packet.messageId;
246
+ if ( !mid ) {
247
+ setImmediate( cb, new Error( 'Missing messageId' ) );
248
+ return;
249
+ }
250
+
251
+ // Check message count pressure
252
+ if ( metrics.messageCount >= limits.maxQueueSize ) {
253
+ const err = new Error( 'QUEUE_FULL' );
254
+ err.code = 'QUEUE_FULL';
255
+ err.pressure = 1.0;
256
+ metrics.errors += 1;
257
+ metrics.lastError = err.code;
258
+ setImmediate( cb, err );
259
+ return;
260
+ }
261
+
262
+ // Ensure payload is a Buffer
263
+ const payloadBuf = Buffer.isBuffer( packet.payload ) ?
264
+ packet.payload :
265
+ Buffer.from( packet.payload || [] );
266
+
267
+ // Check byte pressure
268
+ if ( metrics.totalBytes + payloadBuf.length > limits.maxQueueBytes ) {
269
+ const err = new Error( 'STORAGE_FULL' );
270
+ err.code = 'STORAGE_FULL';
271
+ err.pressure = 1.0;
272
+ metrics.errors += 1;
273
+ metrics.lastError = err.code;
274
+ setImmediate( cb, err );
275
+ return;
276
+ }
277
+
278
+ // Separate metadata from payload
279
+ const meta = {
280
+ cmd: packet.cmd || 'publish',
281
+ messageId: mid,
282
+ topic: packet.topic,
283
+ qos: packet.qos,
284
+ retain: !!packet.retain,
285
+ dup: !!packet.dup,
286
+ properties: packet.properties || {},
287
+ size: payloadBuf.length,
288
+ ts: Date.now()
289
+ };
290
+
291
+ // **Optimistic increment** — increment metrics BEFORE the
292
+ // async db.batch so `getPressure()` is accurate at the
293
+ // call site. Without this, pressure lags the real
294
+ // in-flight count by one db.batch cycle, and `publishNow`'s
295
+ // pre-flight reject (`pressure >= 0.9`) can't keep mqtt.js
296
+ // from overshooting the store cap. Empirically that lag
297
+ // caused ~14 k of 50 k messages to be lost in sustained
298
+ // burst tests. Rollback in the .catch path keeps
299
+ // metrics consistent on failure.
300
+ metrics.messageCount += 1;
301
+ metrics.totalBytes += payloadBuf.length;
302
+
303
+ // Atomic batch write
304
+ db.batch( [
305
+ { type: 'put', key: META + mid, value: meta, valueEncoding: 'json' },
306
+ { type: 'put', key: PAYL + mid, value: payloadBuf, valueEncoding: 'buffer' }
307
+ ] )
308
+ .then( () => {
309
+ circuitBreaker.recordSuccess();
310
+ setImmediate( cb );
311
+ } )
312
+ .catch( ( err ) => {
313
+ // Rollback the optimistic increment — the message
314
+ // never landed in the store.
315
+ metrics.messageCount -= 1;
316
+ metrics.totalBytes -= payloadBuf.length;
317
+ metrics.errors += 1;
318
+ metrics.lastError = err.message;
319
+ circuitBreaker.recordFailure();
320
+ setImmediate( cb, err );
321
+ } );
322
+ },
323
+
324
+ get: ( packet, cb ) => {
325
+ const mid = packet && packet.messageId;
326
+ if ( !mid ) {
327
+ setImmediate( cb, null, null );
328
+ return;
329
+ }
330
+
331
+ if ( !circuitBreaker.canAttempt() ) {
332
+ setImmediate( cb, new Error( 'Store circuit breaker OPEN' ) );
333
+ return;
334
+ }
335
+
336
+ // Normalise driver missing-key shape inline (header design
337
+ // decision 6):
338
+ // `undefined` from the resolve path AND `LEVEL_NOT_FOUND`
339
+ // from the catch path are both treated as "not present".
340
+ // Inline rather than helper-wrapped to avoid an extra Promise
341
+ // allocation per call on the hot path.
342
+ Promise.all( [
343
+ db.get( META + mid, { valueEncoding: 'json' } ),
344
+ db.get( PAYL + mid, { valueEncoding: 'buffer' } )
345
+ ] )
346
+ .then( ( [ meta, payload ] ) => {
347
+ if ( meta === undefined ) {
348
+ circuitBreaker.recordSuccess();
349
+ setImmediate( cb, null, null );
350
+ return;
351
+ }
352
+ // Payload may be `undefined` for orphaned meta — leave
353
+ // the field unset; mqtt.js tolerates it.
354
+ if ( payload !== undefined ) meta.payload = payload;
355
+ circuitBreaker.recordSuccess();
356
+ setImmediate( cb, null, meta );
357
+ } )
358
+ .catch( ( err ) => {
359
+ if ( err && err.code === ERR_LEVEL_NOT_FOUND ) {
360
+ circuitBreaker.recordSuccess();
361
+ setImmediate( cb, null, null );
362
+ return;
363
+ }
364
+ circuitBreaker.recordFailure();
365
+ setImmediate( cb, err );
366
+ } );
367
+ },
368
+
369
+ del: ( packet, cb ) => {
370
+ const mid = packet && packet.messageId;
371
+ if ( !mid ) {
372
+ setImmediate( cb );
373
+ return;
374
+ }
375
+
376
+ // Read meta first (for size accounting), then batch-delete
377
+ // both keys. Missing meta ⇒ idempotent no-op. Driver shape
378
+ // normalised inline (header design decision 6) — `undefined`
379
+ // resolve and
380
+ // `LEVEL_NOT_FOUND` throw are both treated as "not present".
381
+ //
382
+ // **Optimistic decrement** — symmetric with put()'s
383
+ // optimistic increment. Updates metrics BEFORE the async
384
+ // db.batch so `getPressure()` reflects the impending
385
+ // del() call site. Rollback in the .catch path keeps
386
+ // metrics consistent on failure.
387
+ db.get( META + mid, { valueEncoding: 'json' } )
388
+ .then( ( meta ) => {
389
+ if ( meta === undefined ) {
390
+ circuitBreaker.recordSuccess();
391
+ setImmediate( cb );
392
+ return undefined;
393
+ }
394
+ const decBytes = meta.size || 0;
395
+ metrics.messageCount = Math.max( 0, metrics.messageCount - 1 );
396
+ metrics.totalBytes = Math.max( 0, metrics.totalBytes - decBytes );
397
+ return db.batch( [
398
+ { type: 'del', key: META + mid },
399
+ { type: 'del', key: PAYL + mid }
400
+ ] ).then( () => {
401
+ circuitBreaker.recordSuccess();
402
+ setImmediate( cb );
403
+ } ).catch( ( err ) => {
404
+ // Rollback the optimistic decrement — the
405
+ // delete didn't actually land.
406
+ metrics.messageCount += 1;
407
+ metrics.totalBytes += decBytes;
408
+ throw err;
409
+ } );
410
+ } )
411
+ .catch( ( err ) => {
412
+ if ( err && err.code === ERR_LEVEL_NOT_FOUND ) {
413
+ circuitBreaker.recordSuccess();
414
+ setImmediate( cb );
415
+ return;
416
+ }
417
+ circuitBreaker.recordFailure();
418
+ setImmediate( cb, err );
419
+ } );
420
+ },
421
+
422
+ createStream: () => {
423
+ let iterator = null;
424
+
425
+ const stream = new Readable( {
426
+ objectMode: true,
427
+
428
+ async read () {
429
+ try {
430
+ if ( !iterator ) {
431
+ iterator = db.iterator( {
432
+ gte: META,
433
+ lt: META + '\xFF',
434
+ valueEncoding: 'json'
435
+ } );
436
+ }
437
+
438
+ const result = await iterator.next();
439
+
440
+ if ( result === undefined ) {
441
+ this.push( null );
442
+ await iterator.close();
443
+ iterator = null;
444
+ return;
445
+ }
446
+
447
+ const [ key, meta ] = result;
448
+ const mid = key.slice( META.length );
449
+
450
+ // Driver missing-key shape normalised inline
451
+ // (header design decision 6) — `undefined`
452
+ // resolve and `LEVEL_NOT_FOUND` throw both fall
453
+ // back to an empty payload.
454
+ let payload;
455
+ try {
456
+ payload = await db.get( PAYL + mid, { valueEncoding: 'buffer' } );
457
+ } catch ( err ) {
458
+ if ( err && err.code !== ERR_LEVEL_NOT_FOUND ) {
459
+ this.destroy( err );
460
+ return;
461
+ }
462
+ payload = undefined;
463
+ }
464
+ meta.payload = payload === undefined ? EMPTY_PAYLOAD : payload;
465
+ meta.dup = true;
466
+ this.push( meta );
467
+ } catch ( err ) {
468
+ this.destroy( err );
469
+ }
470
+ },
471
+
472
+ async destroy ( err, callback ) {
473
+ if ( iterator ) {
474
+ try {
475
+ await iterator.close();
476
+ } catch ( closeErr ) { // eslint-disable-line no-unused-vars
477
+ // Ignore close errors
478
+ }
479
+ }
480
+ callback( err );
481
+ }
482
+ } );
483
+
484
+ return stream;
485
+ },
486
+
487
+ close: ( cb ) => {
488
+ // Idempotent: mqtt.js's `closeStores` calls us during
489
+ // `client.end()`, AND the emitter's shutdown path may call
490
+ // us again. Double-closing a LevelDB triggers "Database is
491
+ // not open" errors that surface as phantom DELIVERY_FAILED
492
+ // on every in-flight publish. Short-circuit subsequent calls.
493
+ if ( closed ) {
494
+ if ( cb ) setImmediate( cb );
495
+ return;
496
+ }
497
+ closed = true;
498
+ db.close()
499
+ .then( () => cb && cb() )
500
+ .catch( ( err ) => cb && cb( err ) );
501
+ }
502
+ };
503
+
504
+ /**
505
+ * Get current pressure (0.0 to 1.0)
506
+ */
507
+ const getPressure = function () {
508
+ const countPressure = Math.min( 1.0, metrics.messageCount / limits.maxQueueSize );
509
+ const bytePressure = Math.min( 1.0, metrics.totalBytes / limits.maxQueueBytes );
510
+ return Math.max( countPressure, bytePressure );
511
+ };
512
+
513
+ /**
514
+ * Get health status
515
+ */
516
+ const getHealthStatus = function () {
517
+ if ( circuitBreaker.state === 'OPEN' ) {
518
+ return 'CRITICAL';
519
+ }
520
+
521
+ const pressure = getPressure();
522
+ if ( pressure > QUEUE_CRITICAL_THRESHOLD ) {
523
+ return 'RED';
524
+ } else if ( pressure > 0.5 ) {
525
+ return 'YELLOW';
526
+ }
527
+ return 'GREEN';
528
+ };
529
+
530
+ return {
531
+ store,
532
+ getPressure,
533
+ getHealthStatus,
534
+ getMetrics: () => ( { ...metrics } ),
535
+ getCircuitState: () => circuitBreaker.state,
536
+ db
537
+ };
538
+ }; // createMQTTStore()
@@ -0,0 +1,98 @@
1
+ // core/emitter-manager/terminal/assert-columns.js
2
+
3
+ /**
4
+ * @fileoverview Walks the column map terminal received from the wiring
5
+ * layer and checks that the column-internal facts terminal actually
6
+ * reads are well-formed. Throws with `err.code = 'INVALID_CONFIG'` on
7
+ * the first problem found, naming the offending column and field.
8
+ *
9
+ * Why this file exists separately from the universal semantics schema:
10
+ *
11
+ * Same reasoning as QuestDB's `storage-manager/questdb/assert-columns.js`.
12
+ * The semantics schema validates universal correctness ("every column
13
+ * needs a `type`"); adapter-specific contracts ("if `type` is float64
14
+ * AND a `resolution` is declared, it must be a positive finite number")
15
+ * live with the adapter that consumes the fact. Terminal only consumes
16
+ * `resolution` (for per-column formatting), so this file's check is
17
+ * narrower than QDB's: just resolution validity, no type whitelist.
18
+ *
19
+ * Terminal handles every column type at runtime — string, boolean,
20
+ * number, integer, etc. all flow through `formatValue`. So unlike
21
+ * QDB, terminal does not require columns to declare a `type` field at
22
+ * all. Resolution is the one fact terminal reads from columns.
23
+ *
24
+ * Per ADR-018's adapter–semantics alignment: each adapter that
25
+ * consumes column-internal facts MUST validate them defensively at
26
+ * startup AND document those assertions in its `@fileoverview`. This
27
+ * file is terminal's enforcement point; the documentation lives in the
28
+ * `Column-internal facts consumed` section of `index.js`.
29
+ *
30
+ * `err.code` thrown:
31
+ * - `INVALID_CONFIG` — column-internal fact malformed.
32
+ * Per ADR-018, setup-time throws carry a
33
+ * classified `err.code`.
34
+ *
35
+ * @see ADR-018 (adapter–semantics alignment)
36
+ * @see ../../storage-manager/questdb/assert-columns.js (sister file for QDB)
37
+ */
38
+
39
+ /**
40
+ * Walk every column in `columns` and assert the resolution facts
41
+ * terminal reads are well-formed. Throws on the first violation.
42
+ *
43
+ * Behaviour:
44
+ * - Columns without a `resolution` field are fine (terminal falls back
45
+ * to its global `precision` config).
46
+ * - When `resolution` is present AND the column's `type === 'float64'`,
47
+ * resolution must be a positive finite number. Other resolution
48
+ * values would silently produce garbage formatting (`Math.log10` of
49
+ * zero is -Infinity, of a negative is NaN, etc.).
50
+ * - When `resolution` is present but `type !== 'float64'` (or `type`
51
+ * is missing), resolution is ignored — terminal only quantizes
52
+ * float64. We do not throw because semantically the field is dead
53
+ * weight, not corrupting; the column is still usable for display.
54
+ *
55
+ * @param {Object} columns - the columns map from the asset class slice
56
+ * @throws {Error} `err.code === 'INVALID_CONFIG'` on any column-internal
57
+ * fact violation; message names the column and the offending field
58
+ */
59
+ const assertColumnFacts = function ( columns ) {
60
+ if ( !columns || typeof columns !== 'object' ) {
61
+ return;
62
+ }
63
+
64
+ const colNames = Object.keys( columns );
65
+
66
+ for ( let i = 0; i < colNames.length; i += 1 ) {
67
+ const colName = colNames[ i ];
68
+ const colSpec = columns[ colName ];
69
+
70
+ // Defensive: a null or non-object column entry is a caller bug;
71
+ // skip rather than crash on a `.type` access. The semantics
72
+ // schema would have rejected this earlier in any well-formed
73
+ // pipeline; this is the last-line-of-defense path for direct
74
+ // createEmitter calls (e.g., test code).
75
+ if ( !colSpec || typeof colSpec !== 'object' ) {
76
+ continue; // eslint-disable-line no-continue
77
+ }
78
+
79
+ // Resolution check fires only when present AND the column is
80
+ // float64. Other types: resolution is ignored as dead weight,
81
+ // not a hard error — the column is still usable for display.
82
+ if ( colSpec.type === 'float64' && colSpec.resolution !== undefined ) {
83
+ if ( typeof colSpec.resolution !== 'number' ||
84
+ !Number.isFinite( colSpec.resolution ) ||
85
+ colSpec.resolution <= 0 ) {
86
+ const err = new Error(
87
+ `WinkComposer/terminal: column '${colName}' has type 'float64' ` +
88
+ `but resolution is '${colSpec.resolution}'; ` +
89
+ 'expected positive finite number (or omit the field to use the global precision)'
90
+ );
91
+ err.code = 'INVALID_CONFIG';
92
+ throw err;
93
+ }
94
+ }
95
+ }
96
+ };
97
+
98
+ export { assertColumnFacts };