@segmentstream-cloud/app 0.13.2 → 0.14.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.
package/README.md CHANGED
@@ -446,17 +446,17 @@ reach. Look at or change records through `find` and actions; count, sum or compa
446
446
 
447
447
  ```ts
448
448
  const { fields, rows, execution } = await segmentstream.ontology.aggregate({
449
- measures: ["trendtoysSales.revenue", "trendtoysStock.onHand"],
450
- dimensions: ["trendtoysProduct.assortmentStatus"],
449
+ measures: ["acmeSales.revenue", "acmeStock.onHand"],
450
+ dimensions: ["acmeProduct.assortmentStatus"],
451
451
  filters: [
452
452
  {
453
- dimension: "trendtoysSales.orderDate",
453
+ dimension: "acmeSales.orderDate",
454
454
  operator: "between",
455
455
  values: ["2026-09-01", "2026-09-30"],
456
456
  },
457
- { dimension: "trendtoysSales.currency", operator: "equals", value: "RUB" },
457
+ { dimension: "acmeSales.currency", operator: "equals", value: "RUB" },
458
458
  ],
459
- orderBy: [{ measure: "trendtoysSales.revenue", direction: "desc" }],
459
+ orderBy: [{ measure: "acmeSales.revenue", direction: "desc" }],
460
460
  });
461
461
  ```
462
462
 
