@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,226 @@
1
+ // core/source-manager/test-harness/comparator.js
2
+
3
+ /**
4
+ * @fileoverview Check tool for testHarness.
5
+ *
6
+ * Takes the messages the harness generated (the ground truth) and
7
+ * one capture per sink (terminal stdout, MQTT subscriber messages,
8
+ * QuestDB rows) and checks each sink's output against the harness
9
+ * input, matched on `_harnessId`.
10
+ *
11
+ * Why ground-truth-driven: the harness deterministically produced a
12
+ * known sequence of messages. The "correct" output for any sink is
13
+ * exactly that sequence. Comparing sinks to each other only catches
14
+ * alignment drift; comparing each sink to the harness input also
15
+ * catches a whole pipeline going wrong in the same way (a uniform
16
+ * error every sink shares).
17
+ *
18
+ * Per-message rules:
19
+ * - Every checked sink must have the message (matched by _harnessId).
20
+ * - For each column declared in the asset class:
21
+ * - float64 with declared resolution: the sink's value must
22
+ * round to the same grid step as the harness input.
23
+ * - other numerics, strings, bools, timestamps: exact match.
24
+ * - Fuzz messages (those carrying `_harnessFuzzPattern`) skip the
25
+ * value check — fuzz inputs legitimately produce different sink
26
+ * outputs (NaN becomes "NaN" in terminal, null in MQTT JSON,
27
+ * dropped in QDB). The check is "every sink saw it" only.
28
+ *
29
+ * The report:
30
+ * {
31
+ * ok: boolean,
32
+ * errors: [ '<diagnostic line>', ... ],
33
+ * summary: {
34
+ * messageCount,
35
+ * sinkCounts: { terminal, mqtt, qdb }
36
+ * }
37
+ * }
38
+ *
39
+ * Error strings name the harnessId, the column, the harness input,
40
+ * the sink's observed value, and the declared resolution when it
41
+ * applies.
42
+ */
43
+
44
+ const HARNESS_ID_FIELD = '_harnessId';
45
+ const FUZZ_MARKER_FIELD = '_harnessFuzzPattern';
46
+
47
+ /**
48
+ * Walks captured terminal stdout and pulls out each message that
49
+ * was printed in pretty-JSON form (the terminal emitter's
50
+ * `verbose: true` output).
51
+ *
52
+ * Relies on a flat-message layout: each top-level `{` and matching
53
+ * `}` sit at column 0; everything in between is indented. We
54
+ * collect lines between those bookends and JSON.parse the result.
55
+ *
56
+ * @param {string} text - Captured stdout buffer
57
+ * @returns {Object[]} Parsed messages in the order they appeared
58
+ */
59
+ export const parseTerminalOutput = function ( text ) {
60
+ const messages = [];
61
+ let buffer = null;
62
+
63
+ const lines = text.split( '\n' );
64
+ for ( let i = 0; i < lines.length; i += 1 ) {
65
+ const line = lines[ i ];
66
+ if ( line === '{' ) {
67
+ buffer = `${line}\n`;
68
+ } else if ( line === '}' && buffer !== null ) {
69
+ buffer += line;
70
+ messages.push( JSON.parse( buffer ) );
71
+ buffer = null;
72
+ } else if ( buffer !== null ) {
73
+ buffer += `${line}\n`;
74
+ }
75
+ }
76
+
77
+ return messages;
78
+ };
79
+
80
+ /**
81
+ * Returns an array indexed by message harness id. Each entry is the
82
+ * parsed message; missing ids show up as `undefined`. The id field
83
+ * is fixed (`_harnessId`) — the harness always adds it.
84
+ *
85
+ * @param {Object[]} messages - Parsed messages from one sink
86
+ * @returns {Map<number, Object>}
87
+ */
88
+ export const indexById = function ( messages ) {
89
+ const map = new Map();
90
+ for ( const msg of messages ) {
91
+ const id = msg[ HARNESS_ID_FIELD ];
92
+ if ( id !== undefined && id !== null ) {
93
+ map.set( id, msg );
94
+ }
95
+ }
96
+ return map;
97
+ };
98
+
99
+ /**
100
+ * Tells whether a column should be compared with resolution-aware
101
+ * tolerance. Only float64 columns with a declared resolution use
102
+ * the grid-snap check; everything else uses exact equality.
103
+ */
104
+ const usesResolutionTolerance = function ( columnSpec ) {
105
+ return columnSpec && columnSpec.type === 'float64' && typeof columnSpec.resolution === 'number';
106
+ };
107
+
108
+ /**
109
+ * For two float values that should sit on the same resolution grid,
110
+ * tells whether they round to the same grid step. Equivalent to
111
+ * "within half a resolution step of each other".
112
+ */
113
+ const onSameGrid = function ( a, b, resolution ) {
114
+ if ( typeof a !== 'number' || typeof b !== 'number' ) return false;
115
+ if ( Number.isNaN( a ) || Number.isNaN( b ) ) return Number.isNaN( a ) === Number.isNaN( b );
116
+ return Math.round( a / resolution ) === Math.round( b / resolution );
117
+ };
118
+
119
+ /**
120
+ * Returns true when the sink's value matches the harness's value,
121
+ * applying resolution tolerance for float64 columns that declare it.
122
+ */
123
+ const valuesAgree = function ( harnessValue, sinkValue, columnSpec ) {
124
+ if ( usesResolutionTolerance( columnSpec ) ) {
125
+ return onSameGrid( harnessValue, sinkValue, columnSpec.resolution );
126
+ }
127
+ if ( Number.isNaN( harnessValue ) || Number.isNaN( sinkValue ) ) {
128
+ return Number.isNaN( harnessValue ) === Number.isNaN( sinkValue );
129
+ }
130
+ return harnessValue === sinkValue;
131
+ };
132
+
133
+ /**
134
+ * Builds a diagnostic line for one column where a sink's value
135
+ * disagrees with the harness input.
136
+ */
137
+ const formatColumnError = function ( harnessId, sinkName, columnName, columnSpec, harnessValue, sinkValue ) {
138
+ const head = `harnessId=${harnessId}, column '${columnName}' — harness sent ${JSON.stringify( harnessValue )}, ${sinkName} saw ${JSON.stringify( sinkValue )}`;
139
+ if ( usesResolutionTolerance( columnSpec ) ) {
140
+ return `${head}, declared resolution: ${columnSpec.resolution} — beyond resolution`;
141
+ }
142
+ return `${head} — values differ`;
143
+ };
144
+
145
+ /**
146
+ * Compares one sink's captured messages against the harness inputs,
147
+ * column by column. Returns the error strings for this sink.
148
+ */
149
+ const checkSinkAgainstInputs = function ( sinkName, sinkIndex, harnessIndex, columnNames, columns ) {
150
+ const errors = [];
151
+ for ( const [ harnessId, harnessMsg ] of harnessIndex.entries() ) {
152
+ const sinkMsg = sinkIndex.get( harnessId );
153
+ if ( !sinkMsg ) {
154
+ errors.push( `harnessId=${harnessId} missing from ${sinkName}` );
155
+ continue; // eslint-disable-line no-continue
156
+ }
157
+ // Fuzz messages skip the column-by-column check — presence
158
+ // alone is the fuzz assertion.
159
+ if ( harnessMsg[ FUZZ_MARKER_FIELD ] !== undefined ) {
160
+ continue; // eslint-disable-line no-continue
161
+ }
162
+ for ( const columnName of columnNames ) {
163
+ if ( columnName === HARNESS_ID_FIELD ) continue; // eslint-disable-line no-continue
164
+ const harnessValue = harnessMsg[ columnName ];
165
+ const sinkValue = sinkMsg[ columnName ];
166
+ if ( !valuesAgree( harnessValue, sinkValue, columns[ columnName ] ) ) {
167
+ errors.push( formatColumnError( harnessId, sinkName, columnName, columns[ columnName ], harnessValue, sinkValue ) );
168
+ }
169
+ }
170
+ }
171
+ return errors;
172
+ };
173
+
174
+ /**
175
+ * Checks each captured sink against the harness inputs (the ground
176
+ * truth). Skips per-column value checks for fuzz messages — those
177
+ * legitimately produce different sink outputs.
178
+ *
179
+ * @param {Object[]} harnessInputs - Messages the harness produced (ground truth)
180
+ * @param {Object} captures
181
+ * @param {string} [captures.terminal] - Raw stdout buffer
182
+ * @param {Object[]} [captures.mqtt] - Messages received over MQTT
183
+ * @param {Object[]} [captures.qdb] - Rows queried from QuestDB
184
+ * @param {Object} assetClass - Declares column types and resolutions
185
+ * @returns {{ok: boolean, errors: string[], summary: Object}}
186
+ */
187
+ export const compareCaptures = function ( harnessInputs, captures, assetClass ) {
188
+ const harnessIndex = indexById( harnessInputs || [] );
189
+
190
+ const sinkIndices = {};
191
+ const sinkCounts = {};
192
+
193
+ if ( captures.terminal !== undefined ) {
194
+ const parsed = parseTerminalOutput( captures.terminal );
195
+ sinkIndices.terminal = indexById( parsed );
196
+ sinkCounts.terminal = parsed.length;
197
+ }
198
+ if ( captures.mqtt !== undefined ) {
199
+ sinkIndices.mqtt = indexById( captures.mqtt );
200
+ sinkCounts.mqtt = captures.mqtt.length;
201
+ }
202
+ if ( captures.qdb !== undefined ) {
203
+ sinkIndices.qdb = indexById( captures.qdb );
204
+ sinkCounts.qdb = captures.qdb.length;
205
+ }
206
+
207
+ const columnNames = ( assetClass && assetClass.columns ) ? Object.keys( assetClass.columns ) : [];
208
+ const columns = ( assetClass && assetClass.columns ) ? assetClass.columns : {};
209
+
210
+ const errors = [];
211
+ for ( const sinkName of Object.keys( sinkIndices ) ) {
212
+ const sinkErrors = checkSinkAgainstInputs(
213
+ sinkName, sinkIndices[ sinkName ], harnessIndex, columnNames, columns
214
+ );
215
+ errors.push( ...sinkErrors );
216
+ }
217
+
218
+ return {
219
+ ok: errors.length === 0,
220
+ errors,
221
+ summary: {
222
+ messageCount: harnessIndex.size,
223
+ sinkCounts
224
+ }
225
+ };
226
+ };
@@ -0,0 +1,85 @@
1
+ // core/source-manager/test-harness/field-generator.js
2
+
3
+ /**
4
+ * @fileoverview Generates one value for one field of a generated message.
5
+ *
6
+ * Each call returns a value of the field's declared type, drawn from
7
+ * the random generator (or built from message index for timestamps).
8
+ * Float values snap to the declared resolution so the harness's output
9
+ * matches what real sensors would produce — and what QuestDB will
10
+ * store after its own quantization.
11
+ *
12
+ * The field spec shape (`type`, `range`, `resolution`, `values`) is
13
+ * enforced at startup by `validate.js` (FIELD_SPEC_SCHEMA).
14
+ */
15
+
16
+ const generateFloat = function ( spec, prng ) {
17
+ const range = spec.range || [ 0, 1 ];
18
+ const raw = prng.floatInRange( range[ 0 ], range[ 1 ] );
19
+ if ( spec.resolution ) {
20
+ // Snap to grid. Sensors quantize, so generated data should
21
+ // too — otherwise the cross-sink check fails on values that
22
+ // QuestDB rounds and terminal does not.
23
+ return Math.round( raw / spec.resolution ) * spec.resolution;
24
+ }
25
+ return raw;
26
+ };
27
+
28
+ const generateInt = function ( spec, prng ) {
29
+ const range = spec.range || [ 0, 100 ];
30
+ return prng.intInRange( range[ 0 ], range[ 1 ] );
31
+ };
32
+
33
+ const generateBool = function ( spec, prng ) {
34
+ if ( spec.values ) {
35
+ return prng.pickFrom( spec.values );
36
+ }
37
+ return prng.next() < 0.5;
38
+ };
39
+
40
+ const generateString = function ( spec, prng ) {
41
+ // Strings always need an explicit `values` list — validate.js
42
+ // enforces this at startup.
43
+ return prng.pickFrom( spec.values );
44
+ };
45
+
46
+ const generateTimestamp = function ( spec, msgIndex, intervalMs ) {
47
+ if ( spec.mode === 'static' ) {
48
+ return spec.seedValue || 0;
49
+ }
50
+ // monotonic-ms (default): timestamps go up by the message
51
+ // gap, or 1ms per message when the harness is running flat
52
+ // out (intervalMs = 0). First message gets the seed value.
53
+ const start = spec.seedValue || 0;
54
+ const step = ( intervalMs > 0 ) ? intervalMs : 1;
55
+ return start + ( ( msgIndex - 1 ) * step );
56
+ };
57
+
58
+ /**
59
+ * Returns one generated value for the given field spec.
60
+ *
61
+ * @param {Object} fieldSpec - One entry from messageTemplate.fields
62
+ * @param {Object} prng - The random generator (from prng.js)
63
+ * @param {number} msgIndex - 1-based index of the current message
64
+ * @param {number} intervalMs - Gap between messages; used as the
65
+ * timestamp step for monotonic-ms mode
66
+ * @returns {*} A value of the declared type
67
+ */
68
+ export const generateField = function ( fieldSpec, prng, msgIndex, intervalMs ) {
69
+ switch ( fieldSpec.type ) {
70
+ case 'float64':
71
+ return generateFloat( fieldSpec, prng );
72
+ case 'int64':
73
+ return generateInt( fieldSpec, prng );
74
+ case 'bool':
75
+ return generateBool( fieldSpec, prng );
76
+ case 'string':
77
+ return generateString( fieldSpec, prng );
78
+ case 'timestamp':
79
+ return generateTimestamp( fieldSpec, msgIndex, intervalMs );
80
+ default:
81
+ // Unreachable in practice — validate.js rejects unknown
82
+ // types at startup. Kept for clarity at the boundary.
83
+ throw new Error( `testHarness: unknown field type "${fieldSpec.type}"` );
84
+ }
85
+ };
@@ -0,0 +1,87 @@
1
+ // core/source-manager/test-harness/fuzz.js
2
+
3
+ /**
4
+ * @fileoverview Fuzz patterns for testHarness.
5
+ *
6
+ * When a test enables fuzz, the harness injects one bad value into
7
+ * the target column every Nth message. The patterns below are the
8
+ * minimal set we agreed on — common ways production data goes wrong
9
+ * (null, NaN, JS quirks). For each fuzz message, the harness picks
10
+ * one pattern in rotation, so over the run every pattern gets
11
+ * exercised the same number of times.
12
+ *
13
+ * If the chosen pattern does not make sense for the target field's
14
+ * type (for example, NaN on a string field), we fall back to null —
15
+ * always valid, always a useful "bad value" — so the rotation
16
+ * still progresses.
17
+ *
18
+ * Both the pattern set and the null fallback were settled as design
19
+ * decisions on 2026-04-29.
20
+ */
21
+
22
+ /**
23
+ * Names of the six fuzz patterns, in rotation order.
24
+ * @type {string[]}
25
+ */
26
+ export const FUZZ_PATTERN_NAMES = [
27
+ 'null',
28
+ 'NaN',
29
+ 'string-where-number',
30
+ 'undefined',
31
+ 'infinity',
32
+ 'empty-string'
33
+ ];
34
+
35
+ const FUZZ_VALUES = {
36
+ 'null': null,
37
+ 'NaN': NaN,
38
+ 'string-where-number': 'not-a-number',
39
+ 'undefined': undefined,
40
+ 'infinity': Infinity,
41
+ 'empty-string': ''
42
+ };
43
+
44
+ /**
45
+ * Tells whether a given fuzz pattern makes sense for a given
46
+ * declared field type. Patterns that do not match the type fall
47
+ * back to null at injection time.
48
+ *
49
+ * @param {string} patternName
50
+ * @param {string} fieldType - 'float64' | 'int64' | 'bool' | 'string' | 'timestamp'
51
+ * @returns {boolean}
52
+ */
53
+ export const isCompatible = function ( patternName, fieldType ) {
54
+ switch ( patternName ) {
55
+ case 'null':
56
+ case 'undefined':
57
+ return true;
58
+ case 'NaN':
59
+ case 'infinity':
60
+ return fieldType === 'float64';
61
+ case 'string-where-number':
62
+ return fieldType === 'float64' || fieldType === 'int64';
63
+ case 'empty-string':
64
+ return fieldType === 'string';
65
+ default:
66
+ return false;
67
+ }
68
+ };
69
+
70
+ /**
71
+ * Replaces the target column on the message with a fuzz value, and
72
+ * adds a `_harnessFuzzPattern` field naming the pattern that was
73
+ * applied. Mutates `msg` in place.
74
+ *
75
+ * @param {Object} msg - The message being built
76
+ * @param {string} fuzzTarget - Name of the column to fuzz
77
+ * @param {Object} targetSpec - The target column's spec from messageTemplate.fields
78
+ * @param {number} patternIndex - Index into FUZZ_PATTERN_NAMES (rotates)
79
+ */
80
+ export const applyFuzz = function ( msg, fuzzTarget, targetSpec, patternIndex ) {
81
+ const patternName = FUZZ_PATTERN_NAMES[ patternIndex ];
82
+ const compatible = isCompatible( patternName, targetSpec.type );
83
+ const value = compatible ? FUZZ_VALUES[ patternName ] : null;
84
+
85
+ msg[ fuzzTarget ] = value;
86
+ msg._harnessFuzzPattern = patternName; // eslint-disable-line no-underscore-dangle
87
+ };
@@ -0,0 +1,115 @@
1
+ // core/source-manager/test-harness/index.js
2
+
3
+ /**
4
+ * @fileoverview testHarness Source Adapter.
5
+ *
6
+ * A synthetic source used by integration tests to push messages
7
+ * through every sink and check that all sinks agree. The harness
8
+ * does not read from a file or a network — it invents messages on
9
+ * demand from a test-author-supplied `messageTemplate`.
10
+ *
11
+ * Each generated message carries a running number `_harnessId` so
12
+ * the cross-sink check tool can match the same message in every
13
+ * sink, regardless of arrival order.
14
+ *
15
+ * Built native to ADR-018's source role — same
16
+ * `stopFn({ timeout })` shape as CSV and MQTT sources, structured
17
+ * `onStatus` lifecycle phases, classified `err.code` on setup
18
+ * throws (`INVALID_CONFIG`). Module exports follow the contract: `id`,
19
+ * `configSchema`, `start`, `durabilityClass`, and the default
20
+ * aggregate referencing the same constants.
21
+ *
22
+ * Durability (ADR-018): `'best-effort'` — the harness keeps no recovery
23
+ * state; a crashed run is simply re-generated (same seed, same
24
+ * messages).
25
+ *
26
+ * @example
27
+ * import { flow, testHarness } from '@winkjs/composer';
28
+ *
29
+ * const assetClass = {
30
+ * columns: {
31
+ * _harnessId: { type: 'int64' },
32
+ * temperature: { type: 'float64', resolution: 0.01 }
33
+ * }
34
+ * };
35
+ *
36
+ * flow( 'check' )
37
+ * .assetClass( assetClass )
38
+ * .source( testHarness, {
39
+ * messageTemplate: {
40
+ * seed: 42,
41
+ * messageCount: 100,
42
+ * fields: {
43
+ * temperature: { type: 'float64', range: [ 20, 30 ], resolution: 0.01 }
44
+ * }
45
+ * },
46
+ * assetClass
47
+ * } )
48
+ * .run();
49
+ */
50
+
51
+ import { start } from './start.js';
52
+ import { MESSAGE_TEMPLATE_SCHEMA, ASSET_CLASS_SCHEMA } from './validate.js';
53
+
54
+ /**
55
+ * Source identifier — used by the flow runtime for lookup.
56
+ * @type {string}
57
+ */
58
+ export const id = 'testHarness';
59
+
60
+ /**
61
+ * Crash-survival class per ADR-018. For a source the value describes
62
+ * the input it can recover after a disconnect: the harness recovers
63
+ * nothing — a crashed run is re-generated deterministically.
64
+ * @type {string}
65
+ */
66
+ export const durabilityClass = 'best-effort';
67
+
68
+ /**
69
+ * Schema for the harness's public config — the `messageTemplate`
70
+ * and `assetClass` it accepts, plus the caller-suppliable `onStatus`
71
+ * and `shutdownOnComplete`. Exposed for DSL-time validation.
72
+ *
73
+ * `_propertyNames` rejects unknown keys at DSL time (the validator's
74
+ * only unknown-key mechanism, validate.js:68-77) — added by the
75
+ * 2026-07-09 uniformity sweep. The retired `onComplete` key is
76
+ * rejected here: completion travels `onStatus` as
77
+ * `{phase: 'complete', count}` per ADR-018. Runtime-injected
78
+ * callbacks (`onMessage`, `onShutdown`) are deliberately absent.
79
+ *
80
+ * The harness's runtime entry (`start`) re-validates with
81
+ * `validateMessageTemplate` and `validateAssetClass` so any
82
+ * problem fails at startup with classified `err.code`.
83
+ */
84
+ export const configSchema = {
85
+ _propertyNames: [
86
+ 'messageTemplate',
87
+ 'assetClass',
88
+ 'onStatus',
89
+ 'shutdownOnComplete'
90
+ ],
91
+ messageTemplate: {
92
+ type: 'object',
93
+ required: true,
94
+ properties: MESSAGE_TEMPLATE_SCHEMA
95
+ },
96
+ assetClass: {
97
+ type: 'object',
98
+ required: true,
99
+ properties: ASSET_CLASS_SCHEMA
100
+ },
101
+ onStatus: {
102
+ type: 'function',
103
+ required: false,
104
+ error: 'onStatus must be a function'
105
+ },
106
+ shutdownOnComplete: {
107
+ type: 'boolean',
108
+ required: false,
109
+ error: 'shutdownOnComplete must be a boolean'
110
+ }
111
+ };
112
+
113
+ export { start };
114
+
115
+ export default { id, configSchema, durabilityClass, start };
@@ -0,0 +1,57 @@
1
+ // core/source-manager/test-harness/prng.js
2
+
3
+ /* eslint-disable no-bitwise -- Mulberry32 is fundamentally a bit-shuffling PRNG; bitwise ops are intentional and load-bearing. */
4
+
5
+ /**
6
+ * @fileoverview Seeded random number generator for testHarness.
7
+ *
8
+ * Tests need to be reproducible: the same seed must give the same
9
+ * sequence of numbers every run. We use Mulberry32 — a small, fast
10
+ * generator with 32-bit state. Good enough for test data; not
11
+ * suitable for anything cryptographic.
12
+ *
13
+ * Reference: https://github.com/bryc/code/blob/master/jshash/PRNGs.md
14
+ */
15
+
16
+ /**
17
+ * Builds a random-number generator from a numeric seed.
18
+ *
19
+ * @param {number} seed - any finite number; same seed → same sequence
20
+ * @returns {{next: function(): number, intInRange: function(number, number): number, floatInRange: function(number, number): number, pickFrom: function(Array): *}}
21
+ */
22
+ export const createPrng = function ( seed ) {
23
+ // Force the seed into an unsigned 32-bit integer so the maths
24
+ // below behaves the same on every platform.
25
+ let state = seed >>> 0;
26
+
27
+ const next = function () {
28
+ state = ( state + 0x6D2B79F5 ) | 0;
29
+ let t = state;
30
+ t = Math.imul( t ^ ( t >>> 15 ), t | 1 );
31
+ t ^= t + Math.imul( t ^ ( t >>> 7 ), t | 61 );
32
+ return ( ( t ^ ( t >>> 14 ) ) >>> 0 ) / 4294967296;
33
+ };
34
+
35
+ /**
36
+ * Whole number in the inclusive range [min, max].
37
+ */
38
+ const intInRange = function ( min, max ) {
39
+ return Math.floor( min + ( next() * ( ( max - min ) + 1 ) ) );
40
+ };
41
+
42
+ /**
43
+ * Decimal number in the half-open range [min, max).
44
+ */
45
+ const floatInRange = function ( min, max ) {
46
+ return min + ( next() * ( max - min ) );
47
+ };
48
+
49
+ /**
50
+ * Picks one element from a non-empty array.
51
+ */
52
+ const pickFrom = function ( arr ) {
53
+ return arr[ Math.floor( next() * arr.length ) ];
54
+ };
55
+
56
+ return { next, intInRange, floatInRange, pickFrom };
57
+ };