@mongrov/analytics 0.1.0-alpha.0 → 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 (348) 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 +43 -16
  348. package/LICENSE +0 -21
@@ -0,0 +1,279 @@
1
+ /**
2
+ * T-26 — `createSyncManager()` factory.
3
+ *
4
+ * Zod-validates config at construction time (fail-fast), then assembles the
5
+ * full sync stack:
6
+ *
7
+ * OverflowStore ← storage KV
8
+ * SensorBuffer ← OverflowStore + policy + byte budget
9
+ * BatchFlusher ← engine + buffer + columnOrder + optional event bus
10
+ * FlushTriggers ← buffer + flusher + row/age thresholds
11
+ * WatermarkStore← storage KV
12
+ * R2Pusher ← engine + WatermarkStore + optional refreshToken + event bus
13
+ * R2Fetcher ← engine + WatermarkStore + tables + prefetchPolicy
14
+ * SyncScheduler ← wires flush + push + fetch into a background cycle
15
+ *
16
+ * The returned `SyncManager` exposes a compact `SensorSink` + scheduler
17
+ * controls; hooks in `hooks.ts` subscribe to state transitions.
18
+ */
19
+ import { z } from 'zod';
20
+ import { SensorBuffer } from './buffer';
21
+ import { bindFlushEvents, bindPushEvents } from './events';
22
+ import { R2Fetcher } from './fetcher';
23
+ import { BatchFlusher } from './flusher';
24
+ import { mapFirmwareExport } from './mapper/firmware';
25
+ import { OverflowStore } from './overflow';
26
+ import { R2Pusher } from './pusher';
27
+ import { SyncScheduler, } from './scheduler';
28
+ import { FlushTriggers } from './triggers';
29
+ import { WatermarkStore } from './watermark';
30
+ const overflowPolicyEnum = z.enum(['drop-oldest', 'drop-newest', 'block']);
31
+ const prefetchPolicySchema = z.discriminatedUnion('kind', [
32
+ z.object({ kind: z.literal('all-family-on-attach'), windowDays: z.number().int().positive() }),
33
+ z.object({
34
+ kind: z.literal('recent-active-only'),
35
+ activeDays: z.number().int().positive(),
36
+ windowDays: z.number().int().positive(),
37
+ }),
38
+ z.object({ kind: z.literal('lazy') }),
39
+ ]);
40
+ const configSchema = z.object({
41
+ tables: z.array(z.string().min(1)).min(1),
42
+ columnOrder: z.record(z.string(), z.array(z.string()).readonly()),
43
+ prefetchPolicy: prefetchPolicySchema,
44
+ flush: z.object({
45
+ maxRows: z.number().int().positive().optional(),
46
+ maxAgeMs: z.number().int().positive().optional(),
47
+ concurrency: z.number().int().positive().optional(),
48
+ }).optional(),
49
+ overflow: z.object({
50
+ maxBufferBytes: z.number().int().positive().optional(),
51
+ policy: overflowPolicyEnum.optional(),
52
+ }).optional(),
53
+ scheduler: z.object({
54
+ requiresCharging: z.boolean().optional(),
55
+ requiresWifi: z.boolean().optional(),
56
+ taskName: z.string().optional(),
57
+ }).optional(),
58
+ });
59
+ /**
60
+ * Assemble a fully wired `SyncManager`. Throws `z.ZodError` if the config is
61
+ * malformed (e.g., missing columnOrder entries or negative row limits).
62
+ */
63
+ export function createSyncManager(config) {
64
+ configSchema.parse({
65
+ tables: config.tables,
66
+ columnOrder: config.columnOrder,
67
+ prefetchPolicy: config.prefetchPolicy,
68
+ flush: config.flush,
69
+ overflow: config.overflow,
70
+ scheduler: config.scheduler,
71
+ });
72
+ const overflow = new OverflowStore(config.storage);
73
+ const buffer = new SensorBuffer({
74
+ overflow,
75
+ maxBufferBytes: config.overflow?.maxBufferBytes,
76
+ policy: config.overflow?.policy,
77
+ });
78
+ const progress = { pendingByTable: {} };
79
+ const busFlushEmit = config.eventBus ? bindFlushEvents(config.eventBus) : undefined;
80
+ // Compose the flusher emitter with local progress tracking so hooks + the
81
+ // event bus both see every event. When a rules engine is provided, `flushed`
82
+ // events also drive `evaluateOnBatch` — fire-and-forget so a slow evaluator
83
+ // never stalls sync.
84
+ const rulesEngine = config.rulesEngine;
85
+ const rulesLogger = config.logger;
86
+ const flushEmitter = (event) => {
87
+ if (event.type === 'flushed') {
88
+ progress.pendingByTable[event.payload.table] = 0;
89
+ progress.lastFlushAt = Date.now();
90
+ if (rulesEngine && event.payload.affectedUserIds.length > 0) {
91
+ rulesEngine.evaluateOnBatch({
92
+ affectedUserIds: event.payload.affectedUserIds,
93
+ affectedTables: [event.payload.table],
94
+ }).catch((err) => {
95
+ rulesLogger?.warn('sync.factory: rulesEngine.evaluateOnBatch threw', {
96
+ table: event.payload.table,
97
+ err: err instanceof Error ? err.message : String(err),
98
+ });
99
+ });
100
+ }
101
+ }
102
+ busFlushEmit?.(event);
103
+ };
104
+ const engineAsHybrid = config.analytics;
105
+ const flusher = new BatchFlusher({
106
+ engine: engineAsHybrid,
107
+ buffer,
108
+ columnOrder: config.columnOrder,
109
+ concurrency: config.flush?.concurrency,
110
+ emit: flushEmitter,
111
+ });
112
+ const triggers = new FlushTriggers({
113
+ buffer,
114
+ flusher,
115
+ maxRows: config.flush?.maxRows,
116
+ maxAgeMs: config.flush?.maxAgeMs,
117
+ });
118
+ const watermark = new WatermarkStore({ kv: config.storage });
119
+ const busPushEmit = config.eventBus ? bindPushEvents(config.eventBus) : undefined;
120
+ const pusher = new R2Pusher({
121
+ engine: engineAsHybrid,
122
+ watermark,
123
+ refreshToken: config.refreshToken,
124
+ emit: (result) => {
125
+ if (result.ok && result.rowsPushed > 0) {
126
+ progress.lastSyncCompleteAt = Date.now();
127
+ }
128
+ busPushEmit?.(result);
129
+ },
130
+ });
131
+ const fetcher = new R2Fetcher({
132
+ engine: engineAsHybrid,
133
+ watermark,
134
+ tables: [...config.tables],
135
+ });
136
+ const scheduler = new SyncScheduler({
137
+ backgroundTask: config.backgroundTask ?? noopBackgroundTask(),
138
+ constraints: config.constraints ?? alwaysAllowedConstraints(),
139
+ coordinator: {
140
+ flushAll: async () => {
141
+ await Promise.all(config.tables.map(t => flusher.flush(t, 'scheduled')));
142
+ },
143
+ pushAll: async (tables, ctx) => pusher.pushAll(tables, ctx),
144
+ fetchIncremental: async (ctx) => fetcher.fetchIncremental(ctx),
145
+ // Scheduled rules pass runs after the cycle's flush/push/fetch. Fire-
146
+ // and-forget — a throw here would surface as a cycle error, but the
147
+ // catch keeps the scheduler idle.
148
+ onCycleComplete: rulesEngine
149
+ ? async () => {
150
+ try {
151
+ await rulesEngine.evaluateScheduled();
152
+ }
153
+ catch (err) {
154
+ rulesLogger?.warn('sync.factory: rulesEngine.evaluateScheduled threw', {
155
+ err: err instanceof Error ? err.message : String(err),
156
+ });
157
+ }
158
+ }
159
+ : undefined,
160
+ },
161
+ tables: [...config.tables],
162
+ ctx: config.ctx,
163
+ taskName: config.scheduler?.taskName,
164
+ constraintPolicy: {
165
+ requireCharging: config.scheduler?.requiresCharging,
166
+ requireWifi: config.scheduler?.requiresWifi,
167
+ },
168
+ logger: config.logger,
169
+ });
170
+ const state = {
171
+ scheduler: 'idle',
172
+ };
173
+ const subscribers = new Set();
174
+ const notify = () => {
175
+ for (const fn of subscribers)
176
+ fn(state);
177
+ };
178
+ scheduler.subscribe((next) => {
179
+ state.scheduler = next;
180
+ notify();
181
+ });
182
+ const sink = createSensorSink({ buffer, flusher, triggers, tables: config.tables });
183
+ return {
184
+ analytics: config.analytics,
185
+ sink,
186
+ flusher,
187
+ pusher,
188
+ fetcher,
189
+ scheduler,
190
+ state: () => ({ ...state }),
191
+ progress: () => ({
192
+ pendingByTable: { ...progress.pendingByTable },
193
+ lastFlushAt: progress.lastFlushAt,
194
+ lastSyncCompleteAt: progress.lastSyncCompleteAt,
195
+ }),
196
+ subscribe: (fn) => {
197
+ subscribers.add(fn);
198
+ return () => subscribers.delete(fn);
199
+ },
200
+ start: async () => {
201
+ await scheduler.start();
202
+ },
203
+ stop: async () => {
204
+ await scheduler.stop();
205
+ },
206
+ triggerNow: () => scheduler.triggerNow(),
207
+ prefetch: (ctx) => fetcher.prefetchOnAttach(ctx, config.prefetchPolicy),
208
+ };
209
+ }
210
+ function createSensorSink({ buffer, flusher, triggers, tables }) {
211
+ return {
212
+ push: async (batch) => {
213
+ await buffer.push(batch);
214
+ triggers.noteEnqueue(batch.table, Date.now());
215
+ },
216
+ pushFirmware: async (fw, ctx) => {
217
+ const mapped = mapFirmwareExport(fw, ctx);
218
+ const asRows = (rows) => rows;
219
+ const mappedTables = [
220
+ ['hrv', asRows(mapped.hrv)],
221
+ ['heart_rate', asRows(mapped.heart_rate)],
222
+ ['spo2', asRows(mapped.spo2)],
223
+ ['temperature', asRows(mapped.temperature)],
224
+ ['activity', asRows(mapped.activity)],
225
+ ['activity_bucket', asRows(mapped.activity_bucket)],
226
+ ['sleep_session', asRows(mapped.sleep_session)],
227
+ ['sleep_stage', asRows(mapped.sleep_stage)],
228
+ ['sleep_raw', asRows(mapped.sleep_raw)],
229
+ ['device_event', asRows(mapped.device_event)],
230
+ ['device_config', asRows(mapped.device_config)],
231
+ ];
232
+ for (const [table, rows] of mappedTables) {
233
+ if (!rows || rows.length === 0)
234
+ continue;
235
+ await buffer.push({
236
+ table,
237
+ brand: ctx.brand,
238
+ familyId: ctx.familyId,
239
+ userId: ctx.userId,
240
+ deviceId: ctx.deviceId,
241
+ rows,
242
+ });
243
+ triggers.noteEnqueue(table, Date.now());
244
+ }
245
+ },
246
+ flush: async () => {
247
+ const results = [];
248
+ for (const table of tables) {
249
+ results.push(await flusher.flush(table, 'manual'));
250
+ triggers.noteDrain(table);
251
+ }
252
+ return results;
253
+ },
254
+ pendingRowCount: async (table) => {
255
+ const s = await buffer.size(table);
256
+ return s.inMemory + s.overflow;
257
+ },
258
+ clear: async () => {
259
+ await buffer.clear();
260
+ for (const t of tables)
261
+ triggers.noteDrain(t);
262
+ },
263
+ };
264
+ }
265
+ function noopBackgroundTask() {
266
+ const handlers = new Map();
267
+ return {
268
+ register: async (name, handler) => { handlers.set(name, handler); },
269
+ unregister: async (name) => { handlers.delete(name); },
270
+ isRegistered: async (name) => handlers.has(name),
271
+ };
272
+ }
273
+ function alwaysAllowedConstraints() {
274
+ return {
275
+ isCharging: async () => true,
276
+ isOnWifi: async () => true,
277
+ };
278
+ }
279
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/sync/factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * R2 fetcher (T-19 + T-20 + T-21).
3
+ *
4
+ * Reads-back path from the attached R2 Iceberg zone into the local DuckDB
5
+ * warehouse.
6
+ *
7
+ * Three modes:
8
+ * - `prefetchOnAttach(ctx, policy)` — one-shot bulk pull immediately after
9
+ * `analytics.attach()`, driven by a policy:
10
+ * * `all-family-on-attach` — every family row within `windowDays`
11
+ * * `recent-active-only` — active users first (CTE)
12
+ * * `lazy` — no-op
13
+ * - `fetchIncremental(ctx)` — pulls only rows newer than the fetch
14
+ * watermark; `ON CONFLICT DO NOTHING` keeps re-runs idempotent.
15
+ * - `fetchOnDemand(ctx, params)` — bypasses watermark; caller-supplied
16
+ * bounds (`since`, optional `until`, optional `limit`).
17
+ *
18
+ * Every path advances the fetch watermark on success so subsequent
19
+ * incremental pulls stay bounded.
20
+ */
21
+ import type { HybridDuckDB } from '../core/engine';
22
+ import type { AttachContext } from '../core/types';
23
+ import { SyncError } from './errors';
24
+ import type { WatermarkStore } from './watermark';
25
+ export type PrefetchPolicy = {
26
+ kind: 'all-family-on-attach';
27
+ windowDays: number;
28
+ } | {
29
+ kind: 'recent-active-only';
30
+ activeDays: number;
31
+ windowDays: number;
32
+ } | {
33
+ kind: 'lazy';
34
+ };
35
+ export interface FetchResult {
36
+ table: string;
37
+ rowsFetched: number;
38
+ ok: boolean;
39
+ error?: SyncError;
40
+ }
41
+ export interface FetchParams {
42
+ table: string;
43
+ since: Date;
44
+ until?: Date;
45
+ limit?: number;
46
+ }
47
+ export interface R2FetcherConfig {
48
+ engine: HybridDuckDB;
49
+ watermark: WatermarkStore;
50
+ /** Tables that participate in prefetch / incremental fetch. */
51
+ tables: readonly string[];
52
+ now?: () => Date;
53
+ localTable?: (ctx: AttachContext, table: string) => string;
54
+ remoteTable?: (ctx: AttachContext, table: string) => string;
55
+ }
56
+ export declare class R2Fetcher {
57
+ #private;
58
+ constructor(config: R2FetcherConfig);
59
+ /**
60
+ * T-19: one-shot bulk fetch keyed by policy. Returns per-table results so
61
+ * partial failure is observable.
62
+ */
63
+ prefetchOnAttach(ctx: AttachContext, policy: PrefetchPolicy): Promise<FetchResult[]>;
64
+ /**
65
+ * T-20: pulls only rows newer than the fetch watermark; idempotent.
66
+ */
67
+ fetchIncremental(ctx: AttachContext): Promise<FetchResult[]>;
68
+ /**
69
+ * T-21: caller-driven fetch that bypasses the watermark. Advances the
70
+ * fetch watermark only if the pulled range extends past the current one.
71
+ */
72
+ fetchOnDemand(ctx: AttachContext, params: FetchParams): Promise<FetchResult>;
73
+ }
74
+ //# sourceMappingURL=fetcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.d.ts","sourceRoot":"","sources":["../../src/sync/fetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,MAAM,cAAc,GACpB;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtB,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,EAAE,EAAE,OAAO,CAAA;IACX,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,IAAI,CAAA;IACX,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,YAAY,CAAA;IACpB,SAAS,EAAE,cAAc,CAAA;IACzB,+DAA+D;IAC/D,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1D,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CAC5D;AAMD,qBAAa,SAAS;;gBAQR,MAAM,EAAE,eAAe;IASnC;;;OAGG;IACG,gBAAgB,CACpB,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,WAAW,EAAE,CAAC;IAoBzB;;OAEG;IACG,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAWlE;;;OAGG;IACG,aAAa,CACjB,GAAG,EAAE,aAAa,EAClB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,WAAW,CAAC;CAuIxB"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * R2 fetcher (T-19 + T-20 + T-21).
3
+ *
4
+ * Reads-back path from the attached R2 Iceberg zone into the local DuckDB
5
+ * warehouse.
6
+ *
7
+ * Three modes:
8
+ * - `prefetchOnAttach(ctx, policy)` — one-shot bulk pull immediately after
9
+ * `analytics.attach()`, driven by a policy:
10
+ * * `all-family-on-attach` — every family row within `windowDays`
11
+ * * `recent-active-only` — active users first (CTE)
12
+ * * `lazy` — no-op
13
+ * - `fetchIncremental(ctx)` — pulls only rows newer than the fetch
14
+ * watermark; `ON CONFLICT DO NOTHING` keeps re-runs idempotent.
15
+ * - `fetchOnDemand(ctx, params)` — bypasses watermark; caller-supplied
16
+ * bounds (`since`, optional `until`, optional `limit`).
17
+ *
18
+ * Every path advances the fetch watermark on success so subsequent
19
+ * incremental pulls stay bounded.
20
+ */
21
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
22
+ if (kind === "m") throw new TypeError("Private method is not writable");
23
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
24
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
25
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
26
+ };
27
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
28
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
29
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
30
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
31
+ };
32
+ var _R2Fetcher_instances, _R2Fetcher_engine, _R2Fetcher_watermark, _R2Fetcher_tables, _R2Fetcher_now, _R2Fetcher_localTable, _R2Fetcher_remoteTable, _R2Fetcher_prefetchOne, _R2Fetcher_fetchIncrementalOne, _R2Fetcher_countFetched, _R2Fetcher_toFailure;
33
+ import { SyncError } from './errors';
34
+ const DEFAULT_LOCAL = (_c, table) => `main.${table}`;
35
+ const DEFAULT_REMOTE = (c, table) => `zone_${c.tenantId}.${table}`;
36
+ export class R2Fetcher {
37
+ constructor(config) {
38
+ _R2Fetcher_instances.add(this);
39
+ _R2Fetcher_engine.set(this, void 0);
40
+ _R2Fetcher_watermark.set(this, void 0);
41
+ _R2Fetcher_tables.set(this, void 0);
42
+ _R2Fetcher_now.set(this, void 0);
43
+ _R2Fetcher_localTable.set(this, void 0);
44
+ _R2Fetcher_remoteTable.set(this, void 0);
45
+ __classPrivateFieldSet(this, _R2Fetcher_engine, config.engine, "f");
46
+ __classPrivateFieldSet(this, _R2Fetcher_watermark, config.watermark, "f");
47
+ __classPrivateFieldSet(this, _R2Fetcher_tables, config.tables, "f");
48
+ __classPrivateFieldSet(this, _R2Fetcher_now, config.now ?? (() => new Date()), "f");
49
+ __classPrivateFieldSet(this, _R2Fetcher_localTable, config.localTable ?? DEFAULT_LOCAL, "f");
50
+ __classPrivateFieldSet(this, _R2Fetcher_remoteTable, config.remoteTable ?? DEFAULT_REMOTE, "f");
51
+ }
52
+ /**
53
+ * T-19: one-shot bulk fetch keyed by policy. Returns per-table results so
54
+ * partial failure is observable.
55
+ */
56
+ async prefetchOnAttach(ctx, policy) {
57
+ if (policy.kind === 'lazy') {
58
+ return __classPrivateFieldGet(this, _R2Fetcher_tables, "f").map(t => ({ table: t, rowsFetched: 0, ok: true }));
59
+ }
60
+ const cutoffMs = __classPrivateFieldGet(this, _R2Fetcher_now, "f").call(this).getTime() - policy.windowDays * 24 * 60 * 60 * 1000;
61
+ const cutoff = new Date(cutoffMs).toISOString();
62
+ const settled = await Promise.allSettled(__classPrivateFieldGet(this, _R2Fetcher_tables, "f").map(table => __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_prefetchOne).call(this, ctx, table, policy, cutoff)));
63
+ return settled.map((r, i) => {
64
+ const table = __classPrivateFieldGet(this, _R2Fetcher_tables, "f")[i];
65
+ if (r.status === 'fulfilled')
66
+ return r.value;
67
+ return __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_toFailure).call(this, table, r.reason);
68
+ });
69
+ }
70
+ /**
71
+ * T-20: pulls only rows newer than the fetch watermark; idempotent.
72
+ */
73
+ async fetchIncremental(ctx) {
74
+ const settled = await Promise.allSettled(__classPrivateFieldGet(this, _R2Fetcher_tables, "f").map(table => __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_fetchIncrementalOne).call(this, ctx, table)));
75
+ return settled.map((r, i) => {
76
+ const table = __classPrivateFieldGet(this, _R2Fetcher_tables, "f")[i];
77
+ if (r.status === 'fulfilled')
78
+ return r.value;
79
+ return __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_toFailure).call(this, table, r.reason);
80
+ });
81
+ }
82
+ /**
83
+ * T-21: caller-driven fetch that bypasses the watermark. Advances the
84
+ * fetch watermark only if the pulled range extends past the current one.
85
+ */
86
+ async fetchOnDemand(ctx, params) {
87
+ try {
88
+ const local = __classPrivateFieldGet(this, _R2Fetcher_localTable, "f").call(this, ctx, params.table);
89
+ const remote = __classPrivateFieldGet(this, _R2Fetcher_remoteTable, "f").call(this, ctx, params.table);
90
+ const bindings = {
91
+ since: params.since.toISOString(),
92
+ familyId: ctx.tenantId,
93
+ };
94
+ let where = 'ts >= $since AND family_id = $familyId';
95
+ if (params.until) {
96
+ where += ' AND ts <= $until';
97
+ bindings.until = params.until.toISOString();
98
+ }
99
+ const limitClause = params.limit ? ` LIMIT ${Math.max(1, Math.floor(params.limit))}` : '';
100
+ const sql = `INSERT INTO ${local} SELECT * FROM ${remote} WHERE ${where}${limitClause} ON CONFLICT DO NOTHING`;
101
+ await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(sql, bindings);
102
+ const countRows = await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(`SELECT COUNT(*) AS c FROM ${remote} WHERE ${where}${limitClause}`, bindings);
103
+ const rowsFetched = Number(countRows[0]?.c ?? 0);
104
+ const upperBound = params.until ?? __classPrivateFieldGet(this, _R2Fetcher_now, "f").call(this);
105
+ await __classPrivateFieldGet(this, _R2Fetcher_watermark, "f").advance(ctx.brand, ctx.tenantId, params.table, 'fetch', upperBound);
106
+ return { table: params.table, rowsFetched, ok: true };
107
+ }
108
+ catch (err) {
109
+ return __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_toFailure).call(this, params.table, err);
110
+ }
111
+ }
112
+ }
113
+ _R2Fetcher_engine = new WeakMap(), _R2Fetcher_watermark = new WeakMap(), _R2Fetcher_tables = new WeakMap(), _R2Fetcher_now = new WeakMap(), _R2Fetcher_localTable = new WeakMap(), _R2Fetcher_remoteTable = new WeakMap(), _R2Fetcher_instances = new WeakSet(), _R2Fetcher_prefetchOne = async function _R2Fetcher_prefetchOne(ctx, table, policy, cutoff) {
114
+ const local = __classPrivateFieldGet(this, _R2Fetcher_localTable, "f").call(this, ctx, table);
115
+ const remote = __classPrivateFieldGet(this, _R2Fetcher_remoteTable, "f").call(this, ctx, table);
116
+ if (policy.kind === 'all-family-on-attach') {
117
+ const sql = `INSERT INTO ${local} SELECT * FROM ${remote} WHERE family_id = $familyId AND ts >= $cutoff ON CONFLICT DO NOTHING`;
118
+ await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(sql, {
119
+ familyId: ctx.tenantId,
120
+ cutoff,
121
+ });
122
+ }
123
+ else if (policy.kind === 'recent-active-only') {
124
+ const activeCutoffMs = __classPrivateFieldGet(this, _R2Fetcher_now, "f").call(this).getTime() - policy.activeDays * 24 * 60 * 60 * 1000;
125
+ const activeCutoff = new Date(activeCutoffMs).toISOString();
126
+ // CTE picks user_ids seen in the recent window, then filters the window
127
+ // pull down to those users.
128
+ const sql = `WITH active AS (SELECT DISTINCT user_id FROM ${remote} WHERE family_id = $familyId AND ts >= $activeCutoff) INSERT INTO ${local} SELECT r.* FROM ${remote} r JOIN active a ON r.user_id = a.user_id WHERE r.family_id = $familyId AND r.ts >= $cutoff ON CONFLICT DO NOTHING`;
129
+ await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(sql, {
130
+ familyId: ctx.tenantId,
131
+ cutoff,
132
+ activeCutoff,
133
+ });
134
+ }
135
+ const rowsFetched = await __classPrivateFieldGet(this, _R2Fetcher_instances, "m", _R2Fetcher_countFetched).call(this, remote, ctx, cutoff);
136
+ await __classPrivateFieldGet(this, _R2Fetcher_watermark, "f").advance(ctx.brand, ctx.tenantId, table, 'fetch', __classPrivateFieldGet(this, _R2Fetcher_now, "f").call(this));
137
+ return { table, rowsFetched, ok: true };
138
+ }, _R2Fetcher_fetchIncrementalOne = async function _R2Fetcher_fetchIncrementalOne(ctx, table) {
139
+ const watermark = await __classPrivateFieldGet(this, _R2Fetcher_watermark, "f").get(ctx.brand, ctx.tenantId, table, 'fetch');
140
+ const local = __classPrivateFieldGet(this, _R2Fetcher_localTable, "f").call(this, ctx, table);
141
+ const remote = __classPrivateFieldGet(this, _R2Fetcher_remoteTable, "f").call(this, ctx, table);
142
+ const sql = `INSERT INTO ${local} SELECT * FROM ${remote} WHERE ts > $watermark AND family_id = $familyId ON CONFLICT DO NOTHING`;
143
+ await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(sql, {
144
+ watermark: watermark.toISOString(),
145
+ familyId: ctx.tenantId,
146
+ });
147
+ const maxRows = await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(`SELECT MAX(ts) AS max_ts, COUNT(*) AS row_count FROM ${remote} WHERE ts > $watermark AND family_id = $familyId`, { watermark: watermark.toISOString(), familyId: ctx.tenantId });
148
+ const first = maxRows[0];
149
+ const rowsFetched = Number(first?.row_count ?? 0);
150
+ if (first?.max_ts) {
151
+ const nextWatermark = new Date(first.max_ts);
152
+ if (!Number.isNaN(nextWatermark.getTime())) {
153
+ await __classPrivateFieldGet(this, _R2Fetcher_watermark, "f").advance(ctx.brand, ctx.tenantId, table, 'fetch', nextWatermark);
154
+ }
155
+ }
156
+ return { table, rowsFetched, ok: true };
157
+ }, _R2Fetcher_countFetched = async function _R2Fetcher_countFetched(remote, ctx, cutoff) {
158
+ const rows = await __classPrivateFieldGet(this, _R2Fetcher_engine, "f").execute(`SELECT COUNT(*) AS c FROM ${remote} WHERE family_id = $familyId AND ts >= $cutoff`, { familyId: ctx.tenantId, cutoff });
159
+ return Number(rows[0]?.c ?? 0);
160
+ }, _R2Fetcher_toFailure = function _R2Fetcher_toFailure(table, cause) {
161
+ const err = cause instanceof SyncError
162
+ ? cause
163
+ : new SyncError('fetch_failed', `fetch failed for ${table}`, cause);
164
+ return { table, rowsFetched: 0, ok: false, error: err };
165
+ };
166
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../../src/sync/fetcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Batch flusher (T-13 + T-15).
3
+ *
4
+ * Drains a `SensorBuffer` into a DuckDB Appender. Successful flushes are
5
+ * durably observable (buffer.drain() removes both in-memory and overflow
6
+ * copies), and a `SyncEmitter` fan-out surfaces `flushed` events for hooks +
7
+ * downstream invalidation.
8
+ *
9
+ * Retry / concurrency (T-15):
10
+ * - Wrapped in a `p-queue` (concurrency=2 by default) so multi-table flushes
11
+ * run in parallel without hammering the CPU.
12
+ * - Priorities: `foreground` (0) > `manual` (1) > `scheduled` (5) >
13
+ * `background` (10). Lower numeric priority = runs sooner.
14
+ * - Backoff schedule: 1s, 2s, 4s, 8s, 16s, 32s, 60s (capped). 5 consecutive
15
+ * failures per table → the flusher stops re-enqueuing and surfaces
16
+ * `error` state until the next explicit `flush()`.
17
+ * - Per-attempt timeout: 30s. Timeout aborts the attempt and counts as a
18
+ * failure toward the 5-failure limit.
19
+ *
20
+ * Data safety: attempts do *not* remove rows from the buffer until the
21
+ * appender reports success. On failure the drained entries are re-pushed onto
22
+ * the in-memory ring (front of queue) so subsequent retries see the same
23
+ * batch.
24
+ */
25
+ import type { HybridDuckDB } from '../core/engine';
26
+ import type { SensorBuffer } from './buffer';
27
+ import { SyncError } from './errors';
28
+ import type { FlushResult } from './types';
29
+ export type FlushReason = 'row-count' | 'age' | 'foreground' | 'background' | 'manual' | 'scheduled';
30
+ /** Exponential backoff in ms, per attempt index (0-based). Capped at 60s. */
31
+ export declare const BACKOFF_SEQUENCE_MS: readonly number[];
32
+ export declare const MAX_CONSECUTIVE_FAILURES = 5;
33
+ export declare const FLUSH_TIMEOUT_MS = 30000;
34
+ export type FlusherState = 'idle' | 'flushing' | 'error';
35
+ export interface FlushedEvent {
36
+ table: string;
37
+ rowsFlushed: number;
38
+ reason: FlushReason;
39
+ /**
40
+ * Distinct userIds observed across drained buffer entries. Empty when the
41
+ * flush drained zero rows. Consumed by the rules engine to gate
42
+ * `evaluateOnBatch`.
43
+ */
44
+ affectedUserIds: string[];
45
+ }
46
+ export type SyncEmitter = (event: {
47
+ type: 'flushed';
48
+ payload: FlushedEvent;
49
+ } | {
50
+ type: 'flush-failed';
51
+ payload: {
52
+ table: string;
53
+ error: SyncError;
54
+ };
55
+ }) => void;
56
+ export interface BatchFlusherConfig {
57
+ engine: HybridDuckDB;
58
+ buffer: SensorBuffer;
59
+ /**
60
+ * Column order per table. Row objects are projected into positional
61
+ * `appendRow(values[])` calls using this order. Missing tables raise
62
+ * `flush_failed` before a single row is written.
63
+ */
64
+ columnOrder: Record<string, readonly string[]>;
65
+ /** Concurrent flush workers. Default 2 (spec §Retry orchestration). */
66
+ concurrency?: number;
67
+ /** Optional test hook. Defaults to `setTimeout`. */
68
+ sleep?: (ms: number) => Promise<void>;
69
+ /** Optional test hook. Defaults to `Date.now`. */
70
+ now?: () => number;
71
+ /** Optional emitter for `flushed` / `flush-failed` events. */
72
+ emit?: SyncEmitter;
73
+ }
74
+ export declare class BatchFlusher {
75
+ #private;
76
+ constructor(config: BatchFlusherConfig);
77
+ /** Runtime state for a table. `idle` if we've never touched it. */
78
+ stateOf(table: string): FlusherState;
79
+ lastErrorOf(table: string): SyncError | undefined;
80
+ /** Consecutive failures (reset on any success). */
81
+ failureCountOf(table: string): number;
82
+ /**
83
+ * Single flush attempt (no retry). Used directly by explicit user code and
84
+ * indirectly by `scheduleFlush` for the retry loop.
85
+ */
86
+ flush(table: string, reason?: FlushReason): Promise<FlushResult>;
87
+ /**
88
+ * Enqueue a flush with retry orchestration. Non-blocking — returns the
89
+ * promise for callers that want to await final resolution.
90
+ */
91
+ scheduleFlush(table: string, reason: FlushReason): Promise<FlushResult>;
92
+ /** Wait until every in-flight + queued flush settles. */
93
+ drainQueue(): Promise<void>;
94
+ /** Pause the queue — new `scheduleFlush()` calls enqueue but don't run. */
95
+ pauseQueue(): void;
96
+ /** Resume a paused queue. */
97
+ resumeQueue(): void;
98
+ }
99
+ //# sourceMappingURL=flusher.d.ts.map