@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.
- package/README.md +472 -0
- package/dist/core/context.d.ts +24 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +30 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/engine.d.ts +56 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +119 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/extensions.d.ts +33 -0
- package/dist/core/extensions.d.ts.map +1 -0
- package/dist/core/extensions.js +58 -0
- package/dist/core/extensions.js.map +1 -0
- package/dist/core/factory.d.ts +22 -5
- package/dist/core/factory.d.ts.map +1 -1
- package/dist/core/factory.js +231 -6
- package/dist/core/factory.js.map +1 -1
- package/dist/core/hooks.d.ts +32 -13
- package/dist/core/hooks.d.ts.map +1 -1
- package/dist/core/hooks.js +137 -15
- package/dist/core/hooks.js.map +1 -1
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/logger.d.ts +25 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +33 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/machine.d.ts +174 -0
- package/dist/core/machine.d.ts.map +1 -0
- package/dist/core/machine.js +228 -0
- package/dist/core/machine.js.map +1 -0
- package/dist/core/metric_metadata.d.ts +128 -0
- package/dist/core/metric_metadata.d.ts.map +1 -0
- package/dist/core/metric_metadata.js +47 -0
- package/dist/core/metric_metadata.js.map +1 -0
- package/dist/core/migrations.d.ts +52 -0
- package/dist/core/migrations.d.ts.map +1 -0
- package/dist/core/migrations.js +66 -0
- package/dist/core/migrations.js.map +1 -0
- package/dist/core/persistence.d.ts +50 -0
- package/dist/core/persistence.d.ts.map +1 -0
- package/dist/core/persistence.js +80 -0
- package/dist/core/persistence.js.map +1 -0
- package/dist/core/retention.d.ts +90 -0
- package/dist/core/retention.d.ts.map +1 -0
- package/dist/core/retention.js +119 -0
- package/dist/core/retention.js.map +1 -0
- package/dist/core/schemas.d.ts +35 -0
- package/dist/core/schemas.d.ts.map +1 -0
- package/dist/core/schemas.js +209 -0
- package/dist/core/schemas.js.map +1 -0
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/warehouse.d.ts +58 -0
- package/dist/core/warehouse.d.ts.map +1 -0
- package/dist/core/warehouse.js +116 -0
- package/dist/core/warehouse.js.map +1 -0
- package/dist/rules/__fakes__/fakeClock.d.ts +7 -0
- package/dist/rules/__fakes__/fakeClock.d.ts.map +1 -0
- package/dist/rules/__fakes__/fakeClock.js +13 -0
- package/dist/rules/__fakes__/fakeClock.js.map +1 -0
- package/dist/rules/__fakes__/fakeEngine.d.ts +12 -0
- package/dist/rules/__fakes__/fakeEngine.d.ts.map +1 -0
- package/dist/rules/__fakes__/fakeEngine.js +33 -0
- package/dist/rules/__fakes__/fakeEngine.js.map +1 -0
- package/dist/rules/__fakes__/fakeStorage.d.ts +7 -0
- package/dist/rules/__fakes__/fakeStorage.d.ts.map +1 -0
- package/dist/rules/__fakes__/fakeStorage.js +22 -0
- package/dist/rules/__fakes__/fakeStorage.js.map +1 -0
- package/dist/rules/compiler-cache.d.ts +16 -0
- package/dist/rules/compiler-cache.d.ts.map +1 -0
- package/dist/rules/compiler-cache.js +29 -0
- package/dist/rules/compiler-cache.js.map +1 -0
- package/dist/rules/compiler.d.ts +27 -0
- package/dist/rules/compiler.d.ts.map +1 -0
- package/dist/rules/compiler.js +210 -0
- package/dist/rules/compiler.js.map +1 -0
- package/dist/rules/defaults/index.d.ts +13 -0
- package/dist/rules/defaults/index.d.ts.map +1 -0
- package/dist/rules/defaults/index.js +13 -0
- package/dist/rules/defaults/index.js.map +1 -0
- package/dist/rules/defaults/loader.d.ts +18 -0
- package/dist/rules/defaults/loader.d.ts.map +1 -0
- package/dist/rules/defaults/loader.js +37 -0
- package/dist/rules/defaults/loader.js.map +1 -0
- package/dist/rules/defaults/luminx.d.ts +6 -0
- package/dist/rules/defaults/luminx.d.ts.map +1 -0
- package/dist/rules/defaults/luminx.js +64 -0
- package/dist/rules/defaults/luminx.js.map +1 -0
- package/dist/rules/defaults/viva.d.ts +6 -0
- package/dist/rules/defaults/viva.d.ts.map +1 -0
- package/dist/rules/defaults/viva.js +45 -0
- package/dist/rules/defaults/viva.js.map +1 -0
- package/dist/rules/defaults/yogaring.d.ts +6 -0
- package/dist/rules/defaults/yogaring.d.ts.map +1 -0
- package/dist/rules/defaults/yogaring.js +45 -0
- package/dist/rules/defaults/yogaring.js.map +1 -0
- package/dist/rules/defaults/ziva.d.ts +10 -0
- package/dist/rules/defaults/ziva.d.ts.map +1 -0
- package/dist/rules/defaults/ziva.js +87 -0
- package/dist/rules/defaults/ziva.js.map +1 -0
- package/dist/rules/emitter.d.ts +20 -0
- package/dist/rules/emitter.d.ts.map +1 -0
- package/dist/rules/emitter.js +32 -0
- package/dist/rules/emitter.js.map +1 -0
- package/dist/rules/evaluator.d.ts +44 -0
- package/dist/rules/evaluator.d.ts.map +1 -0
- package/dist/rules/evaluator.js +127 -0
- package/dist/rules/evaluator.js.map +1 -0
- package/dist/rules/factory.d.ts +13 -0
- package/dist/rules/factory.d.ts.map +1 -0
- package/dist/rules/factory.js +56 -0
- package/dist/rules/factory.js.map +1 -0
- package/dist/rules/hooks/useRuleRegistry.d.ts +16 -0
- package/dist/rules/hooks/useRuleRegistry.d.ts.map +1 -0
- package/dist/rules/hooks/useRuleRegistry.js +26 -0
- package/dist/rules/hooks/useRuleRegistry.js.map +1 -0
- package/dist/rules/hooks/useRuleViolations.d.ts +21 -0
- package/dist/rules/hooks/useRuleViolations.d.ts.map +1 -0
- package/dist/rules/hooks/useRuleViolations.js +32 -0
- package/dist/rules/hooks/useRuleViolations.js.map +1 -0
- package/dist/rules/index.d.ts +30 -1
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +36 -1
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/registry.d.ts +32 -0
- package/dist/rules/registry.d.ts.map +1 -0
- package/dist/rules/registry.js +98 -0
- package/dist/rules/registry.js.map +1 -0
- package/dist/rules/schema.d.ts +221 -0
- package/dist/rules/schema.d.ts.map +1 -0
- package/dist/rules/schema.js +80 -0
- package/dist/rules/schema.js.map +1 -0
- package/dist/rules/throttle.d.ts +27 -0
- package/dist/rules/throttle.d.ts.map +1 -0
- package/dist/rules/throttle.js +76 -0
- package/dist/rules/throttle.js.map +1 -0
- package/dist/rules/types.d.ts +88 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +9 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/validator.d.ts +27 -0
- package/dist/rules/validator.d.ts.map +1 -0
- package/dist/rules/validator.js +97 -0
- package/dist/rules/validator.js.map +1 -0
- package/dist/sync/buffer.d.ts +51 -0
- package/dist/sync/buffer.d.ts.map +1 -0
- package/dist/sync/buffer.js +188 -0
- package/dist/sync/buffer.js.map +1 -0
- package/dist/sync/context.d.ts +17 -0
- package/dist/sync/context.d.ts.map +1 -0
- package/dist/sync/context.js +23 -0
- package/dist/sync/context.js.map +1 -0
- package/dist/sync/errors.d.ts +19 -0
- package/dist/sync/errors.d.ts.map +1 -0
- package/dist/sync/errors.js +21 -0
- package/dist/sync/errors.js.map +1 -0
- package/dist/sync/events.d.ts +49 -0
- package/dist/sync/events.d.ts.map +1 -0
- package/dist/sync/events.js +46 -0
- package/dist/sync/events.js.map +1 -0
- package/dist/sync/factory.d.ts +65 -0
- package/dist/sync/factory.d.ts.map +1 -0
- package/dist/sync/factory.js +279 -0
- package/dist/sync/factory.js.map +1 -0
- package/dist/sync/fetcher.d.ts +74 -0
- package/dist/sync/fetcher.d.ts.map +1 -0
- package/dist/sync/fetcher.js +166 -0
- package/dist/sync/fetcher.js.map +1 -0
- package/dist/sync/flusher.d.ts +99 -0
- package/dist/sync/flusher.d.ts.map +1 -0
- package/dist/sync/flusher.js +246 -0
- package/dist/sync/flusher.js.map +1 -0
- package/dist/sync/hooks.d.ts +17 -0
- package/dist/sync/hooks.d.ts.map +1 -0
- package/dist/sync/hooks.js +48 -0
- package/dist/sync/hooks.js.map +1 -0
- package/dist/sync/index.d.ts +32 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +36 -1
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/manager.d.ts +48 -0
- package/dist/sync/manager.d.ts.map +1 -0
- package/dist/sync/manager.js +9 -0
- package/dist/sync/manager.js.map +1 -0
- package/dist/sync/mapper/activity.d.ts +31 -0
- package/dist/sync/mapper/activity.d.ts.map +1 -0
- package/dist/sync/mapper/activity.js +56 -0
- package/dist/sync/mapper/activity.js.map +1 -0
- package/dist/sync/mapper/battery.d.ts +12 -0
- package/dist/sync/mapper/battery.d.ts.map +1 -0
- package/dist/sync/mapper/battery.js +22 -0
- package/dist/sync/mapper/battery.js.map +1 -0
- package/dist/sync/mapper/firmware.d.ts +28 -0
- package/dist/sync/mapper/firmware.d.ts.map +1 -0
- package/dist/sync/mapper/firmware.js +43 -0
- package/dist/sync/mapper/firmware.js.map +1 -0
- package/dist/sync/mapper/heart-rate.d.ts +10 -0
- package/dist/sync/mapper/heart-rate.d.ts.map +1 -0
- package/dist/sync/mapper/heart-rate.js +19 -0
- package/dist/sync/mapper/heart-rate.js.map +1 -0
- package/dist/sync/mapper/hrv.d.ts +22 -0
- package/dist/sync/mapper/hrv.d.ts.map +1 -0
- package/dist/sync/mapper/hrv.js +56 -0
- package/dist/sync/mapper/hrv.js.map +1 -0
- package/dist/sync/mapper/ring-config.d.ts +33 -0
- package/dist/sync/mapper/ring-config.d.ts.map +1 -0
- package/dist/sync/mapper/ring-config.js +48 -0
- package/dist/sync/mapper/ring-config.js.map +1 -0
- package/dist/sync/mapper/sleep.d.ts +32 -0
- package/dist/sync/mapper/sleep.d.ts.map +1 -0
- package/dist/sync/mapper/sleep.js +103 -0
- package/dist/sync/mapper/sleep.js.map +1 -0
- package/dist/sync/mapper/spo2.d.ts +9 -0
- package/dist/sync/mapper/spo2.d.ts.map +1 -0
- package/dist/sync/mapper/spo2.js +18 -0
- package/dist/sync/mapper/spo2.js.map +1 -0
- package/dist/sync/mapper/temperature.d.ts +10 -0
- package/dist/sync/mapper/temperature.d.ts.map +1 -0
- package/dist/sync/mapper/temperature.js +19 -0
- package/dist/sync/mapper/temperature.js.map +1 -0
- package/dist/sync/mapper/time.d.ts +49 -0
- package/dist/sync/mapper/time.d.ts.map +1 -0
- package/dist/sync/mapper/time.js +107 -0
- package/dist/sync/mapper/time.js.map +1 -0
- package/dist/sync/mapper/types.d.ts +152 -0
- package/dist/sync/mapper/types.d.ts.map +1 -0
- package/dist/sync/mapper/types.js +15 -0
- package/dist/sync/mapper/types.js.map +1 -0
- package/dist/sync/overflow.d.ts +30 -0
- package/dist/sync/overflow.d.ts.map +1 -0
- package/dist/sync/overflow.js +114 -0
- package/dist/sync/overflow.js.map +1 -0
- package/dist/sync/pusher.d.ts +59 -0
- package/dist/sync/pusher.d.ts.map +1 -0
- package/dist/sync/pusher.js +115 -0
- package/dist/sync/pusher.js.map +1 -0
- package/dist/sync/scheduler.d.ts +84 -0
- package/dist/sync/scheduler.d.ts.map +1 -0
- package/dist/sync/scheduler.js +132 -0
- package/dist/sync/scheduler.js.map +1 -0
- package/dist/sync/triggers.d.ts +64 -0
- package/dist/sync/triggers.d.ts.map +1 -0
- package/dist/sync/triggers.js +113 -0
- package/dist/sync/triggers.js.map +1 -0
- package/dist/sync/types.d.ts +50 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +8 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/sync/watermark.d.ts +32 -0
- package/dist/sync/watermark.d.ts.map +1 -0
- package/dist/sync/watermark.js +61 -0
- package/dist/sync/watermark.js.map +1 -0
- package/dist/tools/__fakes__/engine.d.ts +25 -0
- package/dist/tools/__fakes__/engine.d.ts.map +1 -0
- package/dist/tools/__fakes__/engine.js +37 -0
- package/dist/tools/__fakes__/engine.js.map +1 -0
- package/dist/tools/audit.d.ts +31 -0
- package/dist/tools/audit.d.ts.map +1 -0
- package/dist/tools/audit.js +157 -0
- package/dist/tools/audit.js.map +1 -0
- package/dist/tools/authorize.d.ts +36 -0
- package/dist/tools/authorize.d.ts.map +1 -0
- package/dist/tools/authorize.js +87 -0
- package/dist/tools/authorize.js.map +1 -0
- package/dist/tools/budget.d.ts +15 -0
- package/dist/tools/budget.d.ts.map +1 -0
- package/dist/tools/budget.js +64 -0
- package/dist/tools/budget.js.map +1 -0
- package/dist/tools/factory.d.ts +43 -0
- package/dist/tools/factory.d.ts.map +1 -0
- package/dist/tools/factory.js +137 -0
- package/dist/tools/factory.js.map +1 -0
- package/dist/tools/formatters.d.ts +17 -0
- package/dist/tools/formatters.d.ts.map +1 -0
- package/dist/tools/formatters.js +30 -0
- package/dist/tools/formatters.js.map +1 -0
- package/dist/tools/impls/activity.d.ts +15 -0
- package/dist/tools/impls/activity.d.ts.map +1 -0
- package/dist/tools/impls/activity.js +61 -0
- package/dist/tools/impls/activity.js.map +1 -0
- package/dist/tools/impls/anomaly.d.ts +21 -0
- package/dist/tools/impls/anomaly.d.ts.map +1 -0
- package/dist/tools/impls/anomaly.js +70 -0
- package/dist/tools/impls/anomaly.js.map +1 -0
- package/dist/tools/impls/compare.d.ts +21 -0
- package/dist/tools/impls/compare.d.ts.map +1 -0
- package/dist/tools/impls/compare.js +68 -0
- package/dist/tools/impls/compare.js.map +1 -0
- package/dist/tools/impls/hrv.d.ts +15 -0
- package/dist/tools/impls/hrv.d.ts.map +1 -0
- package/dist/tools/impls/hrv.js +36 -0
- package/dist/tools/impls/hrv.js.map +1 -0
- package/dist/tools/impls/insights.d.ts +18 -0
- package/dist/tools/impls/insights.d.ts.map +1 -0
- package/dist/tools/impls/insights.js +41 -0
- package/dist/tools/impls/insights.js.map +1 -0
- package/dist/tools/impls/sleep.d.ts +15 -0
- package/dist/tools/impls/sleep.d.ts.map +1 -0
- package/dist/tools/impls/sleep.js +43 -0
- package/dist/tools/impls/sleep.js.map +1 -0
- package/dist/tools/index.d.ts +13 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +21 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp/adapter.d.ts +33 -0
- package/dist/tools/mcp/adapter.d.ts.map +1 -0
- package/dist/tools/mcp/adapter.js +56 -0
- package/dist/tools/mcp/adapter.js.map +1 -0
- package/dist/tools/mcp/guard.d.ts +17 -0
- package/dist/tools/mcp/guard.d.ts.map +1 -0
- package/dist/tools/mcp/guard.js +22 -0
- package/dist/tools/mcp/guard.js.map +1 -0
- package/dist/tools/mcp/index.d.ts +20 -0
- package/dist/tools/mcp/index.d.ts.map +1 -0
- package/dist/tools/mcp/index.js +16 -0
- package/dist/tools/mcp/index.js.map +1 -0
- package/dist/tools/mcp/server.d.ts +39 -0
- package/dist/tools/mcp/server.d.ts.map +1 -0
- package/dist/tools/mcp/server.js +70 -0
- package/dist/tools/mcp/server.js.map +1 -0
- package/dist/tools/mcp/transports/http.d.ts +54 -0
- package/dist/tools/mcp/transports/http.d.ts.map +1 -0
- package/dist/tools/mcp/transports/http.js +133 -0
- package/dist/tools/mcp/transports/http.js.map +1 -0
- package/dist/tools/mcp/transports/stdio.d.ts +22 -0
- package/dist/tools/mcp/transports/stdio.d.ts.map +1 -0
- package/dist/tools/mcp/transports/stdio.js +19 -0
- package/dist/tools/mcp/transports/stdio.js.map +1 -0
- package/dist/tools/rate-limit.d.ts +26 -0
- package/dist/tools/rate-limit.d.ts.map +1 -0
- package/dist/tools/rate-limit.js +67 -0
- package/dist/tools/rate-limit.js.map +1 -0
- package/dist/tools/types.d.ts +121 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +16 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/wrap.d.ts +53 -0
- package/dist/tools/wrap.d.ts.map +1 -0
- package/dist/tools/wrap.js +135 -0
- package/dist/tools/wrap.js.map +1 -0
- package/package.json +28 -2
package/README.md
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
# @mongrov/analytics
|
|
2
|
+
|
|
3
|
+
DuckDB + R2 Iceberg local analytics for React Native / Expo apps in the Mongrov platform.
|
|
4
|
+
|
|
5
|
+
Per-family (or per-tenant) warehouses attached as DuckDB catalogs. Zero-copy
|
|
6
|
+
reads against R2 Iceberg tables. Headless core with optional React hooks and
|
|
7
|
+
UI subpaths.
|
|
8
|
+
|
|
9
|
+
Status: **0.1.0-alpha.7** — Phases 1 → 7 landed on the core engine plus the
|
|
10
|
+
full `@mongrov/analytics/sync` subpath (mapper → buffer → flusher → watermark
|
|
11
|
+
→ pusher → fetcher → scheduler → factory + hooks). MinIO integration test
|
|
12
|
+
(T-18 / T-28) and app-kill e2e (T-29) still pending; `0.1.0` publish (T-20)
|
|
13
|
+
follows once integration coverage lands.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Adapter-friendly DuckDB engine** — `HybridDuckDB` wrapper with pluggable
|
|
18
|
+
`DuckDBFactory` so tests can substitute an in-memory fake.
|
|
19
|
+
- **Per-tenant Iceberg warehouse** — `CREATE SECRET` + `ATTACH ... (TYPE ICEBERG)`
|
|
20
|
+
for a single `zone_<tenantId>` catalog per attach.
|
|
21
|
+
- **Migration runner** — versioned per `(brand, tenantId)`, idempotent, KV-tracked.
|
|
22
|
+
- **Retention scheduler** — `max(userOverride, familySetting, brandDefault)`
|
|
23
|
+
precedence; sensor tables use effective days, `insight` 90d, `tool_call_audit`
|
|
24
|
+
30d; DELETE bounded by `sync_watermark` so unsynced rows never drop.
|
|
25
|
+
- **KV persistence** — last-attach ctx restored across restarts (24h TTL);
|
|
26
|
+
per-user retention override.
|
|
27
|
+
- **XState v5 state machine** — `idle → opening → ready → attaching → attached
|
|
28
|
+
→ detaching → error` with token-refresh actor at 75% TTL.
|
|
29
|
+
- **React hooks + provider** — `useAnalytics`, `useTimeseries`, `useInsight`,
|
|
30
|
+
`AnalyticsProvider`.
|
|
31
|
+
- **Structured logger** — supply an `AnalyticsLogger` via config; falls back
|
|
32
|
+
to a no-op sink. No logs on the hot query path.
|
|
33
|
+
- **Typed error taxonomy** — `AnalyticsError` with a code union covering every
|
|
34
|
+
failure mode (`engine_open_failed`, `attach_failed`, `detach_failed`,
|
|
35
|
+
`token_vendor_failed`, `migration_failed`, `retention_failed`, `query_failed`,
|
|
36
|
+
`not_attached`, `not_ready`, `not_implemented`, `extension_load_failed`).
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm add @mongrov/analytics
|
|
42
|
+
# Peer deps
|
|
43
|
+
pnpm add react-native-duckdb @mongrov/db @mongrov/types
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`react-native-duckdb` is declared as an **optional peer** — the package boots
|
|
47
|
+
lazily so importing `createAnalytics` on a Node/CI host without the native
|
|
48
|
+
module does not throw. Failure to resolve it surfaces on `open()` as
|
|
49
|
+
`AnalyticsError('engine_open_failed')`.
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { AnalyticsProvider, createAnalytics, useAnalytics } from '@mongrov/analytics'
|
|
55
|
+
import type { AnalyticsConfig } from '@mongrov/analytics'
|
|
56
|
+
|
|
57
|
+
const config: AnalyticsConfig = {
|
|
58
|
+
storage: kv, // @mongrov/db KVStore or any structural equivalent
|
|
59
|
+
warehouseUriBuilder: (brand, scope, tenantId) =>
|
|
60
|
+
`s3://mongrov-analytics/${brand}/${tenantId}/warehouse`,
|
|
61
|
+
catalogEndpoint: 'https://catalog.mongrov.example',
|
|
62
|
+
tokenVendor: {
|
|
63
|
+
async fetch({ brand, tenantScope, tenantId }) {
|
|
64
|
+
const response = await api.getWarehouseToken({ brand, tenantScope, tenantId })
|
|
65
|
+
return { token: response.token, expiresAt: new Date(response.expiresAt) }
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
familyMembersProvider: async ({ brand, familyId }) => {
|
|
69
|
+
return api.listFamilyMembers({ brand, familyId })
|
|
70
|
+
},
|
|
71
|
+
retention: {
|
|
72
|
+
ziva: { days: 180 },
|
|
73
|
+
luminx: { days: 90 },
|
|
74
|
+
},
|
|
75
|
+
logger: appLogger, // optional; defaults to a no-op
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const engine = createAnalytics(config)
|
|
79
|
+
|
|
80
|
+
function App() {
|
|
81
|
+
return (
|
|
82
|
+
<AnalyticsProvider engine={engine}>
|
|
83
|
+
<MyDashboard />
|
|
84
|
+
</AnalyticsProvider>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function MyDashboard() {
|
|
89
|
+
const { state, attach, isAttached } = useAnalytics()
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (!isAttached) {
|
|
93
|
+
attach({
|
|
94
|
+
brand: 'ziva',
|
|
95
|
+
tenantScope: 'family',
|
|
96
|
+
tenantId: 'fam_123',
|
|
97
|
+
userId: 'user_1',
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
}, [isAttached, attach])
|
|
101
|
+
|
|
102
|
+
return <Text>State: {state}</Text>
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Public API
|
|
107
|
+
|
|
108
|
+
### `createAnalytics(config: AnalyticsConfig): AnalyticsEngine`
|
|
109
|
+
|
|
110
|
+
Wires config + engine + XState machine into an `AnalyticsEngine`. Kicks
|
|
111
|
+
`OPEN` synchronously; the returned engine is `opening` on the next tick.
|
|
112
|
+
|
|
113
|
+
### `AnalyticsProvider`
|
|
114
|
+
|
|
115
|
+
React context provider. Wrap once at the app root with a shared engine
|
|
116
|
+
instance so hooks can consume it.
|
|
117
|
+
|
|
118
|
+
### `useAnalytics(): UseAnalyticsResult`
|
|
119
|
+
|
|
120
|
+
Subscribes to state transitions via `useSyncExternalStore`. Returns:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
type UseAnalyticsResult = {
|
|
124
|
+
state: AnalyticsState
|
|
125
|
+
isReady: boolean
|
|
126
|
+
isAttached: boolean
|
|
127
|
+
error: Error | null
|
|
128
|
+
attach: (ctx: AttachContext) => Promise<void>
|
|
129
|
+
detach: () => Promise<void>
|
|
130
|
+
execute: AnalyticsEngine['execute']
|
|
131
|
+
stream: AnalyticsEngine['stream']
|
|
132
|
+
setRetention: (days: number) => Promise<void>
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### `useTimeseries<T>(key, params?)`
|
|
137
|
+
|
|
138
|
+
Reactive query hook. Runs on mount when engine is attached; re-runs on ctx or
|
|
139
|
+
key change; supports manual `refetch`; exposes loading + error state.
|
|
140
|
+
|
|
141
|
+
### `useInsight<T>(id)`
|
|
142
|
+
|
|
143
|
+
Reads a single row from the `insight` table by `id`. Returns `undefined`
|
|
144
|
+
if not found.
|
|
145
|
+
|
|
146
|
+
### `AnalyticsEngine`
|
|
147
|
+
|
|
148
|
+
Full method surface:
|
|
149
|
+
|
|
150
|
+
- `attach(ctx: AttachContext): Promise<void>`
|
|
151
|
+
- `detach(): Promise<void>`
|
|
152
|
+
- `execute(sql, params?): Promise<Row[]>`
|
|
153
|
+
- `stream(sql, params?): AsyncIterable<Row>`
|
|
154
|
+
- `createAppender(table): AnalyticsAppender`
|
|
155
|
+
- `setRetention(days: number): Promise<void>` — persists user override and
|
|
156
|
+
re-runs sweep with the effective value
|
|
157
|
+
- `getLastAttach(brand: string): Promise<AttachContext | null>` — restore
|
|
158
|
+
ctx from KV (null when older than 24h)
|
|
159
|
+
- `subscribe(listener): Unsubscribe`
|
|
160
|
+
- `close(): Promise<void>`
|
|
161
|
+
- getters: `state`, `lastError`, `catalog`
|
|
162
|
+
|
|
163
|
+
## Config Reference
|
|
164
|
+
|
|
165
|
+
| Field | Type | Notes |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `storage` | `KVStore` | 3-method structural type; supply `@mongrov/db` KVStore. |
|
|
168
|
+
| `warehouseUriBuilder` | `(brand, scope, tenantId) => string` | Returns the S3/Iceberg URI. |
|
|
169
|
+
| `catalogEndpoint` | `string` | Iceberg REST endpoint used by `CREATE SECRET`. |
|
|
170
|
+
| `tokenVendor` | `TokenVendor` | `.fetch({ brand, tenantScope, tenantId })` returns bearer token + `expiresAt`. |
|
|
171
|
+
| `familyMembersProvider` | `FamilyMembersProvider` | Called only when `tenantScope === 'family'`; primes retention math. |
|
|
172
|
+
| `retention` | `Record<string, { days: number }>` | Brand-keyed defaults. Sensor tables use `max(userOverride, familySetting, brandDefault)`; `insight` = 90d fixed; `tool_call_audit` = 30d fixed. |
|
|
173
|
+
| `logger?` | `AnalyticsLogger` | 4 levels (`debug` / `info` / `warn` / `error`); defaults to a no-op sink. |
|
|
174
|
+
|
|
175
|
+
## State Machine
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
idle → opening → ready → attaching → attached → detaching → ready
|
|
179
|
+
↘ error ↙
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- `opening` runs `bootstrapExtensions()` (httpfs + iceberg + parquet).
|
|
183
|
+
- `attaching` runs `attachWarehouse` (URI builder → token fetch → CREATE
|
|
184
|
+
SECRET → ATTACH → optional family-members prime) then `ensureMigrations`.
|
|
185
|
+
- `attached` self-schedules a token refresh at 75% TTL via
|
|
186
|
+
`CREATE OR REPLACE SECRET`.
|
|
187
|
+
- `error` is recoverable — re-issue `OPEN` (or `attach()` from userland).
|
|
188
|
+
|
|
189
|
+
## Retention
|
|
190
|
+
|
|
191
|
+
Precedence per spec (any layer that preserves data wins):
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
effectiveDays = max(userOverride ?? 0, familySetting ?? 0, brandDefault)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Per-table SQL:
|
|
198
|
+
|
|
199
|
+
```sql
|
|
200
|
+
DELETE FROM {catalog}.{table}
|
|
201
|
+
WHERE {tsCol} < LEAST(
|
|
202
|
+
now() - INTERVAL '{days} days',
|
|
203
|
+
(SELECT MAX(cursor_ts) FROM {catalog}.sync_watermark WHERE table_name = '{table}')
|
|
204
|
+
);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- `MAX(cursor_ts)` returns NULL when nothing has synced → `LEAST(_, NULL) = NULL`
|
|
208
|
+
→ `ts < NULL` is FALSE → unsynced rows preserved.
|
|
209
|
+
- Sensor tables (hrv, heart_rate, spo2, temperature, activity, activity_bucket,
|
|
210
|
+
sleep_session, sleep_stage, sleep_raw, device_event) use `effectiveDays`.
|
|
211
|
+
- `sleep_session` sweeps by `ts_end` (session close), not `ts_start`.
|
|
212
|
+
- `insight` sweeps at 90d fixed.
|
|
213
|
+
- `tool_call_audit` sweeps at 30d fixed.
|
|
214
|
+
- `device_config` + `sync_watermark` are skipped.
|
|
215
|
+
|
|
216
|
+
Retention sweep runs automatically on every successful `attach()` and on every
|
|
217
|
+
`setRetention()` call. Failures raise `AnalyticsError('retention_failed', …)`;
|
|
218
|
+
the attach-time sweep swallows and logs via `warn` so a DELETE failure never
|
|
219
|
+
rejects a good attach.
|
|
220
|
+
|
|
221
|
+
## Persistence
|
|
222
|
+
|
|
223
|
+
- **Last-attach:** `analytics:last-attach:{brand}` → `{ ctx, savedAt }`.
|
|
224
|
+
Expires after 24h; `loadLastAttach` deletes stale entries as a side effect.
|
|
225
|
+
- **Retention override:** `analytics:retention:override:{brand}:{tenantId}:{userId}`
|
|
226
|
+
→ `number`. Overwrites cleanly on `setRetention()`.
|
|
227
|
+
|
|
228
|
+
Both writes inside `attach()` and `setRetention()` are best-effort; failure
|
|
229
|
+
surfaces as a `logger.warn(...)` call, never a rejected promise on the caller.
|
|
230
|
+
|
|
231
|
+
## Logging
|
|
232
|
+
|
|
233
|
+
The library only emits at these levels:
|
|
234
|
+
|
|
235
|
+
- **debug** — state transitions on every distinct value change
|
|
236
|
+
(`analytics.state { value }`)
|
|
237
|
+
- **info** — lifecycle milestones (`analytics.attached`, `analytics.detached`,
|
|
238
|
+
`analytics.closed`)
|
|
239
|
+
- **warn** — best-effort catches (`analytics.persist.last_attach_failed`,
|
|
240
|
+
`analytics.persist.clear_last_attach_failed`, `analytics.retention.sweep_failed`)
|
|
241
|
+
- **error** — reserved for callers who want to surface an `AnalyticsError`
|
|
242
|
+
before re-throwing; not used by the library today.
|
|
243
|
+
|
|
244
|
+
Nothing is emitted on the hot query path (`execute`, `stream`, `createAppender`).
|
|
245
|
+
Supply your own logger to trace at your integration layer if needed.
|
|
246
|
+
|
|
247
|
+
## Error Taxonomy
|
|
248
|
+
|
|
249
|
+
Every failure surfaces as an `AnalyticsError` with a discriminating `code`:
|
|
250
|
+
|
|
251
|
+
| Code | Origin |
|
|
252
|
+
|---|---|
|
|
253
|
+
| `engine_open_failed` | DuckDB factory or `open()` threw. |
|
|
254
|
+
| `extension_load_failed` | httpfs / iceberg / parquet install/load failed. |
|
|
255
|
+
| `attach_failed` | Any step of the attach protocol (URI, secret, ATTACH, family). |
|
|
256
|
+
| `detach_failed` | `DETACH` or `DROP SECRET`. |
|
|
257
|
+
| `token_vendor_failed` | `tokenVendor.fetch(...)` or refresh CREATE OR REPLACE SECRET. |
|
|
258
|
+
| `migration_failed` | Any migration step; KV rolled back to `n-1`. |
|
|
259
|
+
| `retention_failed` | `runRetentionSweep` DELETE threw. |
|
|
260
|
+
| `query_failed` | `execute` / `stream` native error. |
|
|
261
|
+
| `not_attached` | Caller invoked `setRetention` before attach. |
|
|
262
|
+
| `not_ready` | Query issued on a closed / non-open engine. |
|
|
263
|
+
| `not_implemented` | Stub not yet wired (rare in 0.1.0-alpha.5). |
|
|
264
|
+
|
|
265
|
+
## Testing
|
|
266
|
+
|
|
267
|
+
The package ships with 125 unit tests across 14 files (Vitest). Fakes for the
|
|
268
|
+
DuckDB instance, KV store, machine actors, and attach dependencies live in
|
|
269
|
+
`src/core/__tests__/__fakes__/`.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
pnpm test # single run
|
|
273
|
+
pnpm test --watch # watch mode
|
|
274
|
+
pnpm typecheck # tsc --noEmit
|
|
275
|
+
pnpm build # tsc -p tsconfig.build.json
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Fake DuckDB** — captures every issued SQL for assertions and scripts return
|
|
279
|
+
values or errors:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { createFakeDuckDB } from '@mongrov/analytics/__tests__/__fakes__/fake-duckdb'
|
|
283
|
+
|
|
284
|
+
const fake = createFakeDuckDB()
|
|
285
|
+
fake.failNextExecute(new Error('disk full'))
|
|
286
|
+
fake.failExecuteMatching(/^DELETE FROM /, new Error('retention sad'))
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Fake KV** — in-memory `KVStore` implementation with the backing map
|
|
290
|
+
exposed for direct assertions.
|
|
291
|
+
|
|
292
|
+
## Integration testing (T-18 — pending)
|
|
293
|
+
|
|
294
|
+
Full end-to-end coverage against a real MinIO + Iceberg REST catalog is
|
|
295
|
+
planned for T-18 in Phase 8. The intended shape:
|
|
296
|
+
|
|
297
|
+
1. Boot MinIO + Iceberg REST via testcontainers.
|
|
298
|
+
2. `createAnalytics` with a real `duckdbFactory` and pointing at the test
|
|
299
|
+
endpoint.
|
|
300
|
+
3. `attach → INSERT (via appender) → SELECT → detach → reattach` — assert
|
|
301
|
+
no leakage.
|
|
302
|
+
4. Brand-switch scenario: attach `brandA`, detach, attach `brandB`, confirm
|
|
303
|
+
no cross-brand catalog visibility.
|
|
304
|
+
|
|
305
|
+
This suite is deferred until testcontainers infra lands.
|
|
306
|
+
|
|
307
|
+
## Boundaries
|
|
308
|
+
|
|
309
|
+
- **No** direct dependencies on `react-native-ble-plx`, `@mongrov/device`,
|
|
310
|
+
`@mongrov/collab`, or any UI-only package.
|
|
311
|
+
- **No** value-level import from `@mongrov/db` — the `KVStore` contract is a
|
|
312
|
+
local structural type so this package can be consumed alone.
|
|
313
|
+
- **No** logs on the hot query path.
|
|
314
|
+
- **No** rejection of a successful attach for persistence / retention failures.
|
|
315
|
+
|
|
316
|
+
## Sync (`@mongrov/analytics/sync`)
|
|
317
|
+
|
|
318
|
+
The `/sync` subpath layers the write-side pipeline on top of the core engine:
|
|
319
|
+
firmware/live rows → mapper → in-memory ring buffer → BatchFlusher (p-queue
|
|
320
|
+
serialised per table) → local DuckDB → R2Pusher → R2Fetcher (incremental /
|
|
321
|
+
prefetch) → SyncScheduler (`expo-background-task`-friendly cycle).
|
|
322
|
+
|
|
323
|
+
### Quick wiring
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
import { createAnalytics } from '@mongrov/analytics'
|
|
327
|
+
import {
|
|
328
|
+
createSyncManager,
|
|
329
|
+
SyncProvider,
|
|
330
|
+
useSensorSink,
|
|
331
|
+
useSyncProgress,
|
|
332
|
+
useSyncState,
|
|
333
|
+
} from '@mongrov/analytics/sync'
|
|
334
|
+
|
|
335
|
+
const analytics = createAnalytics(analyticsConfig)
|
|
336
|
+
|
|
337
|
+
const sync = createSyncManager({
|
|
338
|
+
analytics,
|
|
339
|
+
storage: kv,
|
|
340
|
+
ctx: { brand: 'ziva', tenantScope: 'family', tenantId: 'fam_123', userId: 'u1' },
|
|
341
|
+
tables: [
|
|
342
|
+
'hrv', 'heart_rate', 'spo2', 'temperature',
|
|
343
|
+
'activity', 'activity_bucket',
|
|
344
|
+
'sleep_session', 'sleep_stage', 'sleep_raw',
|
|
345
|
+
'device_event', 'device_config',
|
|
346
|
+
],
|
|
347
|
+
columnOrder: {
|
|
348
|
+
hrv: ['user_id', 'device_id', 'ts', 'rmssd_ms', 'sdnn_ms'],
|
|
349
|
+
// …one entry per table matching the Iceberg column order.
|
|
350
|
+
},
|
|
351
|
+
prefetchPolicy: { kind: 'recent-active-only', activeDays: 30, windowDays: 90 },
|
|
352
|
+
flush: { maxRows: 500, maxAgeMs: 60_000, concurrency: 3 },
|
|
353
|
+
overflow: { maxBufferBytes: 5 * 1024 * 1024, policy: 'drop-oldest' },
|
|
354
|
+
scheduler: { requiresCharging: false, requiresWifi: true, taskName: 'mongrov.sync' },
|
|
355
|
+
eventBus: appEventBus, // optional
|
|
356
|
+
refreshToken: analytics.refreshToken, // optional
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
await sync.start()
|
|
360
|
+
|
|
361
|
+
export default function App() {
|
|
362
|
+
return (
|
|
363
|
+
<AnalyticsProvider engine={analytics}>
|
|
364
|
+
<SyncProvider manager={sync}>
|
|
365
|
+
<Root />
|
|
366
|
+
</SyncProvider>
|
|
367
|
+
</AnalyticsProvider>
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### SensorSink API
|
|
373
|
+
|
|
374
|
+
`useSensorSink()` (or `sync.sink`) returns:
|
|
375
|
+
|
|
376
|
+
- `push(batch: SensorBatch): Promise<void>` — enqueue a pre-mapped batch.
|
|
377
|
+
- `pushFirmware(fw: FirmwareExport, ctx: MapperContext): Promise<void>` —
|
|
378
|
+
runs the firmware mapper (`mapFirmwareExport`) and fans out per table.
|
|
379
|
+
- `flush(): Promise<FlushResult[]>` — drain every configured table now.
|
|
380
|
+
- `pendingRowCount(table?: string): Promise<number>` — in-memory + overflow.
|
|
381
|
+
- `clear(): Promise<void>` — reset buffer + overflow (used on sign-out).
|
|
382
|
+
|
|
383
|
+
### Config reference (sync)
|
|
384
|
+
|
|
385
|
+
| Field | Type | Notes |
|
|
386
|
+
|---|---|---|
|
|
387
|
+
| `analytics` | `AnalyticsEngine` | Core engine created by `createAnalytics(...)`. |
|
|
388
|
+
| `storage` | `KVStore` | Backing store for overflow chunks + watermarks. |
|
|
389
|
+
| `ctx` | `AttachContext` | Passed to scheduler cycle (pushAll / fetchIncremental). |
|
|
390
|
+
| `tables` | `readonly string[]` | Ordered list; enforces flush / push / fetch scope. |
|
|
391
|
+
| `columnOrder` | `Record<string, readonly string[]>` | Column order per table; must match Iceberg schema. |
|
|
392
|
+
| `prefetchPolicy` | `PrefetchPolicy` | `all-family-on-attach` / `recent-active-only` / `lazy`. |
|
|
393
|
+
| `flush.maxRows` | `number` | Row-count trigger (default 500). |
|
|
394
|
+
| `flush.maxAgeMs` | `number` | Age trigger (default 60 000 ms). |
|
|
395
|
+
| `flush.concurrency` | `number` | p-queue concurrency (default 3). |
|
|
396
|
+
| `overflow.maxBufferBytes` | `number` | Ring budget before spill. |
|
|
397
|
+
| `overflow.policy` | `'drop-oldest' \| 'drop-newest' \| 'block'` | Overflow strategy. |
|
|
398
|
+
| `scheduler.requiresCharging` | `boolean` | Skip cycle unless charging. |
|
|
399
|
+
| `scheduler.requiresWifi` | `boolean` | Skip cycle unless Wi-Fi. |
|
|
400
|
+
| `scheduler.taskName` | `string` | Background-task registration name. |
|
|
401
|
+
| `eventBus?` | `EventBus` | Optional bus for `{table}:insert` / `{table}:sync_complete` fan-out. |
|
|
402
|
+
| `refreshToken?` | `() => Promise<void>` | Called by pusher on `401 token_expired` before retry. |
|
|
403
|
+
| `backgroundTask?` | `BackgroundTaskPort` | Inject a native background port (defaults to an in-process no-op). |
|
|
404
|
+
| `constraints?` | `ConstraintPort` | Inject Wi-Fi + charging probes (defaults to always-allowed). |
|
|
405
|
+
| `logger?` | `SchedulerLogger` | Debug hook for the scheduler cycle. |
|
|
406
|
+
|
|
407
|
+
### Prefetch policies
|
|
408
|
+
|
|
409
|
+
- `{ kind: 'all-family-on-attach', windowDays }` — pull the last `windowDays`
|
|
410
|
+
of every configured table for the whole family after attach.
|
|
411
|
+
- `{ kind: 'recent-active-only', activeDays, windowDays }` — only pull for
|
|
412
|
+
users active within `activeDays`, capped at `windowDays`.
|
|
413
|
+
- `{ kind: 'lazy' }` — no prefetch; `fetchIncremental` picks up from the
|
|
414
|
+
fetch watermark on the first scheduler cycle.
|
|
415
|
+
|
|
416
|
+
Call `sync.prefetch(ctx)` right after `analytics.attach(ctx)` completes.
|
|
417
|
+
|
|
418
|
+
### Background scheduling
|
|
419
|
+
|
|
420
|
+
`SyncScheduler` registers a single task (`config.scheduler.taskName`) with
|
|
421
|
+
the injected `BackgroundTaskPort`. Every registered cycle:
|
|
422
|
+
|
|
423
|
+
1. Constraint check (Wi-Fi + optional charging). Missing constraints emit
|
|
424
|
+
`constraint_not_met` and skip.
|
|
425
|
+
2. `coordinator.flushAll()` — parallel `flusher.flush(table, 'scheduled')`.
|
|
426
|
+
3. `coordinator.pushAll(tables, ctx)` — `R2Pusher.pushAll` (concurrency-safe).
|
|
427
|
+
4. `coordinator.fetchIncremental(ctx)` — `R2Fetcher.fetchIncremental`.
|
|
428
|
+
|
|
429
|
+
`sync.triggerNow()` bypasses constraints (e.g., manual "sync now" button).
|
|
430
|
+
`sync.subscribe(fn)` fans out scheduler transitions (`idle | running | error`).
|
|
431
|
+
|
|
432
|
+
### Mapper contract
|
|
433
|
+
|
|
434
|
+
`@mongrov/analytics/sync` exposes per-metric mappers plus a firmware-export
|
|
435
|
+
fan-out:
|
|
436
|
+
|
|
437
|
+
- `mapHrv`, `mapHeartRate`, `mapSpo2`, `mapTemperature`, `mapActivity`,
|
|
438
|
+
`mapBattery`, `mapRingConfig`, `reconstructSleepSessions` — one per raw
|
|
439
|
+
metric family.
|
|
440
|
+
- `mapFirmwareExport(fw, ctx)` — runs every applicable mapper and returns a
|
|
441
|
+
`FirmwareMappedBatch` keyed by table (matches `sync.sink.pushFirmware`).
|
|
442
|
+
|
|
443
|
+
Row types (`HrvRow`, `HeartRateRow`, …, `SleepSessionRow`) are re-exported
|
|
444
|
+
for typed sinks / adapters. `computeNightOf` + `parseTimestamp` cover the
|
|
445
|
+
common time helpers.
|
|
446
|
+
|
|
447
|
+
### Firmware fixtures
|
|
448
|
+
|
|
449
|
+
Fixture exports used across mapper + firmware tests live at
|
|
450
|
+
`src/sync/__tests__/__fixtures__/` (e.g. `firmware-full-day.json`).
|
|
451
|
+
Consumers writing their own regression tests can import them via a relative
|
|
452
|
+
path inside this workspace; they are excluded from the published `dist/`.
|
|
453
|
+
|
|
454
|
+
## AI tools (`@mongrov/analytics/tools`)
|
|
455
|
+
|
|
456
|
+
Six read-only AI SDK v4 tools (`getHRV`, `getSleepSummary`,
|
|
457
|
+
`getActivityTotal`, `compareTrend`, `detectAnomaly`, `getInsights`)
|
|
458
|
+
sit on top of the warehouse, wired through a
|
|
459
|
+
`rate → auth → execute → budget → audit` chain. See
|
|
460
|
+
[`src/tools/README.md`](./src/tools/README.md).
|
|
461
|
+
|
|
462
|
+
### MCP dev server (`@mongrov/analytics/tools/mcp`)
|
|
463
|
+
|
|
464
|
+
Same six tools, exposed over Model Context Protocol via stdio
|
|
465
|
+
(Claude Desktop) or HTTP with bearer auth (MCP Inspector, curl).
|
|
466
|
+
Dev-guarded (`shouldStartMcpServer()`) + `sideEffects: false` so
|
|
467
|
+
prod RN bundles drop the SDK. See
|
|
468
|
+
[`src/tools/mcp/README.md`](./src/tools/mcp/README.md).
|
|
469
|
+
|
|
470
|
+
## License
|
|
471
|
+
|
|
472
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React context for the analytics engine.
|
|
3
|
+
*
|
|
4
|
+
* `AnalyticsProvider` wires an already-constructed `AnalyticsEngine` into the
|
|
5
|
+
* subtree; hooks (`useAnalytics`, `useTimeseries`, `useInsight`) resolve the
|
|
6
|
+
* engine via `useAnalyticsEngine()`.
|
|
7
|
+
*
|
|
8
|
+
* Kept in its own module so `hooks.ts` remains pure logic (easier to unit
|
|
9
|
+
* test) and the .tsx file boundary carries only JSX.
|
|
10
|
+
*/
|
|
11
|
+
import * as React from 'react';
|
|
12
|
+
import type { AnalyticsEngine } from './types';
|
|
13
|
+
export interface AnalyticsProviderProps {
|
|
14
|
+
engine: AnalyticsEngine;
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export declare function AnalyticsProvider({ engine, children }: AnalyticsProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
/**
|
|
19
|
+
* Internal — hooks call this to resolve the engine. Throws a clear error when
|
|
20
|
+
* used outside `AnalyticsProvider` so misconfigured apps fail loudly at mount
|
|
21
|
+
* rather than surfacing an opaque null-ref later.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useAnalyticsEngine(): AnalyticsEngine;
|
|
24
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/core/context.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAI9C,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,sBAAsB,2CAM7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CASpD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* React context for the analytics engine.
|
|
4
|
+
*
|
|
5
|
+
* `AnalyticsProvider` wires an already-constructed `AnalyticsEngine` into the
|
|
6
|
+
* subtree; hooks (`useAnalytics`, `useTimeseries`, `useInsight`) resolve the
|
|
7
|
+
* engine via `useAnalyticsEngine()`.
|
|
8
|
+
*
|
|
9
|
+
* Kept in its own module so `hooks.ts` remains pure logic (easier to unit
|
|
10
|
+
* test) and the .tsx file boundary carries only JSX.
|
|
11
|
+
*/
|
|
12
|
+
import * as React from 'react';
|
|
13
|
+
const AnalyticsContext = React.createContext(null);
|
|
14
|
+
export function AnalyticsProvider({ engine, children }) {
|
|
15
|
+
return (_jsx(AnalyticsContext.Provider, { value: engine, children: children }));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Internal — hooks call this to resolve the engine. Throws a clear error when
|
|
19
|
+
* used outside `AnalyticsProvider` so misconfigured apps fail loudly at mount
|
|
20
|
+
* rather than surfacing an opaque null-ref later.
|
|
21
|
+
*/
|
|
22
|
+
export function useAnalyticsEngine() {
|
|
23
|
+
const engine = React.useContext(AnalyticsContext);
|
|
24
|
+
if (!engine) {
|
|
25
|
+
throw new Error('@mongrov/analytics: hook used outside <AnalyticsProvider>. '
|
|
26
|
+
+ 'Wrap your app with <AnalyticsProvider engine={createAnalytics(...)}>.');
|
|
27
|
+
}
|
|
28
|
+
return engine;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/core/context.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,MAAM,gBAAgB,GAAG,KAAK,CAAC,aAAa,CAAyB,IAAI,CAAC,CAAA;AAO1E,MAAM,UAAU,iBAAiB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAA0B;IAC5E,OAAO,CACL,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,MAAM,YACrC,QAAQ,GACiB,CAC7B,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAA;IACjD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,6DAA6D;cAC3D,uEAAuE,CAC1E,CAAA;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around a native DuckDB instance.
|
|
3
|
+
*
|
|
4
|
+
* Owns lifecycle (`open`/`close`), maps native errors into the analytics
|
|
5
|
+
* error taxonomy, and normalises `execute`/`stream`/`createAppender` so the
|
|
6
|
+
* rest of the package doesn't touch `react-native-duckdb` directly.
|
|
7
|
+
*
|
|
8
|
+
* We depend on `react-native-duckdb` only structurally (via the
|
|
9
|
+
* `DuckDBInstance` interface below) so the package builds and tests without
|
|
10
|
+
* the native module installed. The public factory (T-10) supplies a real
|
|
11
|
+
* instance via a `DuckDBFactory`; test suites supply fakes.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Minimal structural type for the underlying DuckDB connection.
|
|
15
|
+
*
|
|
16
|
+
* Anything the real `react-native-duckdb` connection offers beyond this is
|
|
17
|
+
* ignored — we only lean on what spec §Engine + downstream phases need.
|
|
18
|
+
*/
|
|
19
|
+
export interface DuckDBInstance {
|
|
20
|
+
execute: (sql: string, params?: Record<string, unknown>) => Promise<unknown[]>;
|
|
21
|
+
stream: (sql: string, params?: Record<string, unknown>) => AsyncIterable<unknown[]>;
|
|
22
|
+
createAppender: (table: string) => DuckDBAppender;
|
|
23
|
+
close: () => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export interface DuckDBAppender {
|
|
26
|
+
appendRow: (values: unknown[]) => void;
|
|
27
|
+
flush: () => void;
|
|
28
|
+
close: () => void;
|
|
29
|
+
}
|
|
30
|
+
/** Factory that yields a DuckDB instance (native or fake). */
|
|
31
|
+
export type DuckDBFactory = () => Promise<DuckDBInstance>;
|
|
32
|
+
/**
|
|
33
|
+
* Owns the DuckDB connection lifecycle.
|
|
34
|
+
*
|
|
35
|
+
* - `open()` is idempotent: repeated calls after a successful open are no-ops.
|
|
36
|
+
* - `close()` is idempotent and terminal — subsequent `execute`/`stream`/
|
|
37
|
+
* `createAppender` throw `not_ready`.
|
|
38
|
+
* - `execute()` returns a full row set; `stream()` yields pages of rows
|
|
39
|
+
* (recommended page size `HybridDuckDB.PAGE_SIZE`, applied by the native
|
|
40
|
+
* driver; the wrapper just forwards).
|
|
41
|
+
* - Any native throw is wrapped in `AnalyticsError`.
|
|
42
|
+
*/
|
|
43
|
+
export declare class HybridDuckDB {
|
|
44
|
+
#private;
|
|
45
|
+
/** Recommended page size for streamed queries (spec §Engine). */
|
|
46
|
+
static readonly PAGE_SIZE = 500;
|
|
47
|
+
constructor(factory: DuckDBFactory);
|
|
48
|
+
/** Whether `open()` has completed successfully and `close()` has not been called. */
|
|
49
|
+
get isOpen(): boolean;
|
|
50
|
+
open(): Promise<void>;
|
|
51
|
+
execute<T = unknown>(sql: string, params?: Record<string, unknown>): Promise<T[]>;
|
|
52
|
+
stream<T = unknown>(sql: string, params?: Record<string, unknown>): AsyncIterable<T[]>;
|
|
53
|
+
createAppender(table: string): DuckDBAppender;
|
|
54
|
+
close(): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;IAC9E,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,aAAa,CAAC,OAAO,EAAE,CAAC,CAAA;IACnF,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,cAAc,CAAA;IACjD,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IACtC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,CAAA;AAEzD;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;;IACvB,iEAAiE;IACjE,MAAM,CAAC,QAAQ,CAAC,SAAS,OAAM;gBAMnB,OAAO,EAAE,aAAa;IAIlC,qFAAqF;IACrF,IAAI,MAAM,IAAI,OAAO,CAEpB;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAerB,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAWhF,MAAM,CAAC,CAAC,GAAG,OAAO,EACvB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,aAAa,CAAC,CAAC,EAAE,CAAC;IAYrB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc;IAcvC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAqB7B"}
|