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