@@ -465,8 +465,8 @@ const { fields, rows, execution } = await segmentstream.ontology.aggregate({
465
465
  `sales_2025.revenue`. This differs from the per-entity call and records, which drop it. Fields
466
466
  and rows are keyed by these names.
467
467
  - When an entity reaches a property's entity in more than one way, write a route from it:
468
- `trendtoysSales.returnedProduct.brand`. Its column is still keyed by the property's own name,
469
- `trendtoysProduct.brand`.
468
+ `acmeSales.returnedProduct.brand`. Its column is still keyed by the property's own name,
469
+ `acmeProduct.brand`.
470
470
  - It posts to the ontology service's `/api/ontology/aggregate` as the signed-in user, who needs
471
471
  read permission on every entity the request reaches. Previews and local development answer
472
472
  `aggregate_unavailable`.
@@ -486,13 +486,13 @@ Since 0.13.0, both aggregates take more conditions, buckets and a timezone:
486
486
 
487
487
  ```ts
488
488
  const { rows, execution } = await segmentstream.ontology.aggregate({
489
- measures: ["trendtoysSales.wonOrders"],
490
- dimensions: ["trendtoysSales.orderDate"],
489
+ measures: ["acmeSales.wonOrders"],
490
+ dimensions: ["acmeSales.orderDate"],
491
491
  granularity: "week",
492
- granularityOf: "trendtoysSales.orderDate",
492
+ granularityOf: "acmeSales.orderDate",
493
493
  filters: [
494
- { dimension: "trendtoysSales.orderDate", operator: "inPeriod", value: "last 4 weeks" },
495
- { dimension: "trendtoysProduct.brand", operator: "notIn", values: ["Unknown", "Test"] },
494
+ { dimension: "acmeSales.orderDate", operator: "inPeriod", value: "last 4 weeks" },
495
+ { dimension: "acmeProduct.brand", operator: "notIn", values: ["Unknown", "Test"] },
496
496
  ],
497
497
  timezone: "Europe/Moscow",
498
498
  });
@@ -519,6 +519,40 @@ const { rows, execution } = await segmentstream.ontology.aggregate({
519
519
  `@segmentstream-cloud/ontology` 0.17.0 or later, whose service accepts them; earlier
520
520
  versions refuse them. See [Upgrading to 0.13](#upgrading-to-013).
521
521
 
522
+ Since 0.14.0, the model-level call also takes the measures of the model, which the ontology
523
+ declares in `defineOntology({ measures })` over measures of several entities, by their bare
524
+ names beside `entity.measure` names:
525
+
526
+ ```ts
527
+ const { fields, rows } = await segmentstream.ontology.aggregate({
528
+ measures: ["planAttainment", "acmeSales.ordered"],
529
+ dimensions: ["acmeSales.orderDate"],
530
+ granularity: "month",
531
+ granularityOf: "acmeSales.orderDate",
532
+ filters: [
533
+ { dimension: "acmeSales.orderDate", operator: "inPeriod", value: "this year to date" },
534
+ ],
535
+ orderBy: [{ measure: "planAttainment", direction: "desc" }],
536
+ });
537
+ // fields[1]: { name: "planAttainment", label: "Plan attainment", unit: "ratio", additive: false,
538
+ // filters: [...], inputs: ["acmeSales.ordered", "acmePlans.plannedUnits"] }
539
+ ```
540
+
541
+ - A measure of the model is requested by the camelCase name the ontology declares, typed from
542
+ `typeof ontology`. The platform names it in snake_case, `plan_attainment`; its field and row
543
+ key are the camelCase form, `planAttainment`, as for entity measures.
544
+ - Its field carries `inputs`, the entity measures it reads, as `entity.measure` in camelCase per
545
+ segment, like other names. They are computed whether or not you request them; request one to
546
+ get its column too.
547
+ - Its `filters` lists the filters that applied to every input. A filter on a property the
548
+ measure aligns applies to each entity through its own property; a filter that reaches some of
549
+ its entities and not others is refused with `invalid_aggregate_query`.
550
+ - A group one of its entities has no rows for, or a zero denominator, gives `null`.
551
+ - The caller needs read permission on every entity it reads.
552
+ - These requests need the workspace's ontology deployed with `@segmentstream-cloud/ontology`
553
+ 0.23.0 or later, which declares and serves measures of the model; earlier versions refuse
554
+ them. Requests that name none work as before, and their fields carry no `inputs`.
555
+
522
556
  ## How requests are handled
523
557
 
524
558
  - Page navigations without a session are redirected to `/sign-in?returnTo=<path>` and
@@ -629,3 +663,10 @@ Replace segmentstream.semantic with typed warehouse-backed entity aggregates. Se
629
663
  - The new operators, `inPeriod`, granularities and `timezone` need the workspace's ontology
630
664
  deployed with `@segmentstream-cloud/ontology` 0.17.0 or later. Requests that use none of
631
665
  them work as before.
666
+
667
+ ## Upgrading to 0.14
668
+
669
+ - `segmentstream.ontology.aggregate()` accepts the measures of the model by their bare names, typed
670
+ from an ontology that declares them with `defineOntology({ measures })`. Ontologies without them
671
+ type the same names as before.
672
+ - `AggregateField` gains `inputs`, which only fields of measures of the model carry.
package/dist/data.d.ts CHANGED
@@ -115,7 +115,8 @@ export type ModelAggregateFilter<Dimension extends string = string> = {
115
115
  readonly values?: undefined;
116
116
  };
117
117
  /**
118
- * A model-level aggregate: measures of one or more entities, as `entity.measure`, grouped by
118
+ * A model-level aggregate: measures of one or more entities, as `entity.measure`, and measures of
119
+ * the model, declared in `defineOntology({ measures })`, by their bare names, grouped by
119
120
  * `entity.property` dimensions. A dimension may instead be a route from a measure's entity,
120
121
  * `entity.relationship.property`, which picks the way that entity reaches the property. Date bounds
121
122
  * are `between` or `inPeriod` filters.
@@ -156,6 +157,12 @@ export interface AggregateField {
156
157
  * rows snapshot. The request groups by it or filters it to one day.
157
158
  */
158
159
  readonly balanceOver?: string;
160
+ /**
161
+ * On a measure of the model: the entity measures it reads, as `entity.measure`, in declaration
162
+ * order. They are computed whether or not the request names them, and come back as columns only
163
+ * when it does. Its `filters` are the filters that applied to every one of them.
164
+ */
165
+ readonly inputs?: readonly string[];
159
166
  }
160
167
  export interface AggregateResult<Row = Record<string, string | boolean | null>> {
161
168
  readonly deploymentId: string | null;
@@ -252,7 +252,16 @@ type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
252
252
  */
253
253
  type CamelCase<S extends string> = S extends `${infer Head}_${infer Tail}` ? Tail extends `${Digit}${string}` ? `${Head}_${CamelCase<Tail>}` : `${Head}${Capitalize<CamelCase<Tail>>}` : S;
254
254
  /** `entity.measure` for every measure of every entity, warehouse-backed or stored. */
255
- type ModelMeasuresOf<E> = E extends unknown ? `${CamelCase<NameOf<E>>}.${MeasuresOf<E>}` : never;
255
+ type EntityMeasuresOf<E> = E extends unknown ? `${CamelCase<NameOf<E>>}.${MeasuresOf<E>}` : never;
256
+ /**
257
+ * The measures of the model, `defineOntology({ measures })`, by their bare declared names. An
258
+ * ontology SDK without them, or a definition typed as any record of measures, names none.
259
+ */
260
+ type ModelMeasuresOf<O> = O extends {
261
+ readonly config: {
262
+ readonly measures?: infer M;
263
+ };
264
+ } ? DeclaredNames<NonNullable<M>> : never;
256
265
  /** A route picks how an entity with measures reaches a property, so only such entities start one. */
257
266
  type RoutesOf<E, O> = [MeasuresOf<E>] extends [never] ? never : PathsOf<E, O>;
258
267
  /**
@@ -275,7 +284,7 @@ type TypedOntologyClient<O> = {
275
284
  readonly [N in NameOf<EntitiesOf<O>>]: ClientOf<Extract<EntitiesOf<O>, {
276
285
  readonly name: N;
277
286
  }>, O>;
278
- } & ModelAggregateClient<ModelMeasuresOf<EntitiesOf<O>>, ModelDimensionsOf<EntitiesOf<O>, O>>;
287
+ } & ModelAggregateClient<EntityMeasuresOf<EntitiesOf<O>> | ModelMeasuresOf<O>, ModelDimensionsOf<EntitiesOf<O>, O>>;
279
288
  /** A client for any entity, when the app passes no ontology type. */
280
289
  export type UntypedOntologyClient = ModelAggregateClient & {
281
290
  readonly [entity: string]: EntityClient<JsonRecord, {
package/dist/ontology.js CHANGED
@@ -247,8 +247,8 @@ function aggregateToWire(request) {
247
247
  };
248
248
  }
249
249
  /**
250
- * Field names, the filters each measure applied, a balance's dimension and row keys come back
251
- * camelCase per segment; `execution` is passed on as sent.
250
+ * Field names, the filters each measure applied, a balance's dimension, a model measure's inputs
251
+ * and row keys come back camelCase per segment; `execution` is passed on as sent.
252
252
  */
253
253
  function aggregateResultFromWire(result, decodeName) {
254
254
  return {
@@ -258,6 +258,7 @@ function aggregateResultFromWire(result, decodeName) {
258
258
  name: decodeName(field.name),
259
259
  ...(field.filters === undefined ? {} : { filters: field.filters.map(decodeName) }),
260
260
  ...(field.balanceOver === undefined ? {} : { balanceOver: decodeName(field.balanceOver) }),
261
+ ...(field.inputs === undefined ? {} : { inputs: field.inputs.map(decodeName) }),
261
262
  })),
262
263
  rows: result.rows.map((row) => Object.fromEntries(Object.entries(row).map(([name, value]) => [decodeName(name), value]))),
263
264
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segmentstream-cloud/app",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "description": "Sign-in, users, permissions, the ontology client and warehouse data access for SegmentStream workspace apps built with Next.js.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "SegmentStream",