industrial-model 0.3.0 → 0.4.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 +165 -0
- package/dist/index.cjs +400 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +73 -1
- package/dist/index.d.ts +73 -1
- package/dist/index.js +400 -130
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -26,6 +26,14 @@ var CogniteSdkAdapter = class {
|
|
|
26
26
|
nextCursor: response.nextCursor
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
async aggregateInstances(request) {
|
|
30
|
+
const response = await this.client.instances.aggregate(
|
|
31
|
+
request
|
|
32
|
+
);
|
|
33
|
+
return {
|
|
34
|
+
items: response.items
|
|
35
|
+
};
|
|
36
|
+
}
|
|
29
37
|
};
|
|
30
38
|
|
|
31
39
|
// src/constants.ts
|
|
@@ -34,6 +42,8 @@ var EDGE_MARKER = "<EdgeMarker>";
|
|
|
34
42
|
var MAX_LIMIT = 1e4;
|
|
35
43
|
var DEFAULT_LIMIT = 1e3;
|
|
36
44
|
var MAX_DEPENDENCY_DEPTH = 3;
|
|
45
|
+
var AGGREGATE_LIMIT = 1e3;
|
|
46
|
+
var MAX_GROUP_BY = 5;
|
|
37
47
|
|
|
38
48
|
// src/mappers/utils.ts
|
|
39
49
|
var NODE_PROPERTIES = /* @__PURE__ */ new Set([
|
|
@@ -75,135 +85,32 @@ function buildSelect(source, properties) {
|
|
|
75
85
|
if (properties.length === 0) return {};
|
|
76
86
|
return { sources: [{ source, properties }] };
|
|
77
87
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"exists",
|
|
88
|
-
"prefix",
|
|
89
|
-
"containsAny",
|
|
90
|
-
"containsAll"
|
|
88
|
+
var GROUPABLE_PROPERTY_TYPES = /* @__PURE__ */ new Set([
|
|
89
|
+
"text",
|
|
90
|
+
"direct",
|
|
91
|
+
"int32",
|
|
92
|
+
"int64",
|
|
93
|
+
"float32",
|
|
94
|
+
"float64",
|
|
95
|
+
"boolean",
|
|
96
|
+
"enum"
|
|
91
97
|
]);
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
var NUMERIC_PROPERTY_TYPES = /* @__PURE__ */ new Set(["int32", "int64", "float32", "float64"]);
|
|
99
|
+
function isGroupableProperty(property) {
|
|
100
|
+
if (!isViewPropertyDefinition(property)) return false;
|
|
101
|
+
if (property.type.list === true) return false;
|
|
102
|
+
const type = property.type.type;
|
|
103
|
+
return type != null && GROUPABLE_PROPERTY_TYPES.has(type);
|
|
94
104
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
};
|
|
105
|
+
function isNumericProperty(property) {
|
|
106
|
+
const type = property.type.type;
|
|
107
|
+
return type != null && NUMERIC_PROPERTY_TYPES.has(type);
|
|
108
|
+
}
|
|
109
|
+
function getSelectedGroupByKeys(groupBy) {
|
|
110
|
+
return Object.entries(groupBy).filter((entry) => entry[1] === true).map(([key]) => key);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/validation.ts
|
|
207
114
|
var nodeIdSchema = z.object({
|
|
208
115
|
space: z.string().min(1),
|
|
209
116
|
externalId: z.string().min(1)
|
|
@@ -283,7 +190,7 @@ var leafOps = /* @__PURE__ */ new Set([
|
|
|
283
190
|
function isRecord(value) {
|
|
284
191
|
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
285
192
|
}
|
|
286
|
-
function
|
|
193
|
+
function isLeafFilter(value) {
|
|
287
194
|
return Object.keys(value).some((key) => leafOps.has(key));
|
|
288
195
|
}
|
|
289
196
|
function issuePath(path) {
|
|
@@ -403,7 +310,7 @@ var QueryValidator = class {
|
|
|
403
310
|
errors.push(...await this.validateSelect(options.select, rootView, ["select"]));
|
|
404
311
|
}
|
|
405
312
|
if (options.filters !== void 0) {
|
|
406
|
-
errors.push(...await this.
|
|
313
|
+
errors.push(...await this.validateWhereInput(options.filters, rootView, ["filters"]));
|
|
407
314
|
}
|
|
408
315
|
if (options.sort !== void 0) {
|
|
409
316
|
errors.push(...this.validateSort(options.sort, rootView, ["sort"]));
|
|
@@ -463,6 +370,9 @@ ${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
|
463
370
|
}
|
|
464
371
|
return errors;
|
|
465
372
|
}
|
|
373
|
+
async validateWhereInput(filters, view, path) {
|
|
374
|
+
return this.validateFilters(filters, view, path);
|
|
375
|
+
}
|
|
466
376
|
async validateFilters(filters, view, path) {
|
|
467
377
|
const shape = {
|
|
468
378
|
AND: z.union([recordSchema, z.array(recordSchema)]).optional(),
|
|
@@ -506,7 +416,7 @@ ${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
|
506
416
|
if (!property) continue;
|
|
507
417
|
if (isViewPropertyDefinition(property)) {
|
|
508
418
|
const target2 = getDirectRelationSource(property);
|
|
509
|
-
if (target2 != null && !
|
|
419
|
+
if (target2 != null && !isLeafFilter(value)) {
|
|
510
420
|
const targetView = await this.viewMapper.getView(target2.externalId);
|
|
511
421
|
errors.push(...await this.validateFilters(value, targetView, [...path, name]));
|
|
512
422
|
} else {
|
|
@@ -544,6 +454,351 @@ ${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
|
544
454
|
}
|
|
545
455
|
};
|
|
546
456
|
|
|
457
|
+
// src/mappers/aggregate-validator.ts
|
|
458
|
+
var NODE_COUNT_PROPERTIES = /* @__PURE__ */ new Set(["externalId", "space"]);
|
|
459
|
+
function issuePath2(path) {
|
|
460
|
+
return path.length === 0 ? "aggregate" : path.map(String).join(".");
|
|
461
|
+
}
|
|
462
|
+
function formatZodIssues2(error, path) {
|
|
463
|
+
return error.issues.map((issue) => `${issuePath2([...path, ...issue.path])}: ${issue.message}`);
|
|
464
|
+
}
|
|
465
|
+
function isEmptyObject(value) {
|
|
466
|
+
return value != null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
467
|
+
}
|
|
468
|
+
var AggregateValidator = class {
|
|
469
|
+
constructor(viewMapper) {
|
|
470
|
+
this.queryValidator = new QueryValidator(viewMapper);
|
|
471
|
+
}
|
|
472
|
+
async validate(options, rootView) {
|
|
473
|
+
const errors = [];
|
|
474
|
+
errors.push(...this.validateOptionsShape(options, rootView));
|
|
475
|
+
const selectedGroupBy = options.groupBy ? getSelectedGroupByKeys(options.groupBy) : [];
|
|
476
|
+
if (selectedGroupBy.length === 0 && options.aggregate === void 0) {
|
|
477
|
+
errors.push("aggregate: either groupBy or aggregate must be provided");
|
|
478
|
+
}
|
|
479
|
+
if (options.filters !== void 0) {
|
|
480
|
+
errors.push(
|
|
481
|
+
...await this.queryValidator.validateWhereInput(options.filters, rootView, ["filters"])
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
if (options.groupBy !== void 0) {
|
|
485
|
+
errors.push(...this.validateGroupBy(options.groupBy, rootView, ["groupBy"]));
|
|
486
|
+
}
|
|
487
|
+
if (options.aggregate !== void 0) {
|
|
488
|
+
errors.push(
|
|
489
|
+
...this.validateAggregate(
|
|
490
|
+
options.aggregate,
|
|
491
|
+
rootView,
|
|
492
|
+
["aggregate"]
|
|
493
|
+
)
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
if (errors.length > 0) {
|
|
497
|
+
throw new Error(
|
|
498
|
+
`Invalid aggregate options:
|
|
499
|
+
${errors.map((error) => `- ${error}`).join("\n")}`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
validateOptionsShape(options, rootView) {
|
|
504
|
+
const schema = z.object({
|
|
505
|
+
viewExternalId: z.literal(rootView.externalId),
|
|
506
|
+
filters: z.unknown().optional(),
|
|
507
|
+
groupBy: z.unknown().optional(),
|
|
508
|
+
aggregate: z.unknown().optional()
|
|
509
|
+
}).strict();
|
|
510
|
+
const result = schema.safeParse(options);
|
|
511
|
+
return result.success ? [] : formatZodIssues2(result.error, []);
|
|
512
|
+
}
|
|
513
|
+
validateGroupBy(groupBy, view, path) {
|
|
514
|
+
const shape = {};
|
|
515
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
516
|
+
if (isGroupableProperty(property)) {
|
|
517
|
+
shape[name] = z.literal(true).optional();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
const result = z.object(shape).strict().safeParse(groupBy);
|
|
521
|
+
if (!result.success) {
|
|
522
|
+
return formatZodIssues2(result.error, path);
|
|
523
|
+
}
|
|
524
|
+
const selected = getSelectedGroupByKeys(groupBy);
|
|
525
|
+
const errors = [];
|
|
526
|
+
if (selected.length === 0) {
|
|
527
|
+
errors.push(`${issuePath2(path)}: at least one property must be set to true`);
|
|
528
|
+
}
|
|
529
|
+
if (selected.length > MAX_GROUP_BY) {
|
|
530
|
+
errors.push(`${issuePath2(path)}: at most ${MAX_GROUP_BY} properties can be grouped`);
|
|
531
|
+
}
|
|
532
|
+
for (const name of selected) {
|
|
533
|
+
const property = view.properties[name];
|
|
534
|
+
if (!property || !isGroupableProperty(property)) {
|
|
535
|
+
errors.push(`${issuePath2([...path, name])}: property "${name}" cannot be used in groupBy`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return errors;
|
|
539
|
+
}
|
|
540
|
+
validateAggregate(aggregate, view, path) {
|
|
541
|
+
if ("count" in aggregate) {
|
|
542
|
+
const property2 = aggregate.count;
|
|
543
|
+
if (isEmptyObject(property2)) {
|
|
544
|
+
return [];
|
|
545
|
+
}
|
|
546
|
+
if (typeof property2 === "string") {
|
|
547
|
+
if (NODE_COUNT_PROPERTIES.has(property2)) {
|
|
548
|
+
return [];
|
|
549
|
+
}
|
|
550
|
+
const viewProperty = view.properties[property2];
|
|
551
|
+
if (!viewProperty || !isGroupableProperty(viewProperty)) {
|
|
552
|
+
return [`${issuePath2([...path, "count"])}: property "${property2}" cannot be counted`];
|
|
553
|
+
}
|
|
554
|
+
return [];
|
|
555
|
+
}
|
|
556
|
+
return [`${issuePath2([...path, "count"])}: invalid count property`];
|
|
557
|
+
}
|
|
558
|
+
let propertyName;
|
|
559
|
+
let numericOp = null;
|
|
560
|
+
if ("avg" in aggregate) {
|
|
561
|
+
numericOp = "avg";
|
|
562
|
+
propertyName = aggregate.avg;
|
|
563
|
+
} else if ("min" in aggregate) {
|
|
564
|
+
numericOp = "min";
|
|
565
|
+
propertyName = aggregate.min;
|
|
566
|
+
} else if ("max" in aggregate) {
|
|
567
|
+
numericOp = "max";
|
|
568
|
+
propertyName = aggregate.max;
|
|
569
|
+
} else if ("sum" in aggregate) {
|
|
570
|
+
numericOp = "sum";
|
|
571
|
+
propertyName = aggregate.sum;
|
|
572
|
+
}
|
|
573
|
+
if (numericOp == null) {
|
|
574
|
+
return [`${issuePath2(path)}: unknown aggregate operation`];
|
|
575
|
+
}
|
|
576
|
+
if (typeof propertyName !== "string") {
|
|
577
|
+
return [`${issuePath2(path)}: aggregate property must be a string`];
|
|
578
|
+
}
|
|
579
|
+
const property = view.properties[propertyName];
|
|
580
|
+
if (!property || !isViewPropertyDefinition(property) || !isNumericProperty(property)) {
|
|
581
|
+
return [
|
|
582
|
+
`${issuePath2([...path, numericOp])}: property "${propertyName}" must be a numeric view property`
|
|
583
|
+
];
|
|
584
|
+
}
|
|
585
|
+
if (getDirectRelationSource(property) != null) {
|
|
586
|
+
return [
|
|
587
|
+
`${issuePath2([...path, numericOp])}: property "${propertyName}" is a relation and cannot be aggregated`
|
|
588
|
+
];
|
|
589
|
+
}
|
|
590
|
+
return [];
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// src/mappers/filter-mapper.ts
|
|
595
|
+
var LEAF_OPS = /* @__PURE__ */ new Set([
|
|
596
|
+
"eq",
|
|
597
|
+
"in",
|
|
598
|
+
"gt",
|
|
599
|
+
"gte",
|
|
600
|
+
"lt",
|
|
601
|
+
"lte",
|
|
602
|
+
"exists",
|
|
603
|
+
"prefix",
|
|
604
|
+
"containsAny",
|
|
605
|
+
"containsAll"
|
|
606
|
+
]);
|
|
607
|
+
function isLeafFilter2(value) {
|
|
608
|
+
return Object.keys(value).some((k) => LEAF_OPS.has(k));
|
|
609
|
+
}
|
|
610
|
+
var FilterMapper = class {
|
|
611
|
+
constructor(viewMapper) {
|
|
612
|
+
this.viewMapper = viewMapper;
|
|
613
|
+
}
|
|
614
|
+
async map(input, rootView) {
|
|
615
|
+
const result = [];
|
|
616
|
+
for (const [key, value] of Object.entries(input)) {
|
|
617
|
+
if (value == null) continue;
|
|
618
|
+
if (key === "AND") {
|
|
619
|
+
const clauses = Array.isArray(value) ? value : [value];
|
|
620
|
+
const inner = await Promise.all(clauses.map((c) => this.whereInputToSingle(c, rootView)));
|
|
621
|
+
result.push({ and: inner });
|
|
622
|
+
} else if (key === "OR") {
|
|
623
|
+
const clauses = value;
|
|
624
|
+
const branches = await Promise.all(
|
|
625
|
+
clauses.map((c) => this.whereInputToSingle(c, rootView))
|
|
626
|
+
);
|
|
627
|
+
result.push({ or: branches });
|
|
628
|
+
} else if (key === "NOT") {
|
|
629
|
+
const clauses = Array.isArray(value) ? value : [value];
|
|
630
|
+
const [firstClause, ...restClauses] = clauses;
|
|
631
|
+
const combined = restClauses.length === 0 && firstClause !== void 0 ? firstClause : { AND: clauses };
|
|
632
|
+
result.push({ not: await this.whereInputToSingle(combined, rootView) });
|
|
633
|
+
} else {
|
|
634
|
+
const filterValue = value;
|
|
635
|
+
const property = getPropertyRef(key, rootView);
|
|
636
|
+
if (isLeafFilter2(filterValue)) {
|
|
637
|
+
result.push(...this.leafToFilterDefs(property, filterValue));
|
|
638
|
+
} else {
|
|
639
|
+
const targetView = await this.getNestedTargetView(key, rootView);
|
|
640
|
+
const innerFilter = await this.whereInputToSingle(filterValue, targetView);
|
|
641
|
+
result.push({ nested: { scope: property, filter: innerFilter } });
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return result;
|
|
646
|
+
}
|
|
647
|
+
async whereInputToSingle(input, rootView) {
|
|
648
|
+
const filters = await this.map(input, rootView);
|
|
649
|
+
const [firstFilter, ...restFilters] = filters;
|
|
650
|
+
if (restFilters.length === 0 && firstFilter !== void 0) {
|
|
651
|
+
return firstFilter;
|
|
652
|
+
}
|
|
653
|
+
return { and: filters };
|
|
654
|
+
}
|
|
655
|
+
leafToFilterDefs(property, filter) {
|
|
656
|
+
const result = [];
|
|
657
|
+
if ("eq" in filter && filter.eq !== void 0) {
|
|
658
|
+
result.push({
|
|
659
|
+
equals: { property, value: this.coerceValue(filter.eq) }
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
if ("in" in filter && filter.in !== void 0) {
|
|
663
|
+
result.push({
|
|
664
|
+
in: { property, values: this.coerceValue(filter.in) }
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
if ("gt" in filter && filter.gt !== void 0) {
|
|
668
|
+
result.push({ range: { property, gt: this.coerceValue(filter.gt) } });
|
|
669
|
+
}
|
|
670
|
+
if ("gte" in filter && filter.gte !== void 0) {
|
|
671
|
+
result.push({ range: { property, gte: this.coerceValue(filter.gte) } });
|
|
672
|
+
}
|
|
673
|
+
if ("lt" in filter && filter.lt !== void 0) {
|
|
674
|
+
result.push({ range: { property, lt: this.coerceValue(filter.lt) } });
|
|
675
|
+
}
|
|
676
|
+
if ("lte" in filter && filter.lte !== void 0) {
|
|
677
|
+
result.push({ range: { property, lte: this.coerceValue(filter.lte) } });
|
|
678
|
+
}
|
|
679
|
+
if ("exists" in filter) {
|
|
680
|
+
if (filter.exists === true) {
|
|
681
|
+
result.push({ exists: { property } });
|
|
682
|
+
} else if (filter.exists === false) {
|
|
683
|
+
result.push({ not: { exists: { property } } });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if ("prefix" in filter && filter.prefix !== void 0) {
|
|
687
|
+
result.push({ prefix: { property, value: this.coerceValue(filter.prefix) } });
|
|
688
|
+
}
|
|
689
|
+
if ("containsAll" in filter && filter.containsAll !== void 0) {
|
|
690
|
+
result.push({
|
|
691
|
+
containsAll: {
|
|
692
|
+
property,
|
|
693
|
+
values: this.coerceValue(filter.containsAll)
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
if ("containsAny" in filter && filter.containsAny !== void 0) {
|
|
698
|
+
result.push({
|
|
699
|
+
containsAny: {
|
|
700
|
+
property,
|
|
701
|
+
values: this.coerceValue(filter.containsAny)
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
return result;
|
|
706
|
+
}
|
|
707
|
+
async getNestedTargetView(property, rootView) {
|
|
708
|
+
const viewProp = rootView.properties[property];
|
|
709
|
+
if (!viewProp || !isViewPropertyDefinition(viewProp)) {
|
|
710
|
+
throw new Error(`Property "${property}" is not a mapped property`);
|
|
711
|
+
}
|
|
712
|
+
const source = getDirectRelationSource(viewProp);
|
|
713
|
+
if (!source) throw new Error(`Property "${property}" has no relation source`);
|
|
714
|
+
return this.viewMapper.getView(source.externalId);
|
|
715
|
+
}
|
|
716
|
+
coerceValue(value) {
|
|
717
|
+
if (value instanceof Date) return value.toISOString();
|
|
718
|
+
if (Array.isArray(value)) return value.map((v) => this.coerceValue(v));
|
|
719
|
+
return value;
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
// src/mappers/aggregate-mapper.ts
|
|
724
|
+
var AggregateMapper = class {
|
|
725
|
+
constructor(viewMapper) {
|
|
726
|
+
this.viewMapper = viewMapper;
|
|
727
|
+
this.filterMapper = new FilterMapper(viewMapper);
|
|
728
|
+
this.validator = new AggregateValidator(viewMapper);
|
|
729
|
+
}
|
|
730
|
+
async map(options) {
|
|
731
|
+
const { viewExternalId, filters, groupBy, aggregate } = options;
|
|
732
|
+
const rootView = await this.viewMapper.getView(viewExternalId);
|
|
733
|
+
await this.validator.validate(options, rootView);
|
|
734
|
+
const filterParts = filters ? await this.filterMapper.map(filters, rootView) : [];
|
|
735
|
+
const filter = filterParts.length === 0 ? void 0 : filterParts.length === 1 ? filterParts[0] : { and: filterParts };
|
|
736
|
+
return {
|
|
737
|
+
view: toViewReference(rootView),
|
|
738
|
+
instanceType: "node",
|
|
739
|
+
limit: AGGREGATE_LIMIT,
|
|
740
|
+
...filter !== void 0 ? { filter } : {},
|
|
741
|
+
...groupBy ? { groupBy: getSelectedGroupByKeys(groupBy) } : {},
|
|
742
|
+
...aggregate ? { aggregates: [mapAggregateDefinition(aggregate)] } : {}
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
function mapAggregateDefinition(aggregate) {
|
|
747
|
+
if ("count" in aggregate) {
|
|
748
|
+
const property = aggregate.count;
|
|
749
|
+
if (property != null && typeof property === "object" && !Array.isArray(property) && Object.keys(property).length === 0) {
|
|
750
|
+
return { count: {} };
|
|
751
|
+
}
|
|
752
|
+
if (typeof property === "string") {
|
|
753
|
+
return { count: { property } };
|
|
754
|
+
}
|
|
755
|
+
return { count: {} };
|
|
756
|
+
}
|
|
757
|
+
if ("avg" in aggregate) {
|
|
758
|
+
return { avg: { property: aggregate.avg } };
|
|
759
|
+
}
|
|
760
|
+
if ("min" in aggregate) {
|
|
761
|
+
return { min: { property: aggregate.min } };
|
|
762
|
+
}
|
|
763
|
+
if ("max" in aggregate) {
|
|
764
|
+
return { max: { property: aggregate.max } };
|
|
765
|
+
}
|
|
766
|
+
if ("sum" in aggregate) {
|
|
767
|
+
return { sum: { property: aggregate.sum } };
|
|
768
|
+
}
|
|
769
|
+
throw new Error("Invalid aggregate definition");
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// src/mappers/aggregate-result-mapper.ts
|
|
773
|
+
function isNodeId(value) {
|
|
774
|
+
return value != null && typeof value === "object" && "space" in value && "externalId" in value && typeof value.space === "string" && typeof value.externalId === "string";
|
|
775
|
+
}
|
|
776
|
+
var AggregateResultMapper = class {
|
|
777
|
+
map(response, options) {
|
|
778
|
+
const groupByKeys = options.groupBy ? getSelectedGroupByKeys(options.groupBy) : [];
|
|
779
|
+
return response.items.map((item) => {
|
|
780
|
+
let group;
|
|
781
|
+
if (item.group != null && groupByKeys.length > 0) {
|
|
782
|
+
group = {};
|
|
783
|
+
for (const key of groupByKeys) {
|
|
784
|
+
const value = item.group[key];
|
|
785
|
+
if (value === void 0) continue;
|
|
786
|
+
group[key] = isNodeId(value) ? { space: value.space, externalId: value.externalId } : value;
|
|
787
|
+
}
|
|
788
|
+
if (Object.keys(group).length === 0) {
|
|
789
|
+
group = void 0;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const aggregateValue = item.aggregates[0];
|
|
793
|
+
const aggregate = aggregateValue?.value !== void 0 ? aggregateValue.property != null ? { property: aggregateValue.property, value: aggregateValue.value } : { value: aggregateValue.value } : void 0;
|
|
794
|
+
return {
|
|
795
|
+
...group !== void 0 ? { group } : {},
|
|
796
|
+
...aggregate !== void 0 ? { aggregate } : {}
|
|
797
|
+
};
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
|
|
547
802
|
// src/mappers/sort-mapper.ts
|
|
548
803
|
var SortMapper = class {
|
|
549
804
|
map(sort, rootView) {
|
|
@@ -1113,6 +1368,8 @@ var IndustrialModelClient = class {
|
|
|
1113
1368
|
this.cognite = cognite;
|
|
1114
1369
|
const viewMapper = new ViewMapper(cognite, dataModelId);
|
|
1115
1370
|
this.queryMapper = new QueryMapper(viewMapper);
|
|
1371
|
+
this.aggregateMapper = new AggregateMapper(viewMapper);
|
|
1372
|
+
this.aggregateResultMapper = new AggregateResultMapper();
|
|
1116
1373
|
this.resultMapper = new QueryResultMapper(viewMapper);
|
|
1117
1374
|
this.resultValidator = new QueryResultValidator(viewMapper);
|
|
1118
1375
|
this.validateResults = options.validateResults ?? false;
|
|
@@ -1121,6 +1378,19 @@ var IndustrialModelClient = class {
|
|
|
1121
1378
|
const execute = (options) => this.queryInternal(options);
|
|
1122
1379
|
return execute;
|
|
1123
1380
|
}
|
|
1381
|
+
aggregate() {
|
|
1382
|
+
const execute = (options) => this.aggregateInternal(options);
|
|
1383
|
+
return execute;
|
|
1384
|
+
}
|
|
1385
|
+
async aggregateInternal(options) {
|
|
1386
|
+
const cogniteRequest = await this.aggregateMapper.map(options);
|
|
1387
|
+
const response = await this.cognite.aggregateInstances(cogniteRequest);
|
|
1388
|
+
const items = this.aggregateResultMapper.map(
|
|
1389
|
+
response,
|
|
1390
|
+
options
|
|
1391
|
+
);
|
|
1392
|
+
return { items };
|
|
1393
|
+
}
|
|
1124
1394
|
async queryInternal(options) {
|
|
1125
1395
|
const { viewExternalId, limit = DEFAULT_LIMIT } = options;
|
|
1126
1396
|
const allPages = options.limit === -1;
|