-
f73d40a: fix(analytics): log scalar auto-inferred cubes at debug, not warn
Scalar metric queries (measures only, no
dimensions/timeDimensions) over an unregistered cube — the first-classobject-metric"metric over an object" path — auto-infer a trivial count/sum cube by design. That auto-infer now logs atdebuginstead ofwarn, so boot/render no longer spamsNo cube registered for "..."for a non-problem. Grouped queries (explicit dimension / time bucket) over an unregistered cube keep thewarn, where a forgotten cube registration is a real mistake.- @objectstack/spec@10.3.0
- @objectstack/core@10.3.0
- Updated dependencies [b496498]
- @objectstack/spec@10.2.0
- @objectstack/core@10.2.0
-
49da36e: feat(analytics): correct analytics over federated objects (ADR-0062 Phase 3, D6)
Analytics over an external (federated) object now aggregates against the correct remote table instead of silently querying the wrong one. The
NativeSQLStrategyhand-compilesFROM "<object>"and bare column references, which bypass the driver's physical-table resolution (external.remoteName/remoteSchema/columnMap). It now declines any query whose base or joined object is federated, routing it to theObjectQLStrategy— whoseengine.aggregate()goes through the driver'sgetBuilderand already honoursremoteName/remoteSchema(#2138/#2149). This "reuses the driver's resolution" (D6) rather than re-implementing it.Adds an optional
StrategyContext.isExternalObject(objectName)hook (reported by the analytics plugin from the object'sexternalblock). Purely additive — with no hook, behavior is unchanged for managed objects.
- Updated dependencies [49da36e]
- Updated dependencies [ac79f16]
- @objectstack/spec@10.1.0
- @objectstack/core@10.1.0
-
70609af: Resolve a monetary measure's display currency via the field→tenant chain.
A dataset measure-currency now resolves through: explicit measure
currency→ source-fieldcurrencyConfig.defaultCurrency→ tenant default (ctx.currency). A measure is monetary iff it declares a currency or aggregates acurrency-type field, so count/avg-of-number measures never receive a code. Wires ameasureCurrencyfield-metadata resolver from the data engine's object schema. -
3187952: Dataset analytics enrich dimension result fields with their display label (so report/dashboard table headers read "Status" instead of the raw field name) and expose drill-through metadata on the dataset query result: the base
object, a drillable dimension→field map, and a paralleldrillRawRowsarray of each row's raw grouped values (captured before label resolution). This lets a host drill a grouped bucket back to its underlying records with an exact-match filter built from the stored value, not the display label. Date dimensions are excluded (a humanized bucket can't be exact-matched). -
a581385: Propagate a dataset measure's declared currency to the analytics result field.
Adds an optional
DatasetMeasure.currency(ISO 4217) on the semantic layer and carries it onto each measure result field alongsidelabel/format, so a currency-aware client (Intl symbol) can render¥1,234/$616,000from a real currency code instead of a plain number or a$baked intoformat. Additive and optional — existing datasets are unaffected.
- Updated dependencies [d7ff626]
- Updated dependencies [2a1b16b]
- Updated dependencies [e16f2a8]
- Updated dependencies [e411a82]
- Updated dependencies [a581385]
- Updated dependencies [d5f6d29]
- Updated dependencies [220ce5b]
- Updated dependencies [3efe334]
- Updated dependencies [feead7e]
- Updated dependencies [6ca20b3]
- Updated dependencies [5f875fe]
- Updated dependencies [b469950]
- @objectstack/spec@10.0.0
- @objectstack/core@10.0.0
- Updated dependencies [e7f6539]
- Updated dependencies [2365d07]
- Updated dependencies [6595b53]
- Updated dependencies [fa8964d]
- Updated dependencies [36138c7]
- Updated dependencies [a8e4f3b]
- Updated dependencies [4c213c2]
- Updated dependencies [2afb612]
- @objectstack/spec@9.11.0
- @objectstack/core@9.11.0
-
db02bd5: Fix dashboard time-series charts / "last N months" KPIs that filter or group by a
Field.datetimecolumn silently returning "No rows".The analytics
NativeSQLStrategycompiles dashboard relative-date tokens ({12_months_ago},{today}, …) to ISO date strings and binds them directly into raw SQL, bypassing the driver's own filter coercion. Under better-sqlite3 aField.datetimecolumn is stored as an INTEGER epoch (ms), soassessed_at >= '2025-06-18'became a TEXT-vs-INTEGER affinity compare that is always false — an empty result even though the rows exist.Field.datecolumns store ISO TEXT and were unaffected.The strategy now coerces a temporal comparand to the column's on-disk storage form via a new optional
StrategyContext.coerceTemporalFilterValuehook, wired to the driver's publicSqlDriver.temporalFilterValue(the single source of truth for the storage convention). Coercion is dialect-correct: SQLiteField.datetime→ epoch ms;Field.datetext and native-timestamp dialects (Postgres/MySQL) are left unchanged, so Postgres is never handed an epoch integer. Applied togte/lte/gt/lt/equals,in/notIn, and thedateRange/timeDimensionBETWEENpath. -
fd07027: fix(analytics): make organization timezone actually drive date-dimension bucketing (ADR-0053 Phase 2, #1982)
Date-bucketed analytics silently ignored the reference timezone end-to-end. Three independent seams were broken:
- service-analytics —
NativeSQLStrategy(priority 10) won every cube/dataset query on a SQL driver, but it groups by the raw column (nodate_trunc) and ignorestimezone, so a date dimension never bucketed (one row per raw timestamp) and a non-UTC zone was dropped. It now declines queries that carry atimeDimensions[].granularity, handing them toObjectQLStrategy→engine.aggregate(native bucketing when UTC-safe, uniform in-memory bucketing when non-UTC). - objectql — the in-memory
countaggregation treated the*count-all sentinel (the Cubecountmeasure / a fieldless datasetcount, both compiled tosql: '*') as a column name, counting non-null of a non-existent property →0for every bucket. The driver'sCOUNT(*)masked it; the in-memory path (non-UTC date buckets,driver-rest/driver-memory) returned zeros.*is now counted as all rows. - rest —
resolveExecCtxnever resolved the localization timezone/locale, so/analytics/dataset/queryalways ran withtimezone: 'UTC'. It now resolves them through thesettingsservice (honouring the 4-tier cascade incl. theOS_LOCALIZATION_TIMEZONEenv override), mirroring the dispatcher path.
- service-analytics —
-
Updated dependencies [db02bd5]
-
Updated dependencies [641675d]
-
Updated dependencies [94e9040]
-
Updated dependencies [1f88fd9]
-
Updated dependencies [1f88fd9]
- @objectstack/spec@9.10.0
- @objectstack/core@9.10.0
- @objectstack/spec@9.9.1
- @objectstack/core@9.9.1
-
9afeb2d: feat(settings):
localizationsettings — platform default timezone, language & formats (ADR-0053 Phase 2)Adds a
localizationSettingsManifest, the missing keystone that makes the Phase 2 reference-timezone actually configurable end-to-end. One declaration gives the full settings stack for free: platform built-in default →global→tenantcascade, a permission-gated settings page, and i18n.Keys (organization-level; per-user overrides intentionally out of scope for v1):
timezone(UTC),locale(en-US),default_country,date_format,time_format,number_format,first_day_of_week,currency(USD),fiscal_year_start. Benchmarked against Salesforce/Workday "Company Information + Locale".Resolver 收编 —
resolveExecutionContextnow resolvestimezoneandlocalefrom thelocalizationsettings via thesettingsservice (canonical 4-tier cascade), falling back to a direct tenant-scopedsys_settingread, thenUTC/en-US. This replaces the hand-rolledsys_user_preference+ tenant-onlysys_settingpath from #1978 (which bypassed the settings abstraction and is dropped along with the per-user tier). NewExecutionContext.locale.Consumer wiring — analytics date bucketing now picks up the resolved org timezone:
DatasetExecutorthreadsExecutionContext.timezoneinto the query (precedence: explicit selection tz → request tz → UTC), so #1982's tz-aware buckets fire for a configured org without callers passing a zone. Formulatoday()/datetimewere already wired (#1979/#1980).Email
datetimerendering (SendTemplateInput.timezone, shipped in #1981) is intentionally not wired here: the only currentsendTemplatecallers are pre-session auth emails with no org context; business-notification callers can pass the zone when they appear. -
601cc11: feat(analytics): timezone-aware date bucketing (ADR-0053 Phase 2)
Analytics day/week/month/quarter/year buckets now resolve on a reference timezone's calendar days, so a row near a tz day-boundary lands in the bucket a user in that zone would expect — identically on SQLite and Postgres.
Per ADR-0053 decision D2, bucketing is done in-memory, uniformly for non-UTC zones rather than emitting dialect-specific
date_trunc … AT TIME ZONE(SQLite has no tz database and MySQL needs tz tables loaded, so splitting by dialect would shift bucket boundaries for the same data).engine.aggregate({ timezone })therefore forces the in-memory aggregation path when a non-UTC reference tz is set — the date-rangewherestill goes to the driver, so only matching rows are fetched. UTC / unset keeps the native driver fast path unchanged.- New shared
calendarPartsInTz/calendarPartsInTzOrUtcutil in@objectstack/core(DST-safe viaIntl.DateTimeFormat, never hand-rolled offset math; falls back to UTC for an unset/'UTC'/invalid zone). EngineAggregateOptionsand the analyticsexecuteAggregatebridge /ObjectQLStrategythread the reference timezone (sourced from the dataset selection /ExecutionContext) through toapplyInMemoryAggregation→bucketDateValue, and the draft-preview evaluator'sbucketDate.formatDateBucket(dimension labels) stays UTC-only by design: it re-labels values that were already bucketed upstream, so re-applying a timezone there would shift a correct bucket by a day.
- New shared
- Updated dependencies [84249a4]
- Updated dependencies [11af299]
- Updated dependencies [d5774b5]
- Updated dependencies [134043a]
- Updated dependencies [90108e0]
- Updated dependencies [9afeb2d]
- Updated dependencies [6bec07e]
- Updated dependencies [601cc11]
- Updated dependencies [575448d]
- @objectstack/spec@9.9.0
- @objectstack/core@9.9.0
- Updated dependencies [97c55b3]
- Updated dependencies [1b1f490]
- @objectstack/spec@9.8.0
- @objectstack/core@9.8.0
- @objectstack/spec@9.7.0
- @objectstack/core@9.7.0
- Updated dependencies [d1e930a]
- Updated dependencies [71578f2]
- Updated dependencies [5e3a301]
- Updated dependencies [5db2742]
- @objectstack/spec@9.6.0
- @objectstack/core@9.6.0
- Updated dependencies [ee72aae]
- @objectstack/spec@9.5.1
- @objectstack/core@9.5.1
- Updated dependencies [d08551c]
- Updated dependencies [707aeed]
- Updated dependencies [7a103d4]
- Updated dependencies [4b01250]
- @objectstack/spec@9.5.0
- @objectstack/core@9.5.0
- Updated dependencies [060467a]
- Updated dependencies [0856476]
- Updated dependencies [b678d8c]
- Updated dependencies [b678d8c]
- Updated dependencies [b678d8c]
- @objectstack/spec@9.4.0
- @objectstack/core@9.4.0
- b4765be: Server-side totals for matrix reports (#1753).
queryDatasetselections accepttotals: { groupings: string[][] }— each grouping a subset ofselection.dimensionsto additionally aggregate by ([]= grand total); the marginal rows come back onAnalyticsResult.totalsin request order. Each subtotal/grand total re-runs the full executor pipeline (measure-scoped filters, derived measures, compareTo) grouped only by that subset, so totals use each measure's true aggregate over the underlying rows — anavgtotal is the average of all rows, never an average of bucket averages (the ADR-0021 line that forbids client-side re-aggregation). Dimension display labels resolve on totals rows the same as the primary grid. A matrix report renderer asks for{ groupings: [rowDims, columnDims, []] }and renders the supplied totals row/column.
- Updated dependencies [1ada658]
- Updated dependencies [3219191]
- Updated dependencies [290f631]
- Updated dependencies [50b7b47]
- Updated dependencies [f15d6f6]
- Updated dependencies [f8684ea]
- Updated dependencies [b4765be]
- @objectstack/spec@9.3.0
- @objectstack/core@9.3.0
- Updated dependencies [2f57b75]
- Updated dependencies [2f57b75]
- @objectstack/spec@9.2.0
- @objectstack/core@9.2.0
- Updated dependencies [b9062c9]
- @objectstack/spec@9.1.0
- @objectstack/core@9.1.0
- Updated dependencies [1817845]
- @objectstack/spec@9.0.1
- @objectstack/core@9.0.1
-
4a0736b: Analytics now renders date dimensions as human bucket labels instead of raw epoch millis, and buckets them by their declared granularity.
- A date dimension with an explicit
dateGranularityis now grouped by that bucket (the executor promotes it to a time dimension), so a "monthly" trend chart shows one point per month rather than one per raw timestamp. - Grouped date values are formatted to a sort-stable label per granularity
(
year→2026,quarter→2026-Q2,month→2026-04,day/week→2026-04-15), so charts no longer show1777632968596.
Pairs with the dimension display-label resolution (select option labels / lookup names) shipped previously.
- A date dimension with an explicit
-
2c6864f: Analytics dimensions now render human display labels instead of raw stored values. A
selectdimension shows its optionlabel(e.g.Backlograther thanbacklog), and alookup/master_detaildimension shows the related record's display name (e.g. an account's name rather than its FK id).queryDatasetresolves these server-side, so every dashboard/report chart benefits with no frontend change. Date/number/string dimensions are unaffected, and unresolved values are left as-is. -
0bf39f1:
queryDatasetnow carries each measure's displaylabelandformaton the resultfields, so presentations can show "Tasks" / "$616,000" instead of the raw measure name "task_count" / "616000".AnalyticsResult.fields[]gains optionallabel?andformat?.- The dataset executor enriches measure columns from the dataset's measure
definitions (matching
<name>and<name>__compare).
The format can't be baked into the numeric row value (charts need the raw number), so the renderer applies it at display time.
- Updated dependencies [4c3f693]
- Updated dependencies [0bf39f1]
- Updated dependencies [f533f42]
- Updated dependencies [1c83ee8]
- @objectstack/spec@9.0.0
- @objectstack/core@9.0.0
- @objectstack/spec@8.0.1
- @objectstack/core@8.0.1
- Updated dependencies [a46c017]
- Updated dependencies [b990b89]
- Updated dependencies [99111ec]
- Updated dependencies [d5a8161]
- Updated dependencies [5cf1f1b]
- Updated dependencies [9ef89d4]
- Updated dependencies [3306d2f]
- Updated dependencies [c262301]
- Updated dependencies [bc44195]
- Updated dependencies [9e2e229]
- @objectstack/spec@8.0.0
- @objectstack/core@8.0.0
- @objectstack/spec@7.9.0
- @objectstack/core@7.9.0
- Updated dependencies [06f2bbb]
- Updated dependencies [36719db]
- Updated dependencies [424ab26]
- @objectstack/spec@7.8.0
- @objectstack/core@7.8.0
- Updated dependencies [b391955]
- Updated dependencies [f06b64e]
- Updated dependencies [023bf93]
- @objectstack/spec@7.7.0
- @objectstack/core@7.7.0
- Updated dependencies [955d4c8]
- Updated dependencies [c4a4cbd]
- Updated dependencies [b046ec2]
- Updated dependencies [2170ad9]
- Updated dependencies [02d6359]
- Updated dependencies [7648242]
- Updated dependencies [8fa1e7f]
- Updated dependencies [55866f5]
- Updated dependencies [60f9c45]
- @objectstack/spec@7.6.0
- @objectstack/core@7.6.0
- @objectstack/spec@7.5.0
- @objectstack/core@7.5.0
- @objectstack/spec@7.4.1
- @objectstack/core@7.4.1
- Updated dependencies [23c7107]
- Updated dependencies [c72daad]
- Updated dependencies [f115182]
- Updated dependencies [2faf9f2]
- Updated dependencies [2faf9f2]
- Updated dependencies [2faf9f2]
- Updated dependencies [58b450b]
- Updated dependencies [82eb6cf]
- Updated dependencies [13d8653]
- Updated dependencies [ff3d006]
- Updated dependencies [5e831de]
- @objectstack/spec@7.4.0
- @objectstack/core@7.4.0
- Updated dependencies [5e7c554]
- @objectstack/spec@7.3.0
- @objectstack/core@7.3.0
- @objectstack/spec@7.2.1
- @objectstack/core@7.2.1
- @objectstack/spec@7.2.0
- @objectstack/core@7.2.0
- Updated dependencies [47a92f4]
- @objectstack/spec@7.1.0
- @objectstack/core@7.1.0
- Updated dependencies [74470ad]
- Updated dependencies [d29617e]
- Updated dependencies [dc72172]
- @objectstack/spec@7.0.0
- @objectstack/core@7.0.0
- @objectstack/spec@6.9.0
- @objectstack/core@6.9.0
- @objectstack/spec@6.8.1
- @objectstack/core@6.8.1
- Updated dependencies [6e88f77]
- Updated dependencies [c8b9f57]
- @objectstack/spec@6.8.0
- @objectstack/core@6.8.0
- @objectstack/spec@6.7.1
- @objectstack/core@6.7.1
- Updated dependencies [430067b]
- Updated dependencies [4f9e9d4]
- @objectstack/spec@6.7.0
- @objectstack/core@6.7.0
- Updated dependencies [a49cfc2]
- @objectstack/spec@6.6.0
- @objectstack/core@6.6.0
- @objectstack/spec@6.5.1
- @objectstack/core@6.5.1
- @objectstack/spec@6.5.0
- @objectstack/core@6.5.0
- Updated dependencies [f8651cc]
- Updated dependencies [f8651cc]
- Updated dependencies [0bf6f9a]
- @objectstack/spec@6.4.0
- @objectstack/core@6.4.0
- @objectstack/spec@6.3.0
- @objectstack/core@6.3.0
- Updated dependencies [b4c74a9]
- @objectstack/spec@6.2.0
- @objectstack/core@6.2.0
- @objectstack/spec@6.1.1
- @objectstack/core@6.1.1
- Updated dependencies [93c0589]
- @objectstack/spec@6.1.0
- @objectstack/core@6.1.0
- Updated dependencies [629a716]
- Updated dependencies [dbc4f7d]
- Updated dependencies [944f187]
- @objectstack/spec@6.0.0
- @objectstack/core@6.0.0
- Updated dependencies [bab2b20]
- Updated dependencies [fa011d8]
- Updated dependencies [b806f58]
- @objectstack/spec@5.2.0
- @objectstack/core@5.2.0
- Updated dependencies [75f4ee6]
- Updated dependencies [823d559]
- @objectstack/spec@5.1.0
- @objectstack/core@5.1.0
- Updated dependencies [2f9073a]
- @objectstack/spec@5.0.0
- @objectstack/core@5.0.0
- Updated dependencies [2869891]
- @objectstack/spec@4.2.0
- @objectstack/core@4.2.0
- @objectstack/spec@4.1.1
- @objectstack/core@4.1.1
- Updated dependencies [2108c30]
- Updated dependencies [23db640]
- @objectstack/spec@4.1.0
- @objectstack/core@4.1.0
- 15e0df6: chore: unify all package versions to a single patch release
- Updated dependencies [15e0df6]
- @objectstack/spec@4.0.5
- @objectstack/core@4.0.5
- Updated dependencies [326b66b]
- @objectstack/spec@4.0.4
- @objectstack/core@4.0.4
- @objectstack/spec@4.0.3
- @objectstack/core@4.0.3
- Updated dependencies [5f659e9]
- @objectstack/spec@4.0.2
- @objectstack/core@4.0.2
- Updated dependencies [f08ffc3]
- Updated dependencies [e0b0a78]
- @objectstack/spec@4.0.0
- @objectstack/core@4.0.0
- @objectstack/spec@3.3.1
- @objectstack/core@3.3.1
- @objectstack/spec@3.3.0
- @objectstack/core@3.3.0
All notable changes to this package will be documented in this file.
- Initial implementation of
@objectstack/service-analytics AnalyticsServiceorchestrator implementingIAnalyticsService- Strategy pattern with priority chain:
- P1 — NativeSQLStrategy: Pushes queries as native SQL to SQL-capable drivers (Postgres, MySQL, etc.)
- P2 — ObjectQLStrategy: Translates analytics queries into ObjectQL
engine.aggregate()calls - P3 — InMemoryStrategy: Delegates to any registered
IAnalyticsService(e.g.,MemoryAnalyticsService)
CubeRegistryfor auto-discovery and registration of cubes from manifest definitions and object schema inferenceAnalyticsServicePluginfor kernel plugin lifecycle integrationqueryCapabilities()driver capability probing for strategy selectiongenerateSql()dry-run SQL generation across all strategies- Unit tests covering all strategy branches