- Updated dependencies [9e45b63]
- Updated dependencies [b20201f]
- @objectstack/spec@16.1.0
- @objectstack/core@16.1.0
-
a9459e6: Analytics drill metadata now snapshots raw grouped values for totals/subtotal rows too (#3214). The ADR-0021 D2 drill sidecar (
drillRawRows, #2080) only coveredresult.rows, but the totals rows added in #1753 carry dimension values and go through the same label resolution — which overwrote their stored value (select option value, lookup/master_detail FK id) with the display label, leaving a subtotal drill nothing to exact-match on.queryDatasetnow also emitsdrillRawTotals, aligned toresult.totalsby index (drillRawTotals[i][j]↔result.totals[i].rows[j]), captured in the same pre-label-resolution pass. Each map is restricted to the drillable dimensions the grouping actually groups by, so the grand-total grouping ([]) contributes an empty map per row. Purely additive result props (same as #2080) — no spec-contract change. -
dd9f223: feat(analytics): scope a datetime date-bucket drill to the reference-tz midnight instants (#1752 follow-up)
Closes the one gap left by the initial #1752 change: a
datetimedate dimension bucketed under a non-UTC reference timezone previously fell back to a superset drill (its bucket boundary is that tz's midnight instant, whichYYYY-MM-DDcalendar bounds can't express).@objectstack/coreaddszonedDateStartToUtcMs(ymd, tz)— the UTC instant at which a calendar day begins in a reference timezone (the inverse ofcalendarPartsInTz). DST-safe: the offset is read from the platform tz database viaIntl, with a two-pass resolution for the rare offset-boundary case; an unset/'UTC'/invalid zone returns plain UTC midnight.@objectstack/service-analyticsnow emitsdrillRangesbounds per the field's temporal type (ADR-0053): adatetimefield → ISO instant bounds at the reference tz's midnight (works under any tz, incl. DST); adatefield →YYYY-MM-DDcalendar bounds (tz-naive, exact under any tz). An unknown field type is still emitted only under UTC and omitted (superset) under a non-UTC tz.
No objectui change is needed — the client already forwards whatever bound values the server sends into the drill filter and the
filter[field][gte|lt]URL. -
290e2f0: feat(analytics): emit a half-open date-range drill scope for granularity-bucketed date dimensions (#1752)
A report/dashboard cell grouped by a
dateGranularitydate dimension ("2026-Q2") covers a SPAN of records, so drilling it needs a range (>= start AND < nextStart), which the equality drill contract (drillRawRows) can't express — date dims were therefore excluded from drill metadata and a drill landed on an unscoped superset.@objectstack/coreaddsbucketKeyToCalendarRange(key, granularity), the inverse ofbucketDateValue: it turns a canonical bucket key into its half-open[start, end)calendar span (YYYY-MM-DD,endexclusive). Pure, timezone-naive calendar arithmetic; returnsnullfor unbucketable / out-of-range keys so the caller falls back to an unscoped (superset) drill rather than emit a wrong bound.@objectstack/service-analyticsemits adrillRangessidecar (aligned torowsby index — the range companion todrillRawRows) fordate+dateGranularitydimensions, computed from the canonical bucket key in the pre-label-resolution snapshot pass. Adatetimefield under a non-UTC reference timezone is omitted (host drills a superset) until instant-boundary support lands; a tz-naivedatefield is exact under any timezone (ADR-0053).
Consumed by objectui's report drill-through to scope the drilled record list to the clicked time bucket.
- Updated dependencies [f972574]
- Updated dependencies [6289ec3]
- Updated dependencies [22013aa]
- Updated dependencies [3ad3dd5]
- Updated dependencies [8efa395]
- Updated dependencies [3a18b60]
- Updated dependencies [a8aa34c]
- Updated dependencies [e057f42]
- Updated dependencies [a3823b2]
- Updated dependencies [43a3efb]
- Updated dependencies [524696a]
- Updated dependencies [bfa3c3f]
- Updated dependencies [5e3301d]
- Updated dependencies [dd9f223]
- Updated dependencies [46e876c]
- Updated dependencies [5f05de2]
- Updated dependencies [021ba4c]
- Updated dependencies [158aa14]
- Updated dependencies [62a2117]
- Updated dependencies [d2723e2]
- Updated dependencies [fefcd54]
- Updated dependencies [beaf2de]
- Updated dependencies [369eb6e]
- Updated dependencies [06ff734]
- Updated dependencies [b659111]
- Updated dependencies [5754a23]
- Updated dependencies [6c270a6]
- Updated dependencies [290e2f0]
- Updated dependencies [668dd17]
- Updated dependencies [8abf133]
- Updated dependencies [e0859b1]
- Updated dependencies [04ecd4e]
- Updated dependencies [4d5a892]
- Updated dependencies [16cebeb]
- Updated dependencies [86d30af]
- Updated dependencies [8923843]
- Updated dependencies [a2795f6]
- Updated dependencies [f16b492]
- Updated dependencies [4b6fde8]
- Updated dependencies [2018df9]
- Updated dependencies [fc5a3a2]
- Updated dependencies [8ff9210]
- @objectstack/spec@16.0.0
- @objectstack/core@16.0.0
- Updated dependencies [6289ec3]
- Updated dependencies [8efa395]
- Updated dependencies [bfa3c3f]
- Updated dependencies [62a2117]
- Updated dependencies [06ff734]
- @objectstack/spec@16.0.0-rc.1
- @objectstack/core@16.0.0-rc.1
-
a9459e6: Analytics drill metadata now snapshots raw grouped values for totals/subtotal rows too (#3214). The ADR-0021 D2 drill sidecar (
drillRawRows, #2080) only coveredresult.rows, but the totals rows added in #1753 carry dimension values and go through the same label resolution — which overwrote their stored value (select option value, lookup/master_detail FK id) with the display label, leaving a subtotal drill nothing to exact-match on.queryDatasetnow also emitsdrillRawTotals, aligned toresult.totalsby index (drillRawTotals[i][j]↔result.totals[i].rows[j]), captured in the same pre-label-resolution pass. Each map is restricted to the drillable dimensions the grouping actually groups by, so the grand-total grouping ([]) contributes an empty map per row. Purely additive result props (same as #2080) — no spec-contract change. -
dd9f223: feat(analytics): scope a datetime date-bucket drill to the reference-tz midnight instants (#1752 follow-up)
Closes the one gap left by the initial #1752 change: a
datetimedate dimension bucketed under a non-UTC reference timezone previously fell back to a superset drill (its bucket boundary is that tz's midnight instant, whichYYYY-MM-DDcalendar bounds can't express).@objectstack/coreaddszonedDateStartToUtcMs(ymd, tz)— the UTC instant at which a calendar day begins in a reference timezone (the inverse ofcalendarPartsInTz). DST-safe: the offset is read from the platform tz database viaIntl, with a two-pass resolution for the rare offset-boundary case; an unset/'UTC'/invalid zone returns plain UTC midnight.@objectstack/service-analyticsnow emitsdrillRangesbounds per the field's temporal type (ADR-0053): adatetimefield → ISO instant bounds at the reference tz's midnight (works under any tz, incl. DST); adatefield →YYYY-MM-DDcalendar bounds (tz-naive, exact under any tz). An unknown field type is still emitted only under UTC and omitted (superset) under a non-UTC tz.
No objectui change is needed — the client already forwards whatever bound values the server sends into the drill filter and the
filter[field][gte|lt]URL. -
290e2f0: feat(analytics): emit a half-open date-range drill scope for granularity-bucketed date dimensions (#1752)
A report/dashboard cell grouped by a
dateGranularitydate dimension ("2026-Q2") covers a SPAN of records, so drilling it needs a range (>= start AND < nextStart), which the equality drill contract (drillRawRows) can't express — date dims were therefore excluded from drill metadata and a drill landed on an unscoped superset.@objectstack/coreaddsbucketKeyToCalendarRange(key, granularity), the inverse ofbucketDateValue: it turns a canonical bucket key into its half-open[start, end)calendar span (YYYY-MM-DD,endexclusive). Pure, timezone-naive calendar arithmetic; returnsnullfor unbucketable / out-of-range keys so the caller falls back to an unscoped (superset) drill rather than emit a wrong bound.@objectstack/service-analyticsemits adrillRangessidecar (aligned torowsby index — the range companion todrillRawRows) fordate+dateGranularitydimensions, computed from the canonical bucket key in the pre-label-resolution snapshot pass. Adatetimefield under a non-UTC reference timezone is omitted (host drills a superset) until instant-boundary support lands; a tz-naivedatefield is exact under any timezone (ADR-0053).
Consumed by objectui's report drill-through to scope the drilled record list to the clicked time bucket.
- Updated dependencies [f972574]
- Updated dependencies [22013aa]
- Updated dependencies [3ad3dd5]
- Updated dependencies [3a18b60]
- Updated dependencies [a8aa34c]
- Updated dependencies [e057f42]
- Updated dependencies [a3823b2]
- Updated dependencies [43a3efb]
- Updated dependencies [524696a]
- Updated dependencies [5e3301d]
- Updated dependencies [dd9f223]
- Updated dependencies [46e876c]
- Updated dependencies [5f05de2]
- Updated dependencies [021ba4c]
- Updated dependencies [158aa14]
- Updated dependencies [d2723e2]
- Updated dependencies [fefcd54]
- Updated dependencies [beaf2de]
- Updated dependencies [369eb6e]
- Updated dependencies [b659111]
- Updated dependencies [5754a23]
- Updated dependencies [6c270a6]
- Updated dependencies [290e2f0]
- Updated dependencies [668dd17]
- Updated dependencies [8abf133]
- Updated dependencies [e0859b1]
- Updated dependencies [04ecd4e]
- Updated dependencies [4d5a892]
- Updated dependencies [16cebeb]
- Updated dependencies [86d30af]
- Updated dependencies [8923843]
- Updated dependencies [a2795f6]
- Updated dependencies [f16b492]
- Updated dependencies [4b6fde8]
- Updated dependencies [2018df9]
- Updated dependencies [fc5a3a2]
- @objectstack/spec@16.0.0-rc.0
- @objectstack/core@16.0.0-rc.0
- @objectstack/spec@15.1.1
- @objectstack/core@15.1.1
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [3fe9df1]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [4109153]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [627f225]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- Updated dependencies [f531a26]
- @objectstack/spec@15.1.0
- @objectstack/core@15.1.0
- Updated dependencies [28b7c28]
- Updated dependencies [13749ec]
- Updated dependencies [e62c233]
- Updated dependencies [ed61c9b]
- Updated dependencies [31d04d4]
- @objectstack/spec@15.0.0
- @objectstack/core@15.0.0
- Updated dependencies [16b4bf6]
- Updated dependencies [16b4bf6]
- Updated dependencies [10e8983]
- Updated dependencies [607aaf4]
- Updated dependencies [bb71321]
- @objectstack/spec@14.8.0
- @objectstack/core@14.8.0
- Updated dependencies [d6a72eb]
- @objectstack/spec@14.7.0
- @objectstack/core@14.7.0
- Updated dependencies [609cb13]
- Updated dependencies [ce6d151]
- @objectstack/spec@14.6.0
- @objectstack/core@14.6.0
- Updated dependencies [526805e]
- Updated dependencies [d79ca07]
- Updated dependencies [33ebd34]
- Updated dependencies [c044f08]
- Updated dependencies [01274eb]
- @objectstack/spec@14.5.0
- @objectstack/core@14.5.0
- Updated dependencies [7953832]
- Updated dependencies [82e745e]
- Updated dependencies [f3035bd]
- Updated dependencies [82c0d94]
- Updated dependencies [7449476]
- @objectstack/spec@14.4.0
- @objectstack/core@14.4.0
- Updated dependencies [2a71f48]
- Updated dependencies [02f6af4]
- Updated dependencies [c1064f1]
- @objectstack/spec@14.3.0
- @objectstack/core@14.3.0
- Updated dependencies [ac8f029]
- Updated dependencies [4ab9958]
- @objectstack/spec@14.2.0
- @objectstack/core@14.2.0
- Updated dependencies [5a8465f]
- Updated dependencies [7f8620b]
- Updated dependencies [82ba3a6]
- @objectstack/spec@14.1.0
- @objectstack/core@14.1.0
- Updated dependencies [0a8e685]
- Updated dependencies [afa8115]
- Updated dependencies [80f12ca]
- Updated dependencies [e2fa074]
- Updated dependencies [23c8668]
- Updated dependencies [29f017d]
- Updated dependencies [216fa9a]
- Updated dependencies [6c22b12]
- @objectstack/spec@14.0.0
- @objectstack/core@14.0.0
- Updated dependencies [6d83431]
- Updated dependencies [01917c2]
- Updated dependencies [b271691]
- Updated dependencies [a5a1e41]
- Updated dependencies [466adf6]
- Updated dependencies [5be00c3]
- Updated dependencies [466adf6]
- Updated dependencies [2bee609]
- Updated dependencies [fc7e7f7]
- @objectstack/spec@13.0.0
- @objectstack/core@13.0.0
- Updated dependencies [6cebf22]
- Updated dependencies [21420d9]
- @objectstack/spec@12.6.0
- @objectstack/core@12.6.0
- Updated dependencies [8b3d363]
- @objectstack/spec@12.5.0
- @objectstack/core@12.5.0
- Updated dependencies [60dc3ba]
- @objectstack/spec@12.4.0
- @objectstack/core@12.4.0
- Updated dependencies [e7eceec]
- @objectstack/spec@12.3.0
- @objectstack/core@12.3.0
- Updated dependencies [fce8ff4]
- Updated dependencies [3962023]
- Updated dependencies [2bb193d]
- Updated dependencies [0426d27]
- Updated dependencies [da807f7]
- Updated dependencies [4f5b791]
- @objectstack/spec@12.2.0
- @objectstack/core@12.2.0
- Updated dependencies [93e6d02]
- @objectstack/spec@12.1.0
- @objectstack/core@12.1.0
- Updated dependencies [a8df396]
- Updated dependencies [e695fe0]
- Updated dependencies [7c09621]
- Updated dependencies [7709db4]
- Updated dependencies [2082109]
- Updated dependencies [7c09621]
- Updated dependencies [9860de4]
- Updated dependencies [069c205]
- @objectstack/spec@12.0.0
- @objectstack/core@12.0.0
- Updated dependencies [6a9397e]
- Updated dependencies [c0efe5d]
- @objectstack/spec@11.10.0
- @objectstack/core@11.10.0
- Updated dependencies [d3595d9]
- @objectstack/spec@11.9.0
- @objectstack/core@11.9.0
- @objectstack/spec@11.8.0
- @objectstack/core@11.8.0
- Updated dependencies [5178906]
- @objectstack/spec@11.7.0
- @objectstack/core@11.7.0
- @objectstack/spec@11.6.0
- @objectstack/core@11.6.0
- Updated dependencies [6ee4f04]
- Updated dependencies [c1e3a65]
- @objectstack/spec@11.5.0
- @objectstack/core@11.5.0
- Updated dependencies [5821c51]
- Updated dependencies [a0fce3f]
- @objectstack/spec@11.4.0
- @objectstack/core@11.4.0
- Updated dependencies [58e8e31]
- Updated dependencies [b4a5df0]
- @objectstack/spec@11.3.0
- @objectstack/core@11.3.0
- Updated dependencies [d0f4b13]
- Updated dependencies [302bdab]
- @objectstack/spec@11.2.0
- @objectstack/core@11.2.0
- Updated dependencies [ce0b4f6]
- Updated dependencies [9ccfcd6]
- Updated dependencies [ecf193f]
- Updated dependencies [51bec81]
- Updated dependencies [3e593a7]
- Updated dependencies [63d5403]
- @objectstack/core@11.1.0
- @objectstack/spec@11.1.0
-
5eef4cf: feat(analytics): multi-hop relationship joins for datasets (ADR-0071)
A dataset's
includeand dimension/measurefieldpaths may now traverse up to 3 to-one relationship hops (account.owner.region), not just one. The compiler expands each declared path into the ordered join chain (onecube.joinper path prefix, aliased dot-free asaccount__ownerso it stays a single valid SQL identifier), and the NativeSQLStrategy emits the chainedLEFT JOINs. Per-hop tenant/RLS read-scope is enforced for EVERY object in the chain — the alias-driven scope loop already generalizes, so no security path is rewritten.Restricted to to-one (lookup / master_detail) relationships, which never fan out — aggregates stay correct with no symmetric-aggregate machinery; to-many traversal is out of scope. Single-hop datasets are byte-for-byte unchanged (the dot-free alias is a no-op for a single segment). Undeclared paths are still rejected (ADR-0021 D-C); paths beyond 3 hops are rejected at both parse and compile time.
-
910a8f0: fix(analytics): compare boolean filters/group-by against the real boolean, not stringified '1'
The analytics filter normalizer stringified boolean
true→'1', which the ObjectQL strategy then coerced back to the number1before callingengine.aggregate. Boolean fields hold a realtrue/false, so1 !== truenever matched: a metric widget filtered on a boolean field (e.g.{ is_critical: true }) always returned 0, and pie/donut/bar charts grouped by a boolean dimension failed to bucket.stringifyForCubenow serializes booleans as the tokens'true'/'false', and a newcoerceFilterValueForObjectQLrecovers a real boolean for the ObjectQL engine while the SQL path keeps binding1/0(better-sqlite3 cannot bind a JS boolean). -
715d667: fix(analytics): qualify base-object columns in joined dataset queries
A dataset that joins a related object (
include+ arelationship.fielddimension/measure) emitted BARE base-table columns in SELECT/GROUP BY while the joined columns were alias-qualified. When the base and joined tables share a column name (e.g. both havestatus), the query failed at runtime with "ambiguous column name".NativeSQLStrategynow qualifies plain base-column identifiers with the base table when the cube has joins; single-object cubes are unchanged (byte-for-byte identical SQL). -
Updated dependencies [ab5718a]
-
Updated dependencies [4845c12]
-
Updated dependencies [c1a754a]
-
Updated dependencies [6fbe91f]
-
Updated dependencies [715d667]
-
Updated dependencies [5eef4cf]
-
Updated dependencies [72759e1]
-
Updated dependencies [6c4fbd9]
-
Updated dependencies [ef3ed67]
-
Updated dependencies [cd51229]
-
Updated dependencies [7697a0e]
-
Updated dependencies [e7e04f1]
-
Updated dependencies [cfd5ac4]
-
Updated dependencies [2be5c1f]
-
Updated dependencies [ad143ce]
-
Updated dependencies [5c4a8c8]
-
Updated dependencies [3afaeed]
-
Updated dependencies [8801c02]
-
Updated dependencies [3d04e06]
-
Updated dependencies [4a84c98]
-
Updated dependencies [c715d25]
-
Updated dependencies [aa33b02]
-
Updated dependencies [d980f0d]
-
Updated dependencies [a658523]
-
Updated dependencies [82ff91c]
-
Updated dependencies [638f472]
- @objectstack/spec@11.0.0
- @objectstack/core@11.0.0
-
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