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/README.md +217 -2
- package/dist/index.cjs +458 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -2
- package/dist/index.d.ts +81 -2
- package/dist/index.js +458 -137
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
};
|
|
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
|
|
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
|
-
|
|
247
|
+
const shape = {
|
|
333
248
|
containsAny: z.array(value).optional(),
|
|
334
249
|
containsAll: z.array(value).optional(),
|
|
335
250
|
exists: z.boolean().optional()
|
|
336
|
-
}
|
|
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
|
-
|
|
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
|
-
}
|
|
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.
|
|
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 && !
|
|
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;
|