Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/cube-registry-docblock-measured-15019.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@objectstack/service-analytics": patch
---

`CubeRegistry`'s documentation now describes what the class actually does. Four claims it shipped were measured false against the built package; no behaviour changes, and the corrected text ships in `dist/index.d.ts`, where consumers read it.

The class docblock said cubes reach the registry "from two sources: manifest definitions, and object schema inference". Neither half held. Two sources were missing — a compiled dataset's Cube (ADR-0021), registered under the dataset's name by `queryDataset`, and the ad-hoc Cube `ensureCube` / `inferCubeFromQuery` mints from the members a query references. And object schema inference is `inferFromObject`, which no path in this repository calls: its only in-tree caller is a unit test. The list now names the three sources that do write to the registry, and points at the method for the fourth door instead of advertising it as delivered.

`inferFromObject`'s own "heuristic rules" list was wrong in three of five bullets. Driving the built package:

- `number` / `currency` / `percent` fields mint one `sum` and one `avg` measure each — not the documented `sum`, `avg`, `min`, `max`. No `min` or `max` measure exists.
- `boolean` fields become a `boolean` dimension and nothing else. The documented "`count` measure (count where true)" is not minted.
- Every field becomes a dimension. The documented "all non-computed fields" implies an exclusion the code does not have, on a parameter that carries no such flag.

The two accurate bullets (a default `count` measure, and `date` / `datetime` fields becoming `time` dimensions granulated day/week/month/quarter/year) are kept and stated in the form the run produced.

The method's docblock now also records what it is: a published method with no in-repo caller, still callable by consumers through the package entry (`CubeRegistry`) or `AnalyticsService.cubeRegistry`, whose output does reach the wire because `getMeta()` serves its labels as `CubeMeta` titles.
60 changes: 48 additions & 12 deletions packages/services/service-analytics/src/cube-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,26 @@ import type { Cube } from '@objectstack/spec/data';
/**
* CubeRegistry — Central registry for analytics cube definitions.
*
* Cubes can be registered from two sources:
* 1. **Manifest definitions** — Explicit cube definitions in `objectstack.config.ts`.
* 2. **Object schema inference** — Auto-generated cubes from ObjectQL object schemas.
* The registry is the single source of truth for cube metadata discovery:
* `getMeta()` maps every registered cube's measure/dimension `label` onto the
* `CubeMeta` titles served by `GET /api/v1/analytics/meta`, and the strategy
* chain resolves a query's cube through it.
*
* The registry is the single source of truth for cube metadata discovery
* (used by `getMeta()` and the strategy chain).
* Three sources write to it, all of them from `AnalyticsService`:
* 1. **Manifest definitions** — `AnalyticsServiceConfig.cubes` (`registerAll`),
* i.e. explicit cube definitions authored in `objectstack.config.ts`.
* 2. **Compiled datasets** (ADR-0021) — `compileDataset()`'s Cube, registered
* under the dataset's name by `queryDataset`.
* 3. **Ad-hoc query inference** — `ensureCube` / `inferCubeFromQuery` mints a
* minimal Cube from the members an `AnalyticsQuery` references, once
* `assertInferableCube` (#3867) has confirmed the name is a registered
* object. It infers from the QUERY, never from the object's field schema.
*
* This list used to read "two sources: manifest definitions, and object schema
* inference". Neither half was right: sources 2 and 3 were missing, and object
* schema inference is `inferFromObject` below, which no path in this repository
* calls (#15019). It is described at the method rather than advertised here,
* because listing it would promise a source the platform does not deliver.
*/
export class CubeRegistry {
private cubes = new Map<string, Cube>();
Expand Down Expand Up @@ -58,14 +72,36 @@ export class CubeRegistry {
}

/**
* Auto-generate a cube definition from an object schema.
* Auto-generate a cube definition from an object's FIELD SCHEMA, and register
* it under `objectName`.
*
* ⚠️ Nothing in this repository calls this — the only in-tree caller is a unit
* test, and every cube the platform registers itself comes from one of the
* three sources named on the class above (#15019). That is not the same thing
* as unreachable: `CubeRegistry` is exported from the package entry and
* `AnalyticsService.cubeRegistry` is public, so a consumer of
* `@objectstack/service-analytics` can call it, and what it mints does reach
* the wire — `getMeta()` serves the labels below as `CubeMeta` titles. Whether
* this published method is removed or wired up as a real cube source is #15019.
*
* Heuristic rules, measured by driving the built package (the list this
* replaces claimed three behaviours the code does not have — `min`/`max`
* measures, a `count` measure for booleans, and a computed-field exclusion):
* - `number` / `currency` / `percent` fields → one `sum` and one `avg` measure
* each, labelled with the field's label plus ` (Sum)` / ` (Avg)`. No `min`
* or `max` measure is minted.
* - EVERY field becomes a dimension; there is no computed-field exclusion (the
* `fields` parameter carries no flag one could exclude on).
* - `boolean` fields become a `boolean` DIMENSION and nothing else — no count
* measure is minted for them.
* - `date` / `datetime` fields → `time` dimensions granulated
* day/week/month/quarter/year.
* - A default `count` measure labelled `Count` is always added.
*
* Heuristic rules:
* - `number` fields → `sum`, `avg`, `min`, `max` measures
* - `boolean` fields → `count` measure (count where true)
* - All non-computed fields → dimensions
* - `date`/`datetime` fields → time dimensions with standard granularities
* - A default `count` measure is always added
* Those three defaults (`Count`, and the two composites) are English literals
* with no i18n hook; #14492's ruling listed the `Count` one as a site to carry
* the `builtinAggregate` discriminator, and it was left alone because no
* in-repo path reaches it.
*
* @param objectName - The snake_case object name (used as table/cube name)
* @param fields - Array of field descriptors `{ name, type, label? }`
Expand Down
Loading