You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4edb2de
Browse filesBrowse the repository at this point in the historyBrowse files
feat(lint,spec): os validate refuses a non-datetime duration column; docs, changeset, ledger
- lint: `measure-duration-field-type-refused` at `duration.from` / `.to`, with an
`os explain` entry; `dataset-field-unknown` checks both columns.
- spec: `DATASET_DURATION_FIELD_TYPE`; the unit's type alias shares its schema's
name; liveness drills `duration.{from,to,unit}`.
- docs: the duration measure in analytics.mdx and validating-metadata.mdx.
- changeset: minor on spec, service-analytics and lint.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKPixnkg91qCkQCVFDJuqW
feat(spec,analytics,lint): a dataset measure can aggregate the time between two datetime fields of each record
8
+
9
+
Clause-②: yes (widening)
10
+
Metadata-protocol: added
11
+
12
+
-**`@objectstack/spec`**: `DatasetMeasureSchema` gains an optional `duration: { from, to, unit }`, in place of `field`. `from` and `to` name two `datetime` fields of the dataset's own object by their bare names, `unit` is `seconds`, `minutes`, `hours` or `days`, and the measure's `aggregate` is `avg`, `min`, `max` or `sum` of `to` minus `from`. A relationship path, an expression or the row wildcard in either column, the same column twice, another unit, a counting aggregate, or a `field`, `derived` or `currency` beside it is refused at parse. New exports: `DatasetMeasureDurationSchema`, `DatasetDurationUnit`, `DATASET_DURATION_UNIT_SECONDS`, `DATASET_DURATION_AGGREGATES`, `DATASET_DURATION_FIELD_TYPE` and the `DatasetMeasureDuration` / `DatasetDurationUnit` types.
13
+
-**`@objectstack/service-analytics`**: the dataset compile refuses a duration column declared anything but `datetime` (`400 DATASET_INVALID`, naming the column and its type). The native-SQL strategy computes the duration as one statement on SQLite, PostgreSQL and MySQL, with the same number on each: a record missing either instant is left out, a `to` before its `from` counts as negative, and over a group with no such record `sum` answers `0` and the others `null`. Measure filters, derived measures, ordering and the draft preview serve it. A query the native strategy does not serve (a time dimension bucketed by `granularity`, a field-to-field filter, an object served through engine middleware or an external datasource, a non-SQL datasource) is refused with `400 INVALID_FIELD` naming the measure. Its two columns pass the field-level read gate like any measure's field. The temporal half of the aggregate × field-type refusal now names the duration measure instead of a stored "days open" number.
14
+
-**`@objectstack/lint`**: `os validate` refuses a duration column not declared `datetime` with the new gating rule `measure-duration-field-type-refused` (at `datasets[N].measures[M].duration.from` / `.to`), with an `os explain` entry. `dataset-field-unknown` now also reports a duration column the object does not declare.
15
+
16
+
What to do: nothing. A dataset without `duration` parses, compiles and answers exactly as before.
Copy file name to clipboardExpand all lines: content/docs/references/ui/dataset.mdx
+38-3Lines changed: 38 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
---
2
2
title: Dataset schema — UI Protocol reference
3
3
navTitle: Dataset
4
-
description: "Analytics Dataset — the one semantic layer. Reference for Dataset, DatasetDimension, DatasetMeasure, DerivedMeasureOp: every property with its type and default."
4
+
description: "Analytics Dataset — the one semantic layer. Reference for Dataset, DatasetDimension, DatasetDurationUnit and 3 more: every property with its type and default."
5
5
---
6
6
7
7
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -34,8 +34,8 @@ layer is absorbed/retired in a later phase (D-A).
@@ -89,6 +89,7 @@ const result = DatasetSchema.parse(data);
89
89
|**format**|`string`| optional | Numeral pattern for a NUMERIC measure — grouping, decimals, percent; e.g. "0,0.00", "0.0%". An amount takes its symbol from `currency`, not from a "$" in the pattern. A DATE-valued measure never reads a date pattern: `"YYYY-MM-DD"` renders that arm's default face. A date or datetime value reads `format` as a display style — `short` or `relative`, honoured on both. |
90
90
|**currency**|`string`| optional | Display currency code (ISO 4217) |
|**duration**|`{ from: string; to: string; unit: Enum<'seconds' \| 'minutes' \| 'hours' \| 'days'> }`| optional | Time between two datetime columns of each record (`{ from, to, unit }`), reduced by `aggregate` (avg / min / max / sum). Records with either instant null are excluded. Replaces `field`. |
92
93
93
94
### Nested Shape: `Dataset.protection`
94
95
@@ -115,6 +116,18 @@ const result = DatasetSchema.parse(data);
115
116
|**nullLabel**|`string \| Record<string, string>`| optional | Label for the bucket of rows whose value for this dimension is null (e.g. "System" on a user axis). Display copy only: grouping is unchanged. When absent, consumers fall back to their own default label for that bucket. |
116
117
117
118
119
+
---
120
+
121
+
## DatasetDurationUnit
122
+
123
+
### Allowed Values
124
+
125
+
*`seconds`
126
+
*`minutes`
127
+
*`hours`
128
+
*`days`
129
+
130
+
118
131
---
119
132
120
133
## DatasetMeasure
@@ -131,6 +144,28 @@ const result = DatasetSchema.parse(data);
131
144
|**format**|`string`| optional | Numeral pattern for a NUMERIC measure — grouping, decimals, percent; e.g. "0,0.00", "0.0%". An amount takes its symbol from `currency`, not from a "$" in the pattern. A DATE-valued measure never reads a date pattern: `"YYYY-MM-DD"` renders that arm's default face. A date or datetime value reads `format` as a display style — `short` or `relative`, honoured on both. |
132
145
|**currency**|`string`| optional | Display currency code (ISO 4217) |
|**duration**|`{ from: string; to: string; unit: Enum<'seconds' \| 'minutes' \| 'hours' \| 'days'> }`| optional | Time between two datetime columns of each record (`{ from, to, unit }`), reduced by `aggregate` (avg / min / max / sum). Records with either instant null are excluded. Replaces `field`. |
148
+
149
+
### Nested Shape: `DatasetMeasure.duration`
150
+
151
+
| Property | Type | Required | Description |
152
+
| :--- | :--- | :--- | :--- |
153
+
|**from**|`string`| ✅ | The earlier instant: a datetime field of the dataset's own object, by its bare name. |
154
+
|**to**|`string`| ✅ | The later instant: a datetime field of the dataset's own object, by its bare name. |
155
+
|**unit**|`Enum<'seconds' \| 'minutes' \| 'hours' \| 'days'>`| ✅ | Unit of the result: the measure answers a number of these (1.5 with "days" is a day and a half). |
156
+
157
+
158
+
---
159
+
160
+
## DatasetMeasureDuration
161
+
162
+
### Properties
163
+
164
+
| Property | Type | Required | Description |
165
+
| :--- | :--- | :--- | :--- |
166
+
|**from**|`string`| ✅ | The earlier instant: a datetime field of the dataset's own object, by its bare name. |
167
+
|**to**|`string`| ✅ | The later instant: a datetime field of the dataset's own object, by its bare name. |
168
+
|**unit**|`Enum<'seconds' \| 'minutes' \| 'hours' \| 'days'>`| ✅ | Unit of the result: the measure answers a number of these (1.5 with "days" is a day and a half). |
0 commit comments