@mongrov/analytics 0.1.0-alpha.1 → 0.1.0-alpha.10

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 (347) hide show
  1. package/README.md +472 -0
  2. package/dist/core/context.d.ts +24 -0
  3. package/dist/core/context.d.ts.map +1 -0
  4. package/dist/core/context.js +30 -0
  5. package/dist/core/context.js.map +1 -0
  6. package/dist/core/engine.d.ts +56 -0
  7. package/dist/core/engine.d.ts.map +1 -0
  8. package/dist/core/engine.js +119 -0
  9. package/dist/core/engine.js.map +1 -0
  10. package/dist/core/errors.d.ts +1 -1
  11. package/dist/core/errors.d.ts.map +1 -1
  12. package/dist/core/errors.js.map +1 -1
  13. package/dist/core/extensions.d.ts +33 -0
  14. package/dist/core/extensions.d.ts.map +1 -0
  15. package/dist/core/extensions.js +58 -0
  16. package/dist/core/extensions.js.map +1 -0
  17. package/dist/core/factory.d.ts +22 -5
  18. package/dist/core/factory.d.ts.map +1 -1
  19. package/dist/core/factory.js +231 -6
  20. package/dist/core/factory.js.map +1 -1
  21. package/dist/core/hooks.d.ts +32 -13
  22. package/dist/core/hooks.d.ts.map +1 -1
  23. package/dist/core/hooks.js +137 -15
  24. package/dist/core/hooks.js.map +1 -1
  25. package/dist/core/index.d.ts +3 -0
  26. package/dist/core/index.d.ts.map +1 -1
  27. package/dist/core/index.js +3 -1
  28. package/dist/core/index.js.map +1 -1
  29. package/dist/core/logger.d.ts +25 -0
  30. package/dist/core/logger.d.ts.map +1 -0
  31. package/dist/core/logger.js +33 -0
  32. package/dist/core/logger.js.map +1 -0
  33. package/dist/core/machine.d.ts +174 -0
  34. package/dist/core/machine.d.ts.map +1 -0
  35. package/dist/core/machine.js +228 -0
  36. package/dist/core/machine.js.map +1 -0
  37. package/dist/core/metric_metadata.d.ts +128 -0
  38. package/dist/core/metric_metadata.d.ts.map +1 -0
  39. package/dist/core/metric_metadata.js +47 -0
  40. package/dist/core/metric_metadata.js.map +1 -0
  41. package/dist/core/migrations.d.ts +52 -0
  42. package/dist/core/migrations.d.ts.map +1 -0
  43. package/dist/core/migrations.js +66 -0
  44. package/dist/core/migrations.js.map +1 -0
  45. package/dist/core/persistence.d.ts +50 -0
  46. package/dist/core/persistence.d.ts.map +1 -0
  47. package/dist/core/persistence.js +80 -0
  48. package/dist/core/persistence.js.map +1 -0
  49. package/dist/core/retention.d.ts +90 -0
  50. package/dist/core/retention.d.ts.map +1 -0
  51. package/dist/core/retention.js +119 -0
  52. package/dist/core/retention.js.map +1 -0
  53. package/dist/core/schemas.d.ts +35 -0
  54. package/dist/core/schemas.d.ts.map +1 -0
  55. package/dist/core/schemas.js +209 -0
  56. package/dist/core/schemas.js.map +1 -0
  57. package/dist/core/types.d.ts +10 -0
  58. package/dist/core/types.d.ts.map +1 -1
  59. package/dist/core/warehouse.d.ts +58 -0
  60. package/dist/core/warehouse.d.ts.map +1 -0
  61. package/dist/core/warehouse.js +116 -0
  62. package/dist/core/warehouse.js.map +1 -0
  63. package/dist/rules/__fakes__/fakeClock.d.ts +7 -0
  64. package/dist/rules/__fakes__/fakeClock.d.ts.map +1 -0
  65. package/dist/rules/__fakes__/fakeClock.js +13 -0
  66. package/dist/rules/__fakes__/fakeClock.js.map +1 -0
  67. package/dist/rules/__fakes__/fakeEngine.d.ts +12 -0
  68. package/dist/rules/__fakes__/fakeEngine.d.ts.map +1 -0
  69. package/dist/rules/__fakes__/fakeEngine.js +33 -0
  70. package/dist/rules/__fakes__/fakeEngine.js.map +1 -0
  71. package/dist/rules/__fakes__/fakeStorage.d.ts +7 -0
  72. package/dist/rules/__fakes__/fakeStorage.d.ts.map +1 -0
  73. package/dist/rules/__fakes__/fakeStorage.js +22 -0
  74. package/dist/rules/__fakes__/fakeStorage.js.map +1 -0
  75. package/dist/rules/compiler-cache.d.ts +16 -0
  76. package/dist/rules/compiler-cache.d.ts.map +1 -0
  77. package/dist/rules/compiler-cache.js +29 -0
  78. package/dist/rules/compiler-cache.js.map +1 -0
  79. package/dist/rules/compiler.d.ts +27 -0
  80. package/dist/rules/compiler.d.ts.map +1 -0
  81. package/dist/rules/compiler.js +210 -0
  82. package/dist/rules/compiler.js.map +1 -0
  83. package/dist/rules/defaults/index.d.ts +13 -0
  84. package/dist/rules/defaults/index.d.ts.map +1 -0
  85. package/dist/rules/defaults/index.js +13 -0
  86. package/dist/rules/defaults/index.js.map +1 -0
  87. package/dist/rules/defaults/loader.d.ts +18 -0
  88. package/dist/rules/defaults/loader.d.ts.map +1 -0
  89. package/dist/rules/defaults/loader.js +37 -0
  90. package/dist/rules/defaults/loader.js.map +1 -0
  91. package/dist/rules/defaults/luminx.d.ts +6 -0
  92. package/dist/rules/defaults/luminx.d.ts.map +1 -0
  93. package/dist/rules/defaults/luminx.js +64 -0
  94. package/dist/rules/defaults/luminx.js.map +1 -0
  95. package/dist/rules/defaults/viva.d.ts +6 -0
  96. package/dist/rules/defaults/viva.d.ts.map +1 -0
  97. package/dist/rules/defaults/viva.js +45 -0
  98. package/dist/rules/defaults/viva.js.map +1 -0
  99. package/dist/rules/defaults/yogaring.d.ts +6 -0
  100. package/dist/rules/defaults/yogaring.d.ts.map +1 -0
  101. package/dist/rules/defaults/yogaring.js +45 -0
  102. package/dist/rules/defaults/yogaring.js.map +1 -0
  103. package/dist/rules/defaults/ziva.d.ts +10 -0
  104. package/dist/rules/defaults/ziva.d.ts.map +1 -0
  105. package/dist/rules/defaults/ziva.js +87 -0
  106. package/dist/rules/defaults/ziva.js.map +1 -0
  107. package/dist/rules/emitter.d.ts +20 -0
  108. package/dist/rules/emitter.d.ts.map +1 -0
  109. package/dist/rules/emitter.js +32 -0
  110. package/dist/rules/emitter.js.map +1 -0
  111. package/dist/rules/evaluator.d.ts +44 -0
  112. package/dist/rules/evaluator.d.ts.map +1 -0
  113. package/dist/rules/evaluator.js +127 -0
  114. package/dist/rules/evaluator.js.map +1 -0
  115. package/dist/rules/factory.d.ts +13 -0
  116. package/dist/rules/factory.d.ts.map +1 -0
  117. package/dist/rules/factory.js +56 -0
  118. package/dist/rules/factory.js.map +1 -0
  119. package/dist/rules/hooks/useRuleRegistry.d.ts +16 -0
  120. package/dist/rules/hooks/useRuleRegistry.d.ts.map +1 -0
  121. package/dist/rules/hooks/useRuleRegistry.js +26 -0
  122. package/dist/rules/hooks/useRuleRegistry.js.map +1 -0
  123. package/dist/rules/hooks/useRuleViolations.d.ts +21 -0
  124. package/dist/rules/hooks/useRuleViolations.d.ts.map +1 -0
  125. package/dist/rules/hooks/useRuleViolations.js +32 -0
  126. package/dist/rules/hooks/useRuleViolations.js.map +1 -0
  127. package/dist/rules/index.d.ts +30 -1
  128. package/dist/rules/index.d.ts.map +1 -1
  129. package/dist/rules/index.js +36 -1
  130. package/dist/rules/index.js.map +1 -1
  131. package/dist/rules/registry.d.ts +32 -0
  132. package/dist/rules/registry.d.ts.map +1 -0
  133. package/dist/rules/registry.js +98 -0
  134. package/dist/rules/registry.js.map +1 -0
  135. package/dist/rules/schema.d.ts +221 -0
  136. package/dist/rules/schema.d.ts.map +1 -0
  137. package/dist/rules/schema.js +80 -0
  138. package/dist/rules/schema.js.map +1 -0
  139. package/dist/rules/throttle.d.ts +27 -0
  140. package/dist/rules/throttle.d.ts.map +1 -0
  141. package/dist/rules/throttle.js +76 -0
  142. package/dist/rules/throttle.js.map +1 -0
  143. package/dist/rules/types.d.ts +88 -0
  144. package/dist/rules/types.d.ts.map +1 -0
  145. package/dist/rules/types.js +9 -0
  146. package/dist/rules/types.js.map +1 -0
  147. package/dist/rules/validator.d.ts +27 -0
  148. package/dist/rules/validator.d.ts.map +1 -0
  149. package/dist/rules/validator.js +97 -0
  150. package/dist/rules/validator.js.map +1 -0
  151. package/dist/sync/buffer.d.ts +51 -0
  152. package/dist/sync/buffer.d.ts.map +1 -0
  153. package/dist/sync/buffer.js +188 -0
  154. package/dist/sync/buffer.js.map +1 -0
  155. package/dist/sync/context.d.ts +17 -0
  156. package/dist/sync/context.d.ts.map +1 -0
  157. package/dist/sync/context.js +23 -0
  158. package/dist/sync/context.js.map +1 -0
  159. package/dist/sync/errors.d.ts +19 -0
  160. package/dist/sync/errors.d.ts.map +1 -0
  161. package/dist/sync/errors.js +21 -0
  162. package/dist/sync/errors.js.map +1 -0
  163. package/dist/sync/events.d.ts +49 -0
  164. package/dist/sync/events.d.ts.map +1 -0
  165. package/dist/sync/events.js +46 -0
  166. package/dist/sync/events.js.map +1 -0
  167. package/dist/sync/factory.d.ts +65 -0
  168. package/dist/sync/factory.d.ts.map +1 -0
  169. package/dist/sync/factory.js +279 -0
  170. package/dist/sync/factory.js.map +1 -0
  171. package/dist/sync/fetcher.d.ts +74 -0
  172. package/dist/sync/fetcher.d.ts.map +1 -0
  173. package/dist/sync/fetcher.js +166 -0
  174. package/dist/sync/fetcher.js.map +1 -0
  175. package/dist/sync/flusher.d.ts +99 -0
  176. package/dist/sync/flusher.d.ts.map +1 -0
  177. package/dist/sync/flusher.js +246 -0
  178. package/dist/sync/flusher.js.map +1 -0
  179. package/dist/sync/hooks.d.ts +17 -0
  180. package/dist/sync/hooks.d.ts.map +1 -0
  181. package/dist/sync/hooks.js +48 -0
  182. package/dist/sync/hooks.js.map +1 -0
  183. package/dist/sync/index.d.ts +32 -1
  184. package/dist/sync/index.d.ts.map +1 -1
  185. package/dist/sync/index.js +36 -1
  186. package/dist/sync/index.js.map +1 -1
  187. package/dist/sync/manager.d.ts +48 -0
  188. package/dist/sync/manager.d.ts.map +1 -0
  189. package/dist/sync/manager.js +9 -0
  190. package/dist/sync/manager.js.map +1 -0
  191. package/dist/sync/mapper/activity.d.ts +31 -0
  192. package/dist/sync/mapper/activity.d.ts.map +1 -0
  193. package/dist/sync/mapper/activity.js +56 -0
  194. package/dist/sync/mapper/activity.js.map +1 -0
  195. package/dist/sync/mapper/battery.d.ts +12 -0
  196. package/dist/sync/mapper/battery.d.ts.map +1 -0
  197. package/dist/sync/mapper/battery.js +22 -0
  198. package/dist/sync/mapper/battery.js.map +1 -0
  199. package/dist/sync/mapper/firmware.d.ts +28 -0
  200. package/dist/sync/mapper/firmware.d.ts.map +1 -0
  201. package/dist/sync/mapper/firmware.js +43 -0
  202. package/dist/sync/mapper/firmware.js.map +1 -0
  203. package/dist/sync/mapper/heart-rate.d.ts +10 -0
  204. package/dist/sync/mapper/heart-rate.d.ts.map +1 -0
  205. package/dist/sync/mapper/heart-rate.js +19 -0
  206. package/dist/sync/mapper/heart-rate.js.map +1 -0
  207. package/dist/sync/mapper/hrv.d.ts +22 -0
  208. package/dist/sync/mapper/hrv.d.ts.map +1 -0
  209. package/dist/sync/mapper/hrv.js +56 -0
  210. package/dist/sync/mapper/hrv.js.map +1 -0
  211. package/dist/sync/mapper/ring-config.d.ts +33 -0
  212. package/dist/sync/mapper/ring-config.d.ts.map +1 -0
  213. package/dist/sync/mapper/ring-config.js +48 -0
  214. package/dist/sync/mapper/ring-config.js.map +1 -0
  215. package/dist/sync/mapper/sleep.d.ts +32 -0
  216. package/dist/sync/mapper/sleep.d.ts.map +1 -0
  217. package/dist/sync/mapper/sleep.js +103 -0
  218. package/dist/sync/mapper/sleep.js.map +1 -0
  219. package/dist/sync/mapper/spo2.d.ts +9 -0
  220. package/dist/sync/mapper/spo2.d.ts.map +1 -0
  221. package/dist/sync/mapper/spo2.js +18 -0
  222. package/dist/sync/mapper/spo2.js.map +1 -0
  223. package/dist/sync/mapper/temperature.d.ts +10 -0
  224. package/dist/sync/mapper/temperature.d.ts.map +1 -0
  225. package/dist/sync/mapper/temperature.js +19 -0
  226. package/dist/sync/mapper/temperature.js.map +1 -0
  227. package/dist/sync/mapper/time.d.ts +49 -0
  228. package/dist/sync/mapper/time.d.ts.map +1 -0
  229. package/dist/sync/mapper/time.js +107 -0
  230. package/dist/sync/mapper/time.js.map +1 -0
  231. package/dist/sync/mapper/types.d.ts +152 -0
  232. package/dist/sync/mapper/types.d.ts.map +1 -0
  233. package/dist/sync/mapper/types.js +15 -0
  234. package/dist/sync/mapper/types.js.map +1 -0
  235. package/dist/sync/overflow.d.ts +30 -0
  236. package/dist/sync/overflow.d.ts.map +1 -0
  237. package/dist/sync/overflow.js +114 -0
  238. package/dist/sync/overflow.js.map +1 -0
  239. package/dist/sync/pusher.d.ts +59 -0
  240. package/dist/sync/pusher.d.ts.map +1 -0
  241. package/dist/sync/pusher.js +115 -0
  242. package/dist/sync/pusher.js.map +1 -0
  243. package/dist/sync/scheduler.d.ts +84 -0
  244. package/dist/sync/scheduler.d.ts.map +1 -0
  245. package/dist/sync/scheduler.js +132 -0
  246. package/dist/sync/scheduler.js.map +1 -0
  247. package/dist/sync/triggers.d.ts +64 -0
  248. package/dist/sync/triggers.d.ts.map +1 -0
  249. package/dist/sync/triggers.js +113 -0
  250. package/dist/sync/triggers.js.map +1 -0
  251. package/dist/sync/types.d.ts +50 -0
  252. package/dist/sync/types.d.ts.map +1 -0
  253. package/dist/sync/types.js +8 -0
  254. package/dist/sync/types.js.map +1 -0
  255. package/dist/sync/watermark.d.ts +32 -0
  256. package/dist/sync/watermark.d.ts.map +1 -0
  257. package/dist/sync/watermark.js +61 -0
  258. package/dist/sync/watermark.js.map +1 -0
  259. package/dist/tools/__fakes__/engine.d.ts +25 -0
  260. package/dist/tools/__fakes__/engine.d.ts.map +1 -0
  261. package/dist/tools/__fakes__/engine.js +37 -0
  262. package/dist/tools/__fakes__/engine.js.map +1 -0
  263. package/dist/tools/audit.d.ts +31 -0
  264. package/dist/tools/audit.d.ts.map +1 -0
  265. package/dist/tools/audit.js +157 -0
  266. package/dist/tools/audit.js.map +1 -0
  267. package/dist/tools/authorize.d.ts +36 -0
  268. package/dist/tools/authorize.d.ts.map +1 -0
  269. package/dist/tools/authorize.js +87 -0
  270. package/dist/tools/authorize.js.map +1 -0
  271. package/dist/tools/budget.d.ts +15 -0
  272. package/dist/tools/budget.d.ts.map +1 -0
  273. package/dist/tools/budget.js +64 -0
  274. package/dist/tools/budget.js.map +1 -0
  275. package/dist/tools/factory.d.ts +43 -0
  276. package/dist/tools/factory.d.ts.map +1 -0
  277. package/dist/tools/factory.js +137 -0
  278. package/dist/tools/factory.js.map +1 -0
  279. package/dist/tools/formatters.d.ts +17 -0
  280. package/dist/tools/formatters.d.ts.map +1 -0
  281. package/dist/tools/formatters.js +30 -0
  282. package/dist/tools/formatters.js.map +1 -0
  283. package/dist/tools/impls/activity.d.ts +15 -0
  284. package/dist/tools/impls/activity.d.ts.map +1 -0
  285. package/dist/tools/impls/activity.js +61 -0
  286. package/dist/tools/impls/activity.js.map +1 -0
  287. package/dist/tools/impls/anomaly.d.ts +21 -0
  288. package/dist/tools/impls/anomaly.d.ts.map +1 -0
  289. package/dist/tools/impls/anomaly.js +70 -0
  290. package/dist/tools/impls/anomaly.js.map +1 -0
  291. package/dist/tools/impls/compare.d.ts +21 -0
  292. package/dist/tools/impls/compare.d.ts.map +1 -0
  293. package/dist/tools/impls/compare.js +68 -0
  294. package/dist/tools/impls/compare.js.map +1 -0
  295. package/dist/tools/impls/hrv.d.ts +15 -0
  296. package/dist/tools/impls/hrv.d.ts.map +1 -0
  297. package/dist/tools/impls/hrv.js +36 -0
  298. package/dist/tools/impls/hrv.js.map +1 -0
  299. package/dist/tools/impls/insights.d.ts +18 -0
  300. package/dist/tools/impls/insights.d.ts.map +1 -0
  301. package/dist/tools/impls/insights.js +41 -0
  302. package/dist/tools/impls/insights.js.map +1 -0
  303. package/dist/tools/impls/sleep.d.ts +15 -0
  304. package/dist/tools/impls/sleep.d.ts.map +1 -0
  305. package/dist/tools/impls/sleep.js +43 -0
  306. package/dist/tools/impls/sleep.js.map +1 -0
  307. package/dist/tools/index.d.ts +13 -1
  308. package/dist/tools/index.d.ts.map +1 -1
  309. package/dist/tools/index.js +21 -1
  310. package/dist/tools/index.js.map +1 -1
  311. package/dist/tools/mcp/adapter.d.ts +33 -0
  312. package/dist/tools/mcp/adapter.d.ts.map +1 -0
  313. package/dist/tools/mcp/adapter.js +56 -0
  314. package/dist/tools/mcp/adapter.js.map +1 -0
  315. package/dist/tools/mcp/guard.d.ts +17 -0
  316. package/dist/tools/mcp/guard.d.ts.map +1 -0
  317. package/dist/tools/mcp/guard.js +22 -0
  318. package/dist/tools/mcp/guard.js.map +1 -0
  319. package/dist/tools/mcp/index.d.ts +20 -0
  320. package/dist/tools/mcp/index.d.ts.map +1 -0
  321. package/dist/tools/mcp/index.js +16 -0
  322. package/dist/tools/mcp/index.js.map +1 -0
  323. package/dist/tools/mcp/server.d.ts +39 -0
  324. package/dist/tools/mcp/server.d.ts.map +1 -0
  325. package/dist/tools/mcp/server.js +70 -0
  326. package/dist/tools/mcp/server.js.map +1 -0
  327. package/dist/tools/mcp/transports/http.d.ts +54 -0
  328. package/dist/tools/mcp/transports/http.d.ts.map +1 -0
  329. package/dist/tools/mcp/transports/http.js +133 -0
  330. package/dist/tools/mcp/transports/http.js.map +1 -0
  331. package/dist/tools/mcp/transports/stdio.d.ts +22 -0
  332. package/dist/tools/mcp/transports/stdio.d.ts.map +1 -0
  333. package/dist/tools/mcp/transports/stdio.js +19 -0
  334. package/dist/tools/mcp/transports/stdio.js.map +1 -0
  335. package/dist/tools/rate-limit.d.ts +26 -0
  336. package/dist/tools/rate-limit.d.ts.map +1 -0
  337. package/dist/tools/rate-limit.js +67 -0
  338. package/dist/tools/rate-limit.js.map +1 -0
  339. package/dist/tools/types.d.ts +121 -0
  340. package/dist/tools/types.d.ts.map +1 -0
  341. package/dist/tools/types.js +16 -0
  342. package/dist/tools/types.js.map +1 -0
  343. package/dist/tools/wrap.d.ts +53 -0
  344. package/dist/tools/wrap.d.ts.map +1 -0
  345. package/dist/tools/wrap.js +135 -0
  346. package/dist/tools/wrap.js.map +1 -0
  347. package/package.json +28 -2
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Zod schema for structured threshold rules (spec §Rule schema).
3
+ *
4
+ * The `metric` enum is generated at module load from the frozen
5
+ * `METRIC_METADATA` registry — only metrics with `exposure: 'full'` are
6
+ * accepted. Rules referencing `collected_only` metrics can still ship
7
+ * via `rawSql` + `exposureOverride: true` (see validator.ts).
8
+ */
9
+ import { z } from 'zod';
10
+ import { getExposedMetricIds } from '../core/metric_metadata';
11
+ /** Custom error class for rule-schema / validator failures. */
12
+ export class RuleValidationError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = 'RuleValidationError';
16
+ }
17
+ }
18
+ const exposedMetricIds = getExposedMetricIds();
19
+ export const WINDOWS = ['1h', '6h', '24h', '3d', '7d', '30d'];
20
+ export const AGGREGATIONS = ['avg', 'min', 'max', 'sum', 'last', 'count'];
21
+ export const COMPARES = [
22
+ 'less_than',
23
+ 'greater_than',
24
+ 'equals',
25
+ 'not_equals',
26
+ 'between',
27
+ ];
28
+ export const SEVERITIES = ['info', 'warn', 'critical'];
29
+ const TargetAbsolute = z.object({
30
+ type: z.literal('absolute'),
31
+ value: z.number(),
32
+ });
33
+ const TargetBaselinePercent = z.object({
34
+ type: z.literal('baseline_percent'),
35
+ windowDays: z.number().int().positive(),
36
+ percent: z.number().positive(),
37
+ });
38
+ const TargetBaselineStddev = z.object({
39
+ type: z.literal('baseline_stddev'),
40
+ windowDays: z.number().int().positive(),
41
+ stddevs: z.number(),
42
+ });
43
+ const TargetRange = z.object({
44
+ type: z.literal('range'),
45
+ min: z.number(),
46
+ max: z.number(),
47
+ });
48
+ export const TargetSchema = z.discriminatedUnion('type', [
49
+ TargetAbsolute,
50
+ TargetBaselinePercent,
51
+ TargetBaselineStddev,
52
+ TargetRange,
53
+ ]);
54
+ export const ThrottleSchema = z
55
+ .object({
56
+ minGapMinutes: z.number().nonnegative().default(60),
57
+ maxPerDay: z.number().int().positive().default(3),
58
+ })
59
+ .default({});
60
+ export const RuleSchema = z.object({
61
+ id: z.string().min(1),
62
+ brand: z.string().optional(),
63
+ name: z.string().min(1),
64
+ description: z.string().optional(),
65
+ metric: z.enum(exposedMetricIds),
66
+ window: z.enum(WINDOWS),
67
+ aggregation: z.enum(AGGREGATIONS).default('avg'),
68
+ compare: z.enum(COMPARES),
69
+ target: TargetSchema,
70
+ severity: z.enum(SEVERITIES),
71
+ throttle: ThrottleSchema,
72
+ rawSql: z.string().optional(),
73
+ rawSqlParams: z.array(z.string()).optional(),
74
+ /**
75
+ * Required-true when `rawSql` references `collected_only` columns
76
+ * (BP, vascular aging). Validator enforces.
77
+ */
78
+ exposureOverride: z.boolean().optional(),
79
+ });
80
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/rules/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * KV-backed throttle store for rule fires.
3
+ *
4
+ * Two keys per (rule, user):
5
+ * - `analytics:rules:throttle:{ruleId}:{userId}:last` → ISO date string
6
+ * - `analytics:rules:throttle:{ruleId}:{userId}:count:{yyyy-mm-dd}` → number
7
+ *
8
+ * Order of checks: min-gap first, then daily cap. Stale daily-count
9
+ * entries (from earlier days) are best-effort deleted on next-day reads.
10
+ *
11
+ * All time reads go through an injected `clock: () => Date` so tests can
12
+ * pin the wall clock. Production defaults to `() => new Date()`.
13
+ */
14
+ import type { KVStore } from '../core/types';
15
+ import type { Clock, RulesLogger } from './types';
16
+ import type { Throttle } from './schema';
17
+ export interface ThrottleStore {
18
+ isThrottled(ruleId: string, userId: string, throttle: Throttle): Promise<boolean>;
19
+ recordFire(ruleId: string, userId: string): Promise<void>;
20
+ }
21
+ export interface CreateThrottleConfig {
22
+ storage: KVStore;
23
+ clock?: Clock;
24
+ logger?: RulesLogger;
25
+ }
26
+ export declare function createThrottleStore({ storage, clock, logger, }: CreateThrottleConfig): ThrottleStore;
27
+ //# sourceMappingURL=throttle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../src/rules/throttle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAoBxC,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACjF,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,KAAwB,EACxB,MAAM,GACP,EAAE,oBAAoB,GAAG,aAAa,CAmDtC"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * KV-backed throttle store for rule fires.
3
+ *
4
+ * Two keys per (rule, user):
5
+ * - `analytics:rules:throttle:{ruleId}:{userId}:last` → ISO date string
6
+ * - `analytics:rules:throttle:{ruleId}:{userId}:count:{yyyy-mm-dd}` → number
7
+ *
8
+ * Order of checks: min-gap first, then daily cap. Stale daily-count
9
+ * entries (from earlier days) are best-effort deleted on next-day reads.
10
+ *
11
+ * All time reads go through an injected `clock: () => Date` so tests can
12
+ * pin the wall clock. Production defaults to `() => new Date()`.
13
+ */
14
+ const KEY_PREFIX = 'analytics:rules:throttle:';
15
+ function lastKey(ruleId, userId) {
16
+ return `${KEY_PREFIX}${ruleId}:${userId}:last`;
17
+ }
18
+ function countKey(ruleId, userId, ymd) {
19
+ return `${KEY_PREFIX}${ruleId}:${userId}:count:${ymd}`;
20
+ }
21
+ /** yyyy-mm-dd in UTC. */
22
+ function ymdOf(d) {
23
+ const y = d.getUTCFullYear();
24
+ const m = String(d.getUTCMonth() + 1).padStart(2, '0');
25
+ const day = String(d.getUTCDate()).padStart(2, '0');
26
+ return `${y}-${m}-${day}`;
27
+ }
28
+ export function createThrottleStore({ storage, clock = () => new Date(), logger, }) {
29
+ return {
30
+ async isThrottled(ruleId, userId, throttle) {
31
+ const now = clock();
32
+ const ymd = ymdOf(now);
33
+ // Min-gap check.
34
+ const lastRaw = await storage.get(lastKey(ruleId, userId));
35
+ if (lastRaw) {
36
+ const last = new Date(lastRaw);
37
+ const gapMs = now.getTime() - last.getTime();
38
+ const minGapMs = throttle.minGapMinutes * 60 * 1000;
39
+ if (gapMs < minGapMs) {
40
+ logger?.debug('rules.throttle: gap', {
41
+ ruleId,
42
+ userId,
43
+ gapMs,
44
+ minGapMs,
45
+ });
46
+ return true;
47
+ }
48
+ // Purge stale daily-count keys from prior days (best-effort).
49
+ const lastYmd = ymdOf(last);
50
+ if (lastYmd !== ymd) {
51
+ await storage.delete(countKey(ruleId, userId, lastYmd));
52
+ }
53
+ }
54
+ // Daily cap check.
55
+ const count = (await storage.get(countKey(ruleId, userId, ymd))) ?? 0;
56
+ if (count >= throttle.maxPerDay) {
57
+ logger?.debug('rules.throttle: daily cap', {
58
+ ruleId,
59
+ userId,
60
+ count,
61
+ maxPerDay: throttle.maxPerDay,
62
+ });
63
+ return true;
64
+ }
65
+ return false;
66
+ },
67
+ async recordFire(ruleId, userId) {
68
+ const now = clock();
69
+ const ymd = ymdOf(now);
70
+ await storage.set(lastKey(ruleId, userId), now.toISOString());
71
+ const count = (await storage.get(countKey(ruleId, userId, ymd))) ?? 0;
72
+ await storage.set(countKey(ruleId, userId, ymd), count + 1);
73
+ },
74
+ };
75
+ }
76
+ //# sourceMappingURL=throttle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throttle.js","sourceRoot":"","sources":["../../src/rules/throttle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Public runtime types for @mongrov/analytics/rules.
3
+ *
4
+ * Kept structural (no z.infer imports at value level) so downstream
5
+ * consumers don't need to pull the Zod schema module unless they
6
+ * validate rules themselves.
7
+ */
8
+ import type { AnalyticsEngine, FamilyMembersProvider, KVStore, Unsubscribe } from '../core/types';
9
+ import type { MetricId } from '../core/metric_metadata';
10
+ import type { TableName } from '../core/schemas';
11
+ import type { Rule } from './schema';
12
+ export type { Unsubscribe };
13
+ /** Rule severity mirrors the `insight.severity` column. */
14
+ export type RuleSeverity = 'info' | 'warn' | 'critical';
15
+ /**
16
+ * Subset of a flushed batch that the rules engine reads to gate work.
17
+ * Sync produces this shape after a successful flush.
18
+ *
19
+ * Named `FlushSummary` to avoid collision with sync's own `SensorBatch`
20
+ * (which is the pre-flush push envelope, not the post-flush summary).
21
+ */
22
+ export interface FlushSummary {
23
+ affectedUserIds: string[];
24
+ affectedTables: TableName[];
25
+ }
26
+ /**
27
+ * Compiled artifact for a rule. `sql` uses `$name` placeholders bound
28
+ * via `analytics.execute(sql, params)`; identifiers are already inlined
29
+ * as sanitized literals inside `sql`.
30
+ */
31
+ export interface CompiledRule {
32
+ ruleId: string;
33
+ metric: MetricId;
34
+ sql: string;
35
+ /** Statically-known params. Runtime binds `$userId`/`$brand`/`$familyId` on top. */
36
+ params: Record<string, string | number>;
37
+ description: string;
38
+ }
39
+ /** Structured violation delivered to `on('violation', handler)` subscribers. */
40
+ export interface RuleViolation {
41
+ ruleId: string;
42
+ ruleName: string;
43
+ severity: RuleSeverity;
44
+ userId: string;
45
+ familyId: string;
46
+ brand: string;
47
+ observedValue: number;
48
+ thresholdValue: number;
49
+ observedAt: Date;
50
+ evidence: Record<string, unknown>;
51
+ }
52
+ /** Structured logger (subset of AnalyticsLogger). */
53
+ export interface RulesLogger {
54
+ debug(message: string, meta?: Record<string, unknown>): void;
55
+ info(message: string, meta?: Record<string, unknown>): void;
56
+ warn(message: string, meta?: Record<string, unknown>): void;
57
+ error(message: string, meta?: Record<string, unknown>): void;
58
+ }
59
+ /** Injected by tests; production defaults to `new Date()`. */
60
+ export type Clock = () => Date;
61
+ /** Config for `createRulesEngine`. */
62
+ export interface RulesEngineConfig {
63
+ analytics: AnalyticsEngine;
64
+ storage: KVStore;
65
+ familyMembersProvider: FamilyMembersProvider;
66
+ brand: string;
67
+ /** Family id for `onSchedule` — v0.1.0 single-user apps pass the userId. */
68
+ familyId: string;
69
+ logger?: RulesLogger;
70
+ clock?: Clock;
71
+ }
72
+ /** Public rules engine surface. */
73
+ export interface RulesEngine {
74
+ register(rules: Rule[]): Promise<void>;
75
+ enable(ruleId: string): Promise<void>;
76
+ disable(ruleId: string): Promise<void>;
77
+ list(): Rule[];
78
+ getActive(): Rule[];
79
+ evaluateOnBatch(batch: FlushSummary): Promise<RuleViolation[]>;
80
+ evaluateScheduled(): Promise<RuleViolation[]>;
81
+ on(event: 'violation', handler: (v: RuleViolation) => void): Unsubscribe;
82
+ /**
83
+ * Subscribe to registry mutations (register / enable / disable). Backs the
84
+ * `useRuleRegistry` hook via `useSyncExternalStore`.
85
+ */
86
+ subscribeRegistry(listener: () => void): Unsubscribe;
87
+ }
88
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Public runtime types for @mongrov/analytics/rules.
3
+ *
4
+ * Kept structural (no z.infer imports at value level) so downstream
5
+ * consumers don't need to pull the Zod schema module unless they
6
+ * validate rules themselves.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Static validation for rules — sampling-minimum + rawSql exposure check.
3
+ *
4
+ * Called at `register()` time (before compilation). Fails fast with a
5
+ * `RuleValidationError` whose message includes the metric's sampling
6
+ * cadence + the minimum viable window.
7
+ */
8
+ import { type MetricId } from '../core/metric_metadata';
9
+ import type { RulesLogger } from './types';
10
+ import { type Rule, type Window } from './schema';
11
+ /**
12
+ * Allow-list of windows compatible with `metric.sampling_minutes`.
13
+ * A window is compatible iff its duration ≥ (2 × sampling cadence),
14
+ * i.e. the query is guaranteed to see at least two samples worst-case.
15
+ * `per_session` metrics (sleep_score, sleep_total_minutes) require ≥ 3d.
16
+ */
17
+ export declare function allowedWindowsFor(metric: MetricId): Window[];
18
+ /**
19
+ * Validate a parsed rule. Throws `RuleValidationError` on any of:
20
+ * - window smaller than the metric sampling cadence allows,
21
+ * - rawSql references a `collected_only` column without `exposureOverride: true`.
22
+ * When rawSql is present with `exposureOverride: true`, emits a warn via `logger`.
23
+ */
24
+ export declare function validateRule(rule: Rule, logger?: RulesLogger): void;
25
+ /** Scan a rawSql body for whole-word references to collected-only columns. */
26
+ export declare function referencedCollectedOnlyColumns(sql: string): string[];
27
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/rules/validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,KAAK,IAAI,EAAuB,KAAK,MAAM,EAAW,MAAM,UAAU,CAAA;AA4B/E;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,EAAE,CAS5D;AAgBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAwBnE;AAED,8EAA8E;AAC9E,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAOpE"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Static validation for rules — sampling-minimum + rawSql exposure check.
3
+ *
4
+ * Called at `register()` time (before compilation). Fails fast with a
5
+ * `RuleValidationError` whose message includes the metric's sampling
6
+ * cadence + the minimum viable window.
7
+ */
8
+ import { METRIC_METADATA } from '../core/metric_metadata';
9
+ import { RuleValidationError, WINDOWS } from './schema';
10
+ /** Window durations in minutes. */
11
+ const WINDOW_MINUTES = {
12
+ '1h': 60,
13
+ '6h': 6 * 60,
14
+ '24h': 24 * 60,
15
+ '3d': 3 * 24 * 60,
16
+ '7d': 7 * 24 * 60,
17
+ '30d': 30 * 24 * 60,
18
+ };
19
+ /**
20
+ * Explicit table per spec §Sampling-minimum validator. The remaining
21
+ * exposed metrics fall through to a derived allow-list built from
22
+ * `METRIC_METADATA[id].sampling_minutes` (see `allowedWindowsFor`).
23
+ */
24
+ const EXPLICIT_SAMPLING_MINIMUMS = {
25
+ hrv_ms: ['24h', '3d', '7d', '30d'],
26
+ stress: ['6h', '24h', '3d', '7d', '30d'],
27
+ hr_bpm: ['1h', '6h', '24h', '3d', '7d', '30d'],
28
+ spo2: ['6h', '24h', '3d', '7d', '30d'],
29
+ temp_c: ['24h', '3d', '7d', '30d'],
30
+ activity_steps: ['1h', '6h', '24h', '3d', '7d', '30d'],
31
+ sleep_total_minutes: ['3d', '7d'],
32
+ device_battery: ['1h', '6h', '24h'],
33
+ };
34
+ /**
35
+ * Allow-list of windows compatible with `metric.sampling_minutes`.
36
+ * A window is compatible iff its duration ≥ (2 × sampling cadence),
37
+ * i.e. the query is guaranteed to see at least two samples worst-case.
38
+ * `per_session` metrics (sleep_score, sleep_total_minutes) require ≥ 3d.
39
+ */
40
+ export function allowedWindowsFor(metric) {
41
+ const explicit = EXPLICIT_SAMPLING_MINIMUMS[metric];
42
+ if (explicit)
43
+ return explicit;
44
+ const cadence = METRIC_METADATA[metric].sampling_minutes;
45
+ if (cadence === 'per_session') {
46
+ return ['3d', '7d', '30d'];
47
+ }
48
+ const minWindow = cadence * 2;
49
+ return WINDOWS.filter(w => WINDOW_MINUTES[w] >= minWindow);
50
+ }
51
+ /** Columns whose metric is marked `collected_only`. */
52
+ const COLLECTED_ONLY_COLUMNS = new Set(Object.values(METRIC_METADATA)
53
+ .filter(entry => entry.exposure === 'collected_only')
54
+ .map(entry => entry.column));
55
+ function samplingLabel(metric) {
56
+ const cadence = METRIC_METADATA[metric].sampling_minutes;
57
+ return cadence === 'per_session'
58
+ ? 'once per sleep session'
59
+ : `every ${cadence}min`;
60
+ }
61
+ /**
62
+ * Validate a parsed rule. Throws `RuleValidationError` on any of:
63
+ * - window smaller than the metric sampling cadence allows,
64
+ * - rawSql references a `collected_only` column without `exposureOverride: true`.
65
+ * When rawSql is present with `exposureOverride: true`, emits a warn via `logger`.
66
+ */
67
+ export function validateRule(rule, logger) {
68
+ const allowed = allowedWindowsFor(rule.metric);
69
+ if (!allowed.includes(rule.window)) {
70
+ throw new RuleValidationError(`Rule ${rule.id}: metric ${rule.metric} sampled ${samplingLabel(rule.metric)}. `
71
+ + `Window ${rule.window} too small. Minimum viable: ${allowed[0]}.`);
72
+ }
73
+ if (rule.rawSql) {
74
+ const references = referencedCollectedOnlyColumns(rule.rawSql);
75
+ if (references.length > 0 && !rule.exposureOverride) {
76
+ throw new RuleValidationError(`Rule ${rule.id}: rawSql references collected_only column(s) [${references.join(', ')}]. `
77
+ + `Set exposureOverride: true to acknowledge.`);
78
+ }
79
+ if (rule.exposureOverride && logger) {
80
+ logger.warn('rules.validator: rawSql exposureOverride active', {
81
+ ruleId: rule.id,
82
+ columns: references,
83
+ });
84
+ }
85
+ }
86
+ }
87
+ /** Scan a rawSql body for whole-word references to collected-only columns. */
88
+ export function referencedCollectedOnlyColumns(sql) {
89
+ const hits = new Set();
90
+ for (const col of COLLECTED_ONLY_COLUMNS) {
91
+ const pattern = new RegExp(`\\b${col}\\b`);
92
+ if (pattern.test(sql))
93
+ hits.add(col);
94
+ }
95
+ return Array.from(hits);
96
+ }
97
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/rules/validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Sensor buffer (T-10 + T-12).
3
+ *
4
+ * In-memory ring per table with byte-budget accounting and pluggable overflow
5
+ * policies. All ops are synchronous except `push` (which may await overflow
6
+ * writes when the ring exceeds its byte budget).
7
+ *
8
+ * Byte accounting: we estimate a row's contribution as
9
+ * `JSON.stringify(rows).length` at push time. This is a rough proxy for the
10
+ * DuckDB Appender's actual memory footprint but is stable and cheap. The
11
+ * budget is a soft limit — one push at a time can exceed it before overflow
12
+ * fires.
13
+ *
14
+ * Overflow policies (T-12):
15
+ * - `drop-oldest`: when the ring is full, spill the oldest ring entries to
16
+ * `OverflowStore` until the incoming push fits in memory. Overflow is
17
+ * durable (MMKV via KVStore) so nothing is actually dropped.
18
+ * - `drop-newest`: reject the incoming push and increment a dropped
19
+ * counter. Warning event is emitted via the injected `onDrop` callback.
20
+ * Data is lost — used only when the app opts in for battery-only sync.
21
+ * - `block`: `push` awaits an external drain. Implemented via a
22
+ * `Promise`-based backpressure signal.
23
+ */
24
+ import type { OverflowStore } from './overflow';
25
+ import type { BufferEntry, BufferSize, OverflowPolicy, SensorBatch } from './types';
26
+ export interface SensorBufferConfig {
27
+ overflow: OverflowStore;
28
+ maxBufferBytes?: number;
29
+ policy?: OverflowPolicy;
30
+ /** Fired when a `drop-newest` policy discards an incoming push. */
31
+ onDrop?: (entry: {
32
+ table: string;
33
+ droppedRowCount: number;
34
+ reason: 'drop-newest';
35
+ }) => void;
36
+ /** Optional clock injection for deterministic tests. */
37
+ now?: () => number;
38
+ }
39
+ export declare class SensorBuffer {
40
+ #private;
41
+ constructor(config: SensorBufferConfig);
42
+ push(batch: SensorBatch): Promise<void>;
43
+ /**
44
+ * Drain the buffer for a single table. Overflow entries are drained first
45
+ * so FIFO order across in-memory + overflow is preserved.
46
+ */
47
+ drain(table: string): Promise<BufferEntry[]>;
48
+ size(table?: string): Promise<BufferSize>;
49
+ clear(table?: string): Promise<void>;
50
+ }
51
+ //# sourceMappingURL=buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../src/sync/buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,cAAc,EACd,WAAW,EACZ,MAAM,SAAS,CAAA;AAIhB,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,aAAa,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,mEAAmE;IACnE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE;QACf,KAAK,EAAE,MAAM,CAAA;QACb,eAAe,EAAE,MAAM,CAAA;QACvB,MAAM,EAAE,aAAa,CAAA;KACtB,KAAK,IAAI,CAAA;IACV,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,qBAAa,YAAY;;gBAWX,MAAM,EAAE,kBAAkB;IAQhC,IAAI,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAa7C;;;OAGG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAU5C,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAkBzC,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAqG3C"}