@uptimizr/db 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/AGENTS.md +228 -3
  2. package/README.md +13 -0
  3. package/dist/duckdb/migrations.d.ts.map +1 -1
  4. package/dist/duckdb/migrations.js +169 -0
  5. package/dist/duckdb/migrations.js.map +1 -1
  6. package/dist/duckdb/panelSpecs.d.ts +29 -0
  7. package/dist/duckdb/panelSpecs.d.ts.map +1 -0
  8. package/dist/duckdb/panelSpecs.js +104 -0
  9. package/dist/duckdb/panelSpecs.js.map +1 -0
  10. package/dist/duckdb/projectMetadata.d.ts +43 -0
  11. package/dist/duckdb/projectMetadata.d.ts.map +1 -0
  12. package/dist/duckdb/projectMetadata.js +222 -0
  13. package/dist/duckdb/projectMetadata.js.map +1 -0
  14. package/dist/duckdb/subscriptions.d.ts +45 -0
  15. package/dist/duckdb/subscriptions.d.ts.map +1 -0
  16. package/dist/duckdb/subscriptions.js +202 -0
  17. package/dist/duckdb/subscriptions.js.map +1 -0
  18. package/dist/index.d.ts +18 -4
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +48 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/insights/anomalies.d.ts +213 -0
  23. package/dist/insights/anomalies.d.ts.map +1 -0
  24. package/dist/insights/anomalies.js +340 -0
  25. package/dist/insights/anomalies.js.map +1 -0
  26. package/dist/insights/baseline.d.ts +51 -0
  27. package/dist/insights/baseline.d.ts.map +1 -0
  28. package/dist/insights/baseline.js +48 -0
  29. package/dist/insights/baseline.js.map +1 -0
  30. package/dist/insights/buckets.d.ts +114 -0
  31. package/dist/insights/buckets.d.ts.map +1 -0
  32. package/dist/insights/buckets.js +220 -0
  33. package/dist/insights/buckets.js.map +1 -0
  34. package/dist/insights/changepoint.d.ts +130 -0
  35. package/dist/insights/changepoint.d.ts.map +1 -0
  36. package/dist/insights/changepoint.js +209 -0
  37. package/dist/insights/changepoint.js.map +1 -0
  38. package/dist/insights/evaluate.d.ts +63 -0
  39. package/dist/insights/evaluate.d.ts.map +1 -0
  40. package/dist/insights/evaluate.js +158 -0
  41. package/dist/insights/evaluate.js.map +1 -0
  42. package/dist/insights/health.d.ts +219 -0
  43. package/dist/insights/health.d.ts.map +1 -0
  44. package/dist/insights/health.js +349 -0
  45. package/dist/insights/health.js.map +1 -0
  46. package/dist/insights/index.d.ts +45 -0
  47. package/dist/insights/index.d.ts.map +1 -0
  48. package/dist/insights/index.js +40 -0
  49. package/dist/insights/index.js.map +1 -0
  50. package/dist/insights/measures.d.ts +222 -0
  51. package/dist/insights/measures.d.ts.map +1 -0
  52. package/dist/insights/measures.js +431 -0
  53. package/dist/insights/measures.js.map +1 -0
  54. package/dist/insights/movers.d.ts +123 -0
  55. package/dist/insights/movers.d.ts.map +1 -0
  56. package/dist/insights/movers.js +166 -0
  57. package/dist/insights/movers.js.map +1 -0
  58. package/dist/insights/significance.d.ts +221 -0
  59. package/dist/insights/significance.d.ts.map +1 -0
  60. package/dist/insights/significance.js +531 -0
  61. package/dist/insights/significance.js.map +1 -0
  62. package/dist/insights/stats.d.ts +104 -0
  63. package/dist/insights/stats.d.ts.map +1 -0
  64. package/dist/insights/stats.js +181 -0
  65. package/dist/insights/stats.js.map +1 -0
  66. package/dist/insights/windows.d.ts +95 -0
  67. package/dist/insights/windows.d.ts.map +1 -0
  68. package/dist/insights/windows.js +97 -0
  69. package/dist/insights/windows.js.map +1 -0
  70. package/dist/metadata.d.ts +178 -1
  71. package/dist/metadata.d.ts.map +1 -1
  72. package/dist/metadata.js +80 -0
  73. package/dist/metadata.js.map +1 -1
  74. package/dist/narrative/build.d.ts +78 -0
  75. package/dist/narrative/build.d.ts.map +1 -0
  76. package/dist/narrative/build.js +437 -0
  77. package/dist/narrative/build.js.map +1 -0
  78. package/dist/narrative/index.d.ts +14 -0
  79. package/dist/narrative/index.d.ts.map +1 -0
  80. package/dist/narrative/index.js +13 -0
  81. package/dist/narrative/index.js.map +1 -0
  82. package/dist/narrative/text.d.ts +25 -0
  83. package/dist/narrative/text.d.ts.map +1 -0
  84. package/dist/narrative/text.js +41 -0
  85. package/dist/narrative/text.js.map +1 -0
  86. package/dist/parity/cases.d.ts +3 -0
  87. package/dist/parity/cases.d.ts.map +1 -1
  88. package/dist/parity/cases.js +387 -7
  89. package/dist/parity/cases.js.map +1 -1
  90. package/dist/parity/compare.d.ts +19 -0
  91. package/dist/parity/compare.d.ts.map +1 -1
  92. package/dist/parity/compare.js +19 -0
  93. package/dist/parity/compare.js.map +1 -1
  94. package/dist/parity/fixtures.d.ts.map +1 -1
  95. package/dist/parity/fixtures.js +25 -0
  96. package/dist/parity/fixtures.js.map +1 -1
  97. package/dist/query/aggregations.d.ts +46 -1
  98. package/dist/query/aggregations.d.ts.map +1 -1
  99. package/dist/query/aggregations.js +86 -0
  100. package/dist/query/aggregations.js.map +1 -1
  101. package/dist/query/clickhouseDialect.d.ts.map +1 -1
  102. package/dist/query/clickhouseDialect.js +5 -1
  103. package/dist/query/clickhouseDialect.js.map +1 -1
  104. package/dist/query/customEventVocabulary.d.ts +46 -0
  105. package/dist/query/customEventVocabulary.d.ts.map +1 -0
  106. package/dist/query/customEventVocabulary.js +127 -0
  107. package/dist/query/customEventVocabulary.js.map +1 -0
  108. package/dist/query/dsl/builders.d.ts +38 -0
  109. package/dist/query/dsl/builders.d.ts.map +1 -0
  110. package/dist/query/dsl/builders.js +35 -0
  111. package/dist/query/dsl/builders.js.map +1 -0
  112. package/dist/query/dsl/compare.d.ts +167 -0
  113. package/dist/query/dsl/compare.d.ts.map +1 -0
  114. package/dist/query/dsl/compare.js +358 -0
  115. package/dist/query/dsl/compare.js.map +1 -0
  116. package/dist/query/dsl/compile.d.ts +89 -0
  117. package/dist/query/dsl/compile.d.ts.map +1 -0
  118. package/dist/query/dsl/compile.js +174 -0
  119. package/dist/query/dsl/compile.js.map +1 -0
  120. package/dist/query/dsl/explain.d.ts +115 -0
  121. package/dist/query/dsl/explain.d.ts.map +1 -0
  122. package/dist/query/dsl/explain.js +143 -0
  123. package/dist/query/dsl/explain.js.map +1 -0
  124. package/dist/query/dsl/generic.d.ts +127 -0
  125. package/dist/query/dsl/generic.d.ts.map +1 -0
  126. package/dist/query/dsl/generic.js +323 -0
  127. package/dist/query/dsl/generic.js.map +1 -0
  128. package/dist/query/dsl/index.d.ts +30 -0
  129. package/dist/query/dsl/index.d.ts.map +1 -0
  130. package/dist/query/dsl/index.js +23 -0
  131. package/dist/query/dsl/index.js.map +1 -0
  132. package/dist/query/dsl/order.d.ts +41 -0
  133. package/dist/query/dsl/order.d.ts.map +1 -0
  134. package/dist/query/dsl/order.js +55 -0
  135. package/dist/query/dsl/order.js.map +1 -0
  136. package/dist/query/dsl/significance.d.ts +130 -0
  137. package/dist/query/dsl/significance.d.ts.map +1 -0
  138. package/dist/query/dsl/significance.js +224 -0
  139. package/dist/query/dsl/significance.js.map +1 -0
  140. package/dist/query/index.d.ts +2 -0
  141. package/dist/query/index.d.ts.map +1 -1
  142. package/dist/query/index.js +8 -0
  143. package/dist/query/index.js.map +1 -1
  144. package/dist/query/summary/index.d.ts +4 -2
  145. package/dist/query/summary/index.d.ts.map +1 -1
  146. package/dist/query/summary/index.js +2 -1
  147. package/dist/query/summary/index.js.map +1 -1
  148. package/dist/query/summary/labels.d.ts +101 -0
  149. package/dist/query/summary/labels.d.ts.map +1 -0
  150. package/dist/query/summary/labels.js +261 -0
  151. package/dist/query/summary/labels.js.map +1 -0
  152. package/dist/query/summary/reading.d.ts.map +1 -1
  153. package/dist/query/summary/reading.js +21 -2
  154. package/dist/query/summary/reading.js.map +1 -1
  155. package/dist/query/summary/schema.d.ts +250 -20
  156. package/dist/query/summary/schema.d.ts.map +1 -1
  157. package/dist/query/summary/schema.js +135 -123
  158. package/dist/query/summary/schema.js.map +1 -1
  159. package/dist/query/summary/summarize.d.ts.map +1 -1
  160. package/dist/query/summary/summarize.js +91 -5
  161. package/dist/query/summary/summarize.js.map +1 -1
  162. package/dist/query/summary/types.d.ts +77 -2
  163. package/dist/query/summary/types.d.ts.map +1 -1
  164. package/dist/query/types.d.ts +47 -1
  165. package/dist/query/types.d.ts.map +1 -1
  166. package/dist/subscriptions.d.ts +208 -0
  167. package/dist/subscriptions.d.ts.map +1 -0
  168. package/dist/subscriptions.js +153 -0
  169. package/dist/subscriptions.js.map +1 -0
  170. package/llms.txt +9 -0
  171. package/package.json +8 -8
package/llms.txt CHANGED
@@ -19,7 +19,16 @@
19
19
  - Metric registry: moved to its own dependency-free package, [`@uptimizr/metrics`](../metrics) (ADR 0051 §1) — `METRIC_REGISTRY`, `getMetric`, `allMetrics`, `METRIC_IDS`, `FILTER_TARGETS`, one `MetricDefinition` per aggregation. This package depends on it; a new aggregation without a registry entry fails the build.
20
20
  - Neutral helpers: `toEventRow`, `formatUtcTimestamp`, api-key helpers (`parseApiKeyCapabilities`, `serializeApiKeyCapabilities`, `parseCapabilityList`, `hasCapability`, `toApiKeyColumns`, `toApiKeyRateLimit`), audit helpers (`serializeAuditParams`, `clampAuditTool`), `Project`/`ApiKeyRecord`/`ResolvedApiKey`/`ApiKeyCapability`/`AgentAuditEntry`/`SceneRepresentation*`/`SceneRegionRecord`/`SceneRegionSummary` types.
21
21
  - Scene registry: `duckdbUpsertSceneProxy`, `duckdbPutSceneRegions` (replaces a scene's region set atomically), `duckdbGetSceneRegions`, `duckdbListSceneRegions`.
22
+ - Project metadata (ADR 0051 §5): `duckdbCreateAnnotation`/`duckdbListAnnotations`/`duckdbDeleteAnnotation`, `duckdbPutGlossaryEntry`/`duckdbListGlossary`/`duckdbDeleteGlossaryEntry`, `duckdbCreateSavedAnalysis`/`duckdbListSavedAnalyses`/`duckdbDeleteSavedAnalysis`, plus `METADATA_LIMITS`, `MetadataLimitError`, `clampMetadataLimit`, `parseSavedAnalysisQuery` and the `AnnotationRecord`/`GlossaryEntryRecord`/`SavedAnalysisRecord` types. These tables, plus `panel_specs` below, are the only writable surface besides events; every write needs the collector's `annotate` capability, and no accessor here can write, alter or delete an event.
23
+ - Panel specs (ADR 0051 §7): `duckdbCreatePanelSpec`/`duckdbListPanelSpecs`/`duckdbUpdatePanelSpec`/`duckdbDeletePanelSpec`, `parsePanelSpec(json)`, the `PanelSpecRecord`/`CreatePanelSpecInput`/`UpdatePanelSpecInput` types, and `METADATA_LIMITS.panelSpecs` as the per-project cap. A fourth metadata table on the same `annotate`-gated, audited, bounded path, holding one closed JSON `spec` document per pinned panel — nothing about the chart or the query is promoted to a column, because no read path asks the database about it. Two differences from the other three: the listing is **oldest first** (these are grid positions, not a feed, so a new pin does not reshuffle the dashboard), and a spec can be **updated in place**, keeping its id and its original authorship. `parsePanelSpec` returns `null` for a column that is not a JSON object and the listing drops that row, so one bad spec cannot empty a dashboard. Migrations: DuckDB `0048_panel_specs` + `0049_panel_specs_idx`, Postgres `0020_panel_specs`, SQL Server `0022_panel_specs` + `0023_panel_specs_idx`, ClickHouse `0017_panel_specs`.
22
24
  - Numeric coercion at the store edge (ADR 0051 §2): `coerceRows(metric, rows)`, `numericColumns(rowSchema)`. Every `build*` tags its `QuerySpec` with the registry metric id and every store runner coerces there, so numeric columns reach consumers as JS numbers on DuckDB / ClickHouse / Postgres / SQL Server alike. Registry row schemas are strict `z.number()`; `null` stays `null` (no samples ≠ zero).
23
25
  - Result envelopes (`@uptimizr/db/summary`, ADR 0051 §2): `summarizeRows(metric, rows, ctx)`, `tableResult(metric, rows, ctx)`, `clusterCells(cells, opts)`, `wilsonInterval`, `resultFormatSchema` / `resultSummarySchema` / `tableResultSchema`. Build the collector's `format=table | summary` envelopes from the registry alone — pure, browser-safe, no I/O. The metric's `grain` picks the shape: `ranked` top rows + `rest`, `series` (first/last/min/max/trend/slope over the `axis: true` column), `clusters` (deterministic greedy merge of adjacent occupied cells above a density threshold; 8-neighbourhood in 2D, 26 in 3D), or `record` (the row plus its `rateOf` rates). Bounded by `limits.maxSummaryRows`. `reading` is templated from column semantics — no model. `total`/`share` only for additive units.
26
+ - Spatial labelling (`@uptimizr/db/summary`, ADR 0051 §2 / sketch §B.2): `labelClusters(clusters, { axes, cellSize, scene })`, `labelPoint(point, scene, axes, threshold)`, `isWorldSpatialMetric(metric)`, `worldAxisMap(axes)`. Pass `SummaryContext.scene` = `{ id, regions: [{ id, bounds }], meshes: [{ name, aabb }] }` (the collector loads both from the scene registry once per request) and every cluster of a world-space grid gains `region` (smallest containing region by volume), `regions[]` (every containing id, ascending), `nearestMesh` (a proxy box containing the centroid, else the nearest box centre within `cellSize × 2`) and `distance` (world units, `0` when contained). A containing region upgrades `drill.region` from an ad-hoc box to the region id. Only voxel (`vx/vy/vz`) and ground-bin (`gx/gz`) grids are world-space; viewport and angular grids are left untouched. Missing proxy/regions → `null` plus a caveat naming what is missing, never a guess. Pure, deterministic (ties break by id/name), `O(clusters × boxes)` with an early exit.
27
+ - Query DSL (ADR 0051 §3): `compileQuery(projectId, query, dialect, resolved?, tier?)`, `compileMetric(metric, projectId, options, dialect)`, `toBuilderOptions(query, resolved?, tier?)`, `builderFor(name)`. A validated `queryV1` document (grammar in `@uptimizr/schema`, registry validation in `@uptimizr/metrics`) compiles either onto the metric's own `build*` via `FILTER_TARGETS` — byte-identical to the canned endpoint's spec — or, for a metric that declares `genericGroupBy`, onto the shared `compileGenericGroupBy` at any grain it declares. `compileMetric` dispatches on `options.tier`. A store implements the DSL as `run<Engine>Query(compileMetric(…))`. Resolve a `filters.region` region id and a derived `cellSize` before compiling.
28
+ - Compare / explain / order (ADR 0051 §3, #304), all pure: `compareRows(metric, current, previous, ctx)` joins two runs of the same spec on the dimension key into `{ current, previous, delta, deltaPct, significance? }`; `summarizeComparison` digests that into ranked movers with a templated reading; `twoProportionZ`, `welchT`, `wilsonScoreInterval`, `normalCdf`, `studentTTwoSided` are the statistics behind `significance`; `explainQuery(metric, spec, ctx)` renders the plan with parameters named but never valued, plus `warnings` for a silent capture channel, a below-minimum sample, missing spatial labels and truncation; `applyOrder` re-sorts a delegated result and `ORDER_AFTER_CAP_CAVEAT` says when that is not a bottom-N.
29
+ - Session narrative (ADR 0051 §7): `buildSessionNarrative(events, opts)` + `renderSessionNarrativeText(narrative)` — pure compaction of one session's `AnyEvent[]` into an ordered, `maxEntries`-bounded account (scene changes, dwell above `minDwellMs`, interactions, perf dips below `fpsThreshold`, errors, capability changes, XR, end reason, closing totals entry), timestamps relative to the session start. Shapes and caps come from `@uptimizr/metrics` (`NARRATIVE_LIMITS`). An allow-list, not a redactor: never `visitorId`, `url`, `pageMeta`, `user`, positions/rays, or `device` beyond the engine; custom-event property keys only (ADR 0003).
30
+ - Custom-event vocabulary (ADR 0051 §5): `buildCustomEventVocabulary(projectId, opts, dialect)` + `foldCustomEventVocabulary(rows)`. The query counts `custom` events and distinct sessions per developer-defined name and samples that name's 20 most recent `payload` documents; the pure fold derives the union of `props` keys with a coarse type (`string`/`number`/`boolean`/`null`/`mixed`) from them, because key enumeration over an open JSON object has no portable spelling across DuckDB / ClickHouse / Postgres / SQL Server. Stores call the fold, so the raw payload never leaves this layer and only key names and value kinds are ever reported (ADR 0003). Served as `GET /api/v1/vocabulary/custom-events` and inside the project context document.
31
+ - Insight primitives (ADR 0051 §4, `src/insights/`): `buildMetricBuckets(projectId, { metric, bucket, since, until, scene }, dialect)` — the single dialect-authored per-bucket series both primitives consume — plus `computeBaseline`, `rankMovers`, `resolveBaselineWindow`/`resolveMoversWindows`, `BUCKET_MEASURES` / `BUCKETABLE_METRIC_IDS` / `MOVERS_DEFAULT_METRICS` / `MOVERS_MAX_METRICS`, and the pure statistics (`mean`, `median`, `medianAbsoluteDeviation`, `quantile`, `leastSquaresSlope`, `robustZ`). `detectAnomalies` (#306) scores the same series bucket by bucket — a rolling median/MAD for `spike`/`drop` and `cusumChangePoints` for `shift` — and `contributorDimensionFor` / `contributorWindows` / `attributeContributor` name the dimension value holding the excess, from an optional `groupBy` on `buildMetricBuckets` capped at `ANOMALY_MAX_CONTRIBUTOR_SCANS` extra scans per request. **No statistic runs in SQL** — five dialects disagree about `quantile`/`median`, so the query returns raw bucket values and TypeScript does the rest. A metric is bucketable only if `measures.ts` declares how its `comparable.primary` buckets; metrics defined by joins, window functions or caller-supplied predicates are excluded rather than approximated.
32
+ - Significance and scene health (#307): `computeSignificance` picks the test from the measure — a two-proportion z with Wilson/Newcombe intervals for a declared rate, an exact Poisson conditional-binomial test for a bare count, Welch's t over the per-bucket values for a level (so `n` is the bucket count, never the event count) — and returns `{ a, b, effect, ci95, p, test, effectUnit, significant, powerNote }`. `computeSceneHealth` / `rankSceneHealth` / `HEALTH_FACTORS` score each scene 0-100 over six weighted factors normalised against the project's own preceding window, every factor carrying the metric id, raw value, baseline and weight behind it; `resolveHealthWindows` rounds `until` **up** (the one primitive that does, because no factor is a count). `BUCKET_MEASURE_VARIANTS` declares the named auxiliary series they read (a rate denominator, an FPS tail) — never caller input, and declaring one does not make a metric bucketable.
24
33
  - Parity: `PARITY_CASES`, `diffParity` (DuckDB-vs-ClickHouse cross-engine equality), `numericColumnsForSpec` (assert numeric types per engine).
25
34
  - `readDbSettings()` — environment-driven DuckDB settings with a local-dev default (`DUCKDB_PATH`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uptimizr/db",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "OSS storage contracts (dialect-agnostic query layer, neutral event/metadata types) and the single-file DuckDB store for Uptimizr.",
5
5
  "keywords": [
6
6
  "uptimizr",
@@ -55,15 +55,15 @@
55
55
  "llms.txt"
56
56
  ],
57
57
  "dependencies": {
58
- "@duckdb/node-api": "^1.5.5-r.4",
59
- "zod": "^4.5.4",
60
- "@uptimizr/metrics": "0.1.0",
61
- "@uptimizr/schema": "1.1.0"
58
+ "@duckdb/node-api": "^1.5.5-r.5",
59
+ "zod": "^4.6.5",
60
+ "@uptimizr/metrics": "0.2.0",
61
+ "@uptimizr/schema": "1.2.0"
62
62
  },
63
63
  "devDependencies": {
64
- "@types/node": "^26.4.1",
65
- "tsx": "^4.23.13",
66
- "vitest": "^4.1.11"
64
+ "@types/node": "^26.6.2",
65
+ "tsx": "^4.23.15",
66
+ "vitest": "^5.0.1"
67
67
  },
68
68
  "scripts": {
69
69
  "build": "tsc -p tsconfig.json",