industrial-model 0.3.0 → 0.5.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/dist/index.js CHANGED
@@ -26,6 +26,21 @@ var CogniteSdkAdapter = class {
26
26
  nextCursor: response.nextCursor
27
27
  };
28
28
  }
29
+ async searchInstances(request) {
30
+ const search = this.client.instances.search;
31
+ const response = await search(request);
32
+ return {
33
+ items: response.items
34
+ };
35
+ }
36
+ async aggregateInstances(request) {
37
+ const response = await this.client.instances.aggregate(
38
+ request
39
+ );
40
+ return {
41
+ items: response.items
42
+ };
43
+ }
29
44
  };
30
45
 
31
46
  // src/constants.ts
@@ -34,6 +49,8 @@ var EDGE_MARKER = "<EdgeMarker>";
34
49
  var MAX_LIMIT = 1e4;
35
50
  var DEFAULT_LIMIT = 1e3;
36
51
  var MAX_DEPENDENCY_DEPTH = 3;
52
+ var AGGREGATE_LIMIT = 1e3;
53
+ var MAX_GROUP_BY = 5;
37
54
 
38
55
  // src/mappers/utils.ts
39
56
  var NODE_PROPERTIES = /* @__PURE__ */ new Set([
@@ -75,135 +92,32 @@ function buildSelect(source, properties) {
75
92
  if (properties.length === 0) return {};
76
93
  return { sources: [{ source, properties }] };
77
94
  }
78
-
79
- // src/mappers/filter-mapper.ts
80
- var LEAF_OPS = /* @__PURE__ */ new Set([
81
- "eq",
82
- "in",
83
- "gt",
84
- "gte",
85
- "lt",
86
- "lte",
87
- "exists",
88
- "prefix",
89
- "containsAny",
90
- "containsAll"
95
+ var GROUPABLE_PROPERTY_TYPES = /* @__PURE__ */ new Set([
96
+ "text",
97
+ "direct",
98
+ "int32",
99
+ "int64",
100
+ "float32",
101
+ "float64",
102
+ "boolean",
103
+ "enum"
91
104
  ]);
92
- function isLeafFilter(value) {
93
- return Object.keys(value).some((k) => LEAF_OPS.has(k));
105
+ var NUMERIC_PROPERTY_TYPES = /* @__PURE__ */ new Set(["int32", "int64", "float32", "float64"]);
106
+ function isGroupableProperty(property) {
107
+ if (!isViewPropertyDefinition(property)) return false;
108
+ if (property.type.list === true) return false;
109
+ const type = property.type.type;
110
+ return type != null && GROUPABLE_PROPERTY_TYPES.has(type);
94
111
  }
95
- var FilterMapper = class {
96
- constructor(viewMapper) {
97
- this.viewMapper = viewMapper;
98
- }
99
- async map(input, rootView) {
100
- const result = [];
101
- for (const [key, value] of Object.entries(input)) {
102
- if (value == null) continue;
103
- if (key === "AND") {
104
- const clauses = Array.isArray(value) ? value : [value];
105
- const inner = await Promise.all(clauses.map((c) => this.whereInputToSingle(c, rootView)));
106
- result.push({ and: inner });
107
- } else if (key === "OR") {
108
- const clauses = value;
109
- const branches = await Promise.all(
110
- clauses.map((c) => this.whereInputToSingle(c, rootView))
111
- );
112
- result.push({ or: branches });
113
- } else if (key === "NOT") {
114
- const clauses = Array.isArray(value) ? value : [value];
115
- const [firstClause, ...restClauses] = clauses;
116
- const combined = restClauses.length === 0 && firstClause !== void 0 ? firstClause : { AND: clauses };
117
- result.push({ not: await this.whereInputToSingle(combined, rootView) });
118
- } else {
119
- const filterValue = value;
120
- const property = getPropertyRef(key, rootView);
121
- if (isLeafFilter(filterValue)) {
122
- result.push(...this.leafToFilterDefs(property, filterValue));
123
- } else {
124
- const targetView = await this.getNestedTargetView(key, rootView);
125
- const innerFilter = await this.whereInputToSingle(filterValue, targetView);
126
- result.push({ nested: { scope: property, filter: innerFilter } });
127
- }
128
- }
129
- }
130
- return result;
131
- }
132
- async whereInputToSingle(input, rootView) {
133
- const filters = await this.map(input, rootView);
134
- const [firstFilter, ...restFilters] = filters;
135
- if (restFilters.length === 0 && firstFilter !== void 0) {
136
- return firstFilter;
137
- }
138
- return { and: filters };
139
- }
140
- leafToFilterDefs(property, filter) {
141
- const result = [];
142
- if ("eq" in filter && filter.eq !== void 0) {
143
- result.push({
144
- equals: { property, value: this.coerceValue(filter.eq) }
145
- });
146
- }
147
- if ("in" in filter && filter.in !== void 0) {
148
- result.push({
149
- in: { property, values: this.coerceValue(filter.in) }
150
- });
151
- }
152
- if ("gt" in filter && filter.gt !== void 0) {
153
- result.push({ range: { property, gt: this.coerceValue(filter.gt) } });
154
- }
155
- if ("gte" in filter && filter.gte !== void 0) {
156
- result.push({ range: { property, gte: this.coerceValue(filter.gte) } });
157
- }
158
- if ("lt" in filter && filter.lt !== void 0) {
159
- result.push({ range: { property, lt: this.coerceValue(filter.lt) } });
160
- }
161
- if ("lte" in filter && filter.lte !== void 0) {
162
- result.push({ range: { property, lte: this.coerceValue(filter.lte) } });
163
- }
164
- if ("exists" in filter) {
165
- if (filter.exists === true) {
166
- result.push({ exists: { property } });
167
- } else if (filter.exists === false) {
168
- result.push({ not: { exists: { property } } });
169
- }
170
- }
171
- if ("prefix" in filter && filter.prefix !== void 0) {
172
- result.push({ prefix: { property, value: this.coerceValue(filter.prefix) } });
173
- }
174
- if ("containsAll" in filter && filter.containsAll !== void 0) {
175
- result.push({
176
- containsAll: {
177
- property,
178
- values: this.coerceValue(filter.containsAll)
179
- }
180
- });
181
- }
182
- if ("containsAny" in filter && filter.containsAny !== void 0) {
183
- result.push({
184
- containsAny: {
185
- property,
186
- values: this.coerceValue(filter.containsAny)
187
- }
188
- });
189
- }
190
- return result;
191
- }
192
- async getNestedTargetView(property, rootView) {
193
- const viewProp = rootView.properties[property];
194
- if (!viewProp || !isViewPropertyDefinition(viewProp)) {
195
- throw new Error(`Property "${property}" is not a mapped property`);
196
- }
197
- const source = getDirectRelationSource(viewProp);
198
- if (!source) throw new Error(`Property "${property}" has no relation source`);
199
- return this.viewMapper.getView(source.externalId);
200
- }
201
- coerceValue(value) {
202
- if (value instanceof Date) return value.toISOString();
203
- if (Array.isArray(value)) return value.map((v) => this.coerceValue(v));
204
- return value;
205
- }
206
- };
112
+ function isNumericProperty(property) {
113
+ const type = property.type.type;
114
+ return type != null && NUMERIC_PROPERTY_TYPES.has(type);
115
+ }
116
+ function getSelectedGroupByKeys(groupBy) {
117
+ return Object.entries(groupBy).filter((entry) => entry[1] === true).map(([key]) => key);
118
+ }
119
+
120
+ // src/validation.ts
207
121
  var nodeIdSchema = z.object({
208
122
  space: z.string().min(1),
209
123
  externalId: z.string().min(1)
@@ -277,13 +191,14 @@ var leafOps = /* @__PURE__ */ new Set([
277
191
  "lte",
278
192
  "exists",
279
193
  "prefix",
194
+ "search",
280
195
  "containsAny",
281
196
  "containsAll"
282
197
  ]);
283
198
  function isRecord(value) {
284
199
  return value != null && typeof value === "object" && !Array.isArray(value);
285
200
  }
286
- function isLeafFilter2(value) {
201
+ function isLeafFilter(value) {
287
202
  return Object.keys(value).some((key) => leafOps.has(key));
288
203
  }
289
204
  function issuePath(path) {
@@ -329,19 +244,33 @@ function leafFilterSchema(property) {
329
244
  const value = baseValueSchema(property);
330
245
  const isList = typeof property !== "string" && property.type.list === true;
331
246
  if (isList) {
332
- return z.object({
247
+ const shape = {
333
248
  containsAny: z.array(value).optional(),
334
249
  containsAll: z.array(value).optional(),
335
250
  exists: z.boolean().optional()
336
- }).strict();
251
+ };
252
+ if (property.type.type === "text") {
253
+ shape.search = z.object({
254
+ query: z.string(),
255
+ operator: z.enum(["OR", "AND"]).optional()
256
+ }).strict().optional();
257
+ }
258
+ return z.object(shape).strict();
337
259
  }
338
260
  if (property === "node-string" || typeof property !== "string" && property.type.type === "text") {
339
- return z.object({
261
+ const shape = {
340
262
  eq: z.string().optional(),
341
263
  in: z.array(z.string()).optional(),
342
264
  prefix: z.string().optional(),
343
265
  exists: z.boolean().optional()
344
- }).strict();
266
+ };
267
+ if (property !== "node-string") {
268
+ shape.search = z.object({
269
+ query: z.string(),
270
+ operator: z.enum(["OR", "AND"]).optional()
271
+ }).strict().optional();
272
+ }
273
+ return z.object(shape).strict();
345
274
  }
346
275
  if (typeof property !== "string" && property.type.type === "enum") {
347
276
  return z.object({
@@ -403,7 +332,7 @@ var QueryValidator = class {
403
332
  errors.push(...await this.validateSelect(options.select, rootView, ["select"]));
404
333
  }
405
334
  if (options.filters !== void 0) {
406
- errors.push(...await this.validateFilters(options.filters, rootView, ["filters"]));
335
+ errors.push(...await this.validateWhereInput(options.filters, rootView, ["filters"]));
407
336
  }
408
337
  if (options.sort !== void 0) {
409
338
  errors.push(...this.validateSort(options.sort, rootView, ["sort"]));
@@ -463,6 +392,9 @@ ${errors.map((error) => `- ${error}`).join("\n")}`);
463
392
  }
464
393
  return errors;
465
394
  }
395
+ async validateWhereInput(filters, view, path) {
396
+ return this.validateFilters(filters, view, path);
397
+ }
466
398
  async validateFilters(filters, view, path) {
467
399
  const shape = {
468
400
  AND: z.union([recordSchema, z.array(recordSchema)]).optional(),
@@ -506,7 +438,7 @@ ${errors.map((error) => `- ${error}`).join("\n")}`);
506
438
  if (!property) continue;
507
439
  if (isViewPropertyDefinition(property)) {
508
440
  const target2 = getDirectRelationSource(property);
509
- if (target2 != null && !isLeafFilter2(value)) {
441
+ if (target2 != null && !isLeafFilter(value)) {
510
442
  const targetView = await this.viewMapper.getView(target2.externalId);
511
443
  errors.push(...await this.validateFilters(value, targetView, [...path, name]));
512
444
  } else {
@@ -544,6 +476,380 @@ ${errors.map((error) => `- ${error}`).join("\n")}`);
544
476
  }
545
477
  };
546
478
 
479
+ // src/mappers/aggregate-validator.ts
480
+ var NODE_COUNT_PROPERTIES = /* @__PURE__ */ new Set(["externalId", "space"]);
481
+ function issuePath2(path) {
482
+ return path.length === 0 ? "aggregate" : path.map(String).join(".");
483
+ }
484
+ function formatZodIssues2(error, path) {
485
+ return error.issues.map((issue) => `${issuePath2([...path, ...issue.path])}: ${issue.message}`);
486
+ }
487
+ function isEmptyObject(value) {
488
+ return value != null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
489
+ }
490
+ var AggregateValidator = class {
491
+ constructor(viewMapper) {
492
+ this.queryValidator = new QueryValidator(viewMapper);
493
+ }
494
+ async validate(options, rootView) {
495
+ const errors = [];
496
+ errors.push(...this.validateOptionsShape(options, rootView));
497
+ const selectedGroupBy = options.groupBy ? getSelectedGroupByKeys(options.groupBy) : [];
498
+ if (selectedGroupBy.length === 0 && options.aggregate === void 0) {
499
+ errors.push("aggregate: either groupBy or aggregate must be provided");
500
+ }
501
+ if (options.filters !== void 0) {
502
+ errors.push(
503
+ ...await this.queryValidator.validateWhereInput(options.filters, rootView, ["filters"])
504
+ );
505
+ }
506
+ if (options.groupBy !== void 0) {
507
+ errors.push(...this.validateGroupBy(options.groupBy, rootView, ["groupBy"]));
508
+ }
509
+ if (options.aggregate !== void 0) {
510
+ errors.push(
511
+ ...this.validateAggregate(
512
+ options.aggregate,
513
+ rootView,
514
+ ["aggregate"]
515
+ )
516
+ );
517
+ }
518
+ if (errors.length > 0) {
519
+ throw new Error(
520
+ `Invalid aggregate options:
521
+ ${errors.map((error) => `- ${error}`).join("\n")}`
522
+ );
523
+ }
524
+ }
525
+ validateOptionsShape(options, rootView) {
526
+ const schema = z.object({
527
+ viewExternalId: z.literal(rootView.externalId),
528
+ filters: z.unknown().optional(),
529
+ groupBy: z.unknown().optional(),
530
+ aggregate: z.unknown().optional()
531
+ }).strict();
532
+ const result = schema.safeParse(options);
533
+ return result.success ? [] : formatZodIssues2(result.error, []);
534
+ }
535
+ validateGroupBy(groupBy, view, path) {
536
+ const shape = {};
537
+ for (const [name, property] of Object.entries(view.properties)) {
538
+ if (isGroupableProperty(property)) {
539
+ shape[name] = z.literal(true).optional();
540
+ }
541
+ }
542
+ const result = z.object(shape).strict().safeParse(groupBy);
543
+ if (!result.success) {
544
+ return formatZodIssues2(result.error, path);
545
+ }
546
+ const selected = getSelectedGroupByKeys(groupBy);
547
+ const errors = [];
548
+ if (selected.length === 0) {
549
+ errors.push(`${issuePath2(path)}: at least one property must be set to true`);
550
+ }
551
+ if (selected.length > MAX_GROUP_BY) {
552
+ errors.push(`${issuePath2(path)}: at most ${MAX_GROUP_BY} properties can be grouped`);
553
+ }
554
+ for (const name of selected) {
555
+ const property = view.properties[name];
556
+ if (!property || !isGroupableProperty(property)) {
557
+ errors.push(`${issuePath2([...path, name])}: property "${name}" cannot be used in groupBy`);
558
+ }
559
+ }
560
+ return errors;
561
+ }
562
+ validateAggregate(aggregate, view, path) {
563
+ if ("count" in aggregate) {
564
+ const property2 = aggregate.count;
565
+ if (isEmptyObject(property2)) {
566
+ return [];
567
+ }
568
+ if (typeof property2 === "string") {
569
+ if (NODE_COUNT_PROPERTIES.has(property2)) {
570
+ return [];
571
+ }
572
+ const viewProperty = view.properties[property2];
573
+ if (!viewProperty || !isGroupableProperty(viewProperty)) {
574
+ return [`${issuePath2([...path, "count"])}: property "${property2}" cannot be counted`];
575
+ }
576
+ return [];
577
+ }
578
+ return [`${issuePath2([...path, "count"])}: invalid count property`];
579
+ }
580
+ let propertyName;
581
+ let numericOp = null;
582
+ if ("avg" in aggregate) {
583
+ numericOp = "avg";
584
+ propertyName = aggregate.avg;
585
+ } else if ("min" in aggregate) {
586
+ numericOp = "min";
587
+ propertyName = aggregate.min;
588
+ } else if ("max" in aggregate) {
589
+ numericOp = "max";
590
+ propertyName = aggregate.max;
591
+ } else if ("sum" in aggregate) {
592
+ numericOp = "sum";
593
+ propertyName = aggregate.sum;
594
+ }
595
+ if (numericOp == null) {
596
+ return [`${issuePath2(path)}: unknown aggregate operation`];
597
+ }
598
+ if (typeof propertyName !== "string") {
599
+ return [`${issuePath2(path)}: aggregate property must be a string`];
600
+ }
601
+ const property = view.properties[propertyName];
602
+ if (!property || !isViewPropertyDefinition(property) || !isNumericProperty(property)) {
603
+ return [
604
+ `${issuePath2([...path, numericOp])}: property "${propertyName}" must be a numeric view property`
605
+ ];
606
+ }
607
+ if (getDirectRelationSource(property) != null) {
608
+ return [
609
+ `${issuePath2([...path, numericOp])}: property "${propertyName}" is a relation and cannot be aggregated`
610
+ ];
611
+ }
612
+ return [];
613
+ }
614
+ };
615
+
616
+ // src/mappers/filter-mapper.ts
617
+ var LEAF_OPS = /* @__PURE__ */ new Set([
618
+ "eq",
619
+ "in",
620
+ "gt",
621
+ "gte",
622
+ "lt",
623
+ "lte",
624
+ "exists",
625
+ "prefix",
626
+ "containsAny",
627
+ "containsAll"
628
+ ]);
629
+ function isLeafFilter2(value) {
630
+ return Object.keys(value).some((k) => LEAF_OPS.has(k));
631
+ }
632
+ var FilterMapper = class {
633
+ constructor(viewMapper, cognite) {
634
+ this.viewMapper = viewMapper;
635
+ this.cognite = cognite;
636
+ }
637
+ async map(input, rootView) {
638
+ const result = [];
639
+ const searchQueries = {};
640
+ for (const [key, value] of Object.entries(input)) {
641
+ if (value == null) continue;
642
+ if (key === "AND") {
643
+ const clauses = Array.isArray(value) ? value : [value];
644
+ const inner = await Promise.all(clauses.map((c) => this.whereInputToSingle(c, rootView)));
645
+ result.push({ and: inner });
646
+ } else if (key === "OR") {
647
+ const clauses = value;
648
+ const branches = await Promise.all(
649
+ clauses.map((c) => this.whereInputToSingle(c, rootView))
650
+ );
651
+ result.push({ or: branches });
652
+ } else if (key === "NOT") {
653
+ const clauses = Array.isArray(value) ? value : [value];
654
+ const [firstClause, ...restClauses] = clauses;
655
+ const combined = restClauses.length === 0 && firstClause !== void 0 ? firstClause : { AND: clauses };
656
+ result.push({ not: await this.whereInputToSingle(combined, rootView) });
657
+ } else {
658
+ const filterValue = value;
659
+ const property = getPropertyRef(key, rootView);
660
+ const hasLeafFilter = isLeafFilter2(filterValue);
661
+ if (hasLeafFilter) {
662
+ result.push(...this.leafToFilterDefs(property, filterValue));
663
+ }
664
+ if ("search" in filterValue && filterValue.search != null) {
665
+ searchQueries[key] = filterValue.search;
666
+ } else if (!hasLeafFilter) {
667
+ const targetView = await this.getNestedTargetView(key, rootView);
668
+ const innerFilter = await this.whereInputToSingle(filterValue, targetView);
669
+ result.push({ nested: { scope: property, filter: innerFilter } });
670
+ }
671
+ }
672
+ }
673
+ if (Object.keys(searchQueries).length > 0) {
674
+ const searchFilters = await Promise.all(
675
+ Object.entries(searchQueries).map(
676
+ ([key, filterValue]) => this.searchToFilterDef(key, filterValue, rootView)
677
+ )
678
+ );
679
+ result.push(...searchFilters);
680
+ }
681
+ return result;
682
+ }
683
+ async searchToFilterDef(propertyName, search, rootView) {
684
+ const response = await this.cognite.searchInstances({
685
+ view: toViewReference(rootView),
686
+ query: search.query,
687
+ instanceType: "node",
688
+ properties: [propertyName],
689
+ operator: search.operator ?? "OR",
690
+ limit: 1e3
691
+ });
692
+ const instanceRefs = response.items.map((item) => ({
693
+ space: item.space,
694
+ externalId: item.externalId
695
+ }));
696
+ return { instanceReferences: instanceRefs };
697
+ }
698
+ async whereInputToSingle(input, rootView) {
699
+ const filters = await this.map(input, rootView);
700
+ const [firstFilter, ...restFilters] = filters;
701
+ if (restFilters.length === 0 && firstFilter !== void 0) {
702
+ return firstFilter;
703
+ }
704
+ return { and: filters };
705
+ }
706
+ leafToFilterDefs(property, filter) {
707
+ const result = [];
708
+ if ("eq" in filter && filter.eq !== void 0) {
709
+ result.push({
710
+ equals: { property, value: this.coerceValue(filter.eq) }
711
+ });
712
+ }
713
+ if ("in" in filter && filter.in !== void 0) {
714
+ result.push({
715
+ in: { property, values: this.coerceValue(filter.in) }
716
+ });
717
+ }
718
+ if ("gt" in filter && filter.gt !== void 0) {
719
+ result.push({ range: { property, gt: this.coerceValue(filter.gt) } });
720
+ }
721
+ if ("gte" in filter && filter.gte !== void 0) {
722
+ result.push({ range: { property, gte: this.coerceValue(filter.gte) } });
723
+ }
724
+ if ("lt" in filter && filter.lt !== void 0) {
725
+ result.push({ range: { property, lt: this.coerceValue(filter.lt) } });
726
+ }
727
+ if ("lte" in filter && filter.lte !== void 0) {
728
+ result.push({ range: { property, lte: this.coerceValue(filter.lte) } });
729
+ }
730
+ if ("exists" in filter) {
731
+ if (filter.exists === true) {
732
+ result.push({ exists: { property } });
733
+ } else if (filter.exists === false) {
734
+ result.push({ not: { exists: { property } } });
735
+ }
736
+ }
737
+ if ("prefix" in filter && filter.prefix !== void 0) {
738
+ result.push({ prefix: { property, value: this.coerceValue(filter.prefix) } });
739
+ }
740
+ if ("containsAll" in filter && filter.containsAll !== void 0) {
741
+ result.push({
742
+ containsAll: {
743
+ property,
744
+ values: this.coerceValue(filter.containsAll)
745
+ }
746
+ });
747
+ }
748
+ if ("containsAny" in filter && filter.containsAny !== void 0) {
749
+ result.push({
750
+ containsAny: {
751
+ property,
752
+ values: this.coerceValue(filter.containsAny)
753
+ }
754
+ });
755
+ }
756
+ return result;
757
+ }
758
+ async getNestedTargetView(property, rootView) {
759
+ const viewProp = rootView.properties[property];
760
+ if (!viewProp || !isViewPropertyDefinition(viewProp)) {
761
+ throw new Error(`Property "${property}" is not a mapped property`);
762
+ }
763
+ const source = getDirectRelationSource(viewProp);
764
+ if (!source) throw new Error(`Property "${property}" has no relation source`);
765
+ return this.viewMapper.getView(source.externalId);
766
+ }
767
+ coerceValue(value) {
768
+ if (value instanceof Date) return value.toISOString();
769
+ if (Array.isArray(value)) return value.map((v) => this.coerceValue(v));
770
+ return value;
771
+ }
772
+ };
773
+
774
+ // src/mappers/aggregate-mapper.ts
775
+ var AggregateMapper = class {
776
+ constructor(viewMapper, cognite) {
777
+ this.viewMapper = viewMapper;
778
+ this.filterMapper = new FilterMapper(viewMapper, cognite);
779
+ this.validator = new AggregateValidator(viewMapper);
780
+ }
781
+ async map(options) {
782
+ const { viewExternalId, filters, groupBy, aggregate } = options;
783
+ const rootView = await this.viewMapper.getView(viewExternalId);
784
+ await this.validator.validate(options, rootView);
785
+ const filterParts = filters ? await this.filterMapper.map(filters, rootView) : [];
786
+ const filter = filterParts.length === 0 ? void 0 : filterParts.length === 1 ? filterParts[0] : { and: filterParts };
787
+ return {
788
+ view: toViewReference(rootView),
789
+ instanceType: "node",
790
+ limit: AGGREGATE_LIMIT,
791
+ ...filter !== void 0 ? { filter } : {},
792
+ ...groupBy ? { groupBy: getSelectedGroupByKeys(groupBy) } : {},
793
+ ...aggregate ? { aggregates: [mapAggregateDefinition(aggregate)] } : {}
794
+ };
795
+ }
796
+ };
797
+ function mapAggregateDefinition(aggregate) {
798
+ if ("count" in aggregate) {
799
+ const property = aggregate.count;
800
+ if (property != null && typeof property === "object" && !Array.isArray(property) && Object.keys(property).length === 0) {
801
+ return { count: {} };
802
+ }
803
+ if (typeof property === "string") {
804
+ return { count: { property } };
805
+ }
806
+ return { count: {} };
807
+ }
808
+ if ("avg" in aggregate) {
809
+ return { avg: { property: aggregate.avg } };
810
+ }
811
+ if ("min" in aggregate) {
812
+ return { min: { property: aggregate.min } };
813
+ }
814
+ if ("max" in aggregate) {
815
+ return { max: { property: aggregate.max } };
816
+ }
817
+ if ("sum" in aggregate) {
818
+ return { sum: { property: aggregate.sum } };
819
+ }
820
+ throw new Error("Invalid aggregate definition");
821
+ }
822
+
823
+ // src/mappers/aggregate-result-mapper.ts
824
+ function isNodeId(value) {
825
+ return value != null && typeof value === "object" && "space" in value && "externalId" in value && typeof value.space === "string" && typeof value.externalId === "string";
826
+ }
827
+ var AggregateResultMapper = class {
828
+ map(response, options) {
829
+ const groupByKeys = options.groupBy ? getSelectedGroupByKeys(options.groupBy) : [];
830
+ return response.items.map((item) => {
831
+ let group;
832
+ if (item.group != null && groupByKeys.length > 0) {
833
+ group = {};
834
+ for (const key of groupByKeys) {
835
+ const value = item.group[key];
836
+ if (value === void 0) continue;
837
+ group[key] = isNodeId(value) ? { space: value.space, externalId: value.externalId } : value;
838
+ }
839
+ if (Object.keys(group).length === 0) {
840
+ group = void 0;
841
+ }
842
+ }
843
+ const aggregateValue = item.aggregates[0];
844
+ const aggregate = aggregateValue?.value !== void 0 ? aggregateValue.property != null ? { property: aggregateValue.property, value: aggregateValue.value } : { value: aggregateValue.value } : void 0;
845
+ return {
846
+ ...group !== void 0 ? { group } : {},
847
+ ...aggregate !== void 0 ? { aggregate } : {}
848
+ };
849
+ });
850
+ }
851
+ };
852
+
547
853
  // src/mappers/sort-mapper.ts
548
854
  var SortMapper = class {
549
855
  map(sort, rootView) {
@@ -564,9 +870,9 @@ var SortMapper = class {
564
870
 
565
871
  // src/mappers/query-mapper.ts
566
872
  var QueryMapper = class {
567
- constructor(viewMapper) {
873
+ constructor(viewMapper, cognite) {
568
874
  this.viewMapper = viewMapper;
569
- this.filterMapper = new FilterMapper(viewMapper);
875
+ this.filterMapper = new FilterMapper(viewMapper, cognite);
570
876
  this.sortMapper = new SortMapper();
571
877
  this.validator = new QueryValidator(viewMapper);
572
878
  }
@@ -1112,7 +1418,9 @@ var IndustrialModelClient = class {
1112
1418
  const cognite = createCogniteAdapter(client);
1113
1419
  this.cognite = cognite;
1114
1420
  const viewMapper = new ViewMapper(cognite, dataModelId);
1115
- this.queryMapper = new QueryMapper(viewMapper);
1421
+ this.queryMapper = new QueryMapper(viewMapper, cognite);
1422
+ this.aggregateMapper = new AggregateMapper(viewMapper, cognite);
1423
+ this.aggregateResultMapper = new AggregateResultMapper();
1116
1424
  this.resultMapper = new QueryResultMapper(viewMapper);
1117
1425
  this.resultValidator = new QueryResultValidator(viewMapper);
1118
1426
  this.validateResults = options.validateResults ?? false;
@@ -1121,6 +1429,19 @@ var IndustrialModelClient = class {
1121
1429
  const execute = (options) => this.queryInternal(options);
1122
1430
  return execute;
1123
1431
  }
1432
+ aggregate() {
1433
+ const execute = (options) => this.aggregateInternal(options);
1434
+ return execute;
1435
+ }
1436
+ async aggregateInternal(options) {
1437
+ const cogniteRequest = await this.aggregateMapper.map(options);
1438
+ const response = await this.cognite.aggregateInstances(cogniteRequest);
1439
+ const items = this.aggregateResultMapper.map(
1440
+ response,
1441
+ options
1442
+ );
1443
+ return { items };
1444
+ }
1124
1445
  async queryInternal(options) {
1125
1446
  const { viewExternalId, limit = DEFAULT_LIMIT } = options;
1126
1447
  const allPages = options.limit === -1;