industrial-model 0.2.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 +178 -1
- package/dist/index.cjs +710 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +139 -5
- package/dist/index.d.ts +139 -5
- package/dist/index.js +709 -6
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
|
|
3
5
|
// src/cognite/adapter.ts
|
|
4
6
|
function createCogniteAdapter(client) {
|
|
5
7
|
return new CogniteSdkAdapter(client);
|
|
@@ -26,6 +28,14 @@ var CogniteSdkAdapter = class {
|
|
|
26
28
|
nextCursor: response.nextCursor
|
|
27
29
|
};
|
|
28
30
|
}
|
|
31
|
+
async aggregateInstances(request) {
|
|
32
|
+
const response = await this.client.instances.aggregate(
|
|
33
|
+
request
|
|
34
|
+
);
|
|
35
|
+
return {
|
|
36
|
+
items: response.items
|
|
37
|
+
};
|
|
38
|
+
}
|
|
29
39
|
};
|
|
30
40
|
|
|
31
41
|
// src/constants.ts
|
|
@@ -34,6 +44,8 @@ var EDGE_MARKER = "<EdgeMarker>";
|
|
|
34
44
|
var MAX_LIMIT = 1e4;
|
|
35
45
|
var DEFAULT_LIMIT = 1e3;
|
|
36
46
|
var MAX_DEPENDENCY_DEPTH = 3;
|
|
47
|
+
var AGGREGATE_LIMIT = 1e3;
|
|
48
|
+
var MAX_GROUP_BY = 5;
|
|
37
49
|
|
|
38
50
|
// src/mappers/utils.ts
|
|
39
51
|
var NODE_PROPERTIES = /* @__PURE__ */ new Set([
|
|
@@ -75,6 +87,511 @@ function buildSelect(source, properties) {
|
|
|
75
87
|
if (properties.length === 0) return {};
|
|
76
88
|
return { sources: [{ source, properties }] };
|
|
77
89
|
}
|
|
90
|
+
var GROUPABLE_PROPERTY_TYPES = /* @__PURE__ */ new Set([
|
|
91
|
+
"text",
|
|
92
|
+
"direct",
|
|
93
|
+
"int32",
|
|
94
|
+
"int64",
|
|
95
|
+
"float32",
|
|
96
|
+
"float64",
|
|
97
|
+
"boolean",
|
|
98
|
+
"enum"
|
|
99
|
+
]);
|
|
100
|
+
var NUMERIC_PROPERTY_TYPES = /* @__PURE__ */ new Set(["int32", "int64", "float32", "float64"]);
|
|
101
|
+
function isGroupableProperty(property) {
|
|
102
|
+
if (!isViewPropertyDefinition(property)) return false;
|
|
103
|
+
if (property.type.list === true) return false;
|
|
104
|
+
const type = property.type.type;
|
|
105
|
+
return type != null && GROUPABLE_PROPERTY_TYPES.has(type);
|
|
106
|
+
}
|
|
107
|
+
function isNumericProperty(property) {
|
|
108
|
+
const type = property.type.type;
|
|
109
|
+
return type != null && NUMERIC_PROPERTY_TYPES.has(type);
|
|
110
|
+
}
|
|
111
|
+
function getSelectedGroupByKeys(groupBy) {
|
|
112
|
+
return Object.entries(groupBy).filter((entry) => entry[1] === true).map(([key]) => key);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/validation.ts
|
|
116
|
+
var nodeIdSchema = zod.z.object({
|
|
117
|
+
space: zod.z.string().min(1),
|
|
118
|
+
externalId: zod.z.string().min(1)
|
|
119
|
+
});
|
|
120
|
+
function dateSchema(dateMode) {
|
|
121
|
+
if (dateMode === "coerce") {
|
|
122
|
+
return zod.z.preprocess(
|
|
123
|
+
(value) => typeof value === "string" || typeof value === "number" ? new Date(value) : value,
|
|
124
|
+
zod.z.date()
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return zod.z.union([zod.z.string(), zod.z.date()]);
|
|
128
|
+
}
|
|
129
|
+
function propertyValueSchema(property, options = {}) {
|
|
130
|
+
const type = property.type;
|
|
131
|
+
let schema;
|
|
132
|
+
switch (type.type) {
|
|
133
|
+
case "text":
|
|
134
|
+
case "enum":
|
|
135
|
+
schema = zod.z.string();
|
|
136
|
+
break;
|
|
137
|
+
case "int32":
|
|
138
|
+
case "int64":
|
|
139
|
+
schema = zod.z.number().int();
|
|
140
|
+
break;
|
|
141
|
+
case "float32":
|
|
142
|
+
case "float64":
|
|
143
|
+
schema = zod.z.number();
|
|
144
|
+
break;
|
|
145
|
+
case "boolean":
|
|
146
|
+
schema = zod.z.boolean();
|
|
147
|
+
break;
|
|
148
|
+
case "date":
|
|
149
|
+
case "timestamp":
|
|
150
|
+
schema = dateSchema(options.dateMode);
|
|
151
|
+
break;
|
|
152
|
+
case "direct":
|
|
153
|
+
schema = nodeIdSchema;
|
|
154
|
+
break;
|
|
155
|
+
case "json":
|
|
156
|
+
schema = zod.z.unknown();
|
|
157
|
+
break;
|
|
158
|
+
default:
|
|
159
|
+
schema = zod.z.unknown();
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
return type.list === true ? zod.z.array(schema) : schema;
|
|
163
|
+
}
|
|
164
|
+
function buildViewSchema(view, options = {}) {
|
|
165
|
+
const shape = {};
|
|
166
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
167
|
+
if (isViewPropertyDefinition(property)) {
|
|
168
|
+
shape[name] = propertyValueSchema(property, options).optional();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return zod.z.object(shape).strict();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/mappers/query-validator.ts
|
|
175
|
+
var NODE_STRING_PROPERTIES = ["externalId", "space"];
|
|
176
|
+
var NODE_NUMBER_PROPERTIES = ["createdTime", "deletedTime", "lastUpdatedTime"];
|
|
177
|
+
var NODE_PROPERTIES2 = /* @__PURE__ */ new Set([...NODE_STRING_PROPERTIES, ...NODE_NUMBER_PROPERTIES]);
|
|
178
|
+
var SORT_DIRECTION_SCHEMA = zod.z.enum(["ascending", "descending"]);
|
|
179
|
+
var recordSchema = zod.z.record(zod.z.string(), zod.z.unknown());
|
|
180
|
+
var leafOps = /* @__PURE__ */ new Set([
|
|
181
|
+
"eq",
|
|
182
|
+
"in",
|
|
183
|
+
"gt",
|
|
184
|
+
"gte",
|
|
185
|
+
"lt",
|
|
186
|
+
"lte",
|
|
187
|
+
"exists",
|
|
188
|
+
"prefix",
|
|
189
|
+
"containsAny",
|
|
190
|
+
"containsAll"
|
|
191
|
+
]);
|
|
192
|
+
function isRecord(value) {
|
|
193
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
194
|
+
}
|
|
195
|
+
function isLeafFilter(value) {
|
|
196
|
+
return Object.keys(value).some((key) => leafOps.has(key));
|
|
197
|
+
}
|
|
198
|
+
function issuePath(path) {
|
|
199
|
+
return path.length === 0 ? "query" : path.map(String).join(".");
|
|
200
|
+
}
|
|
201
|
+
function formatZodIssues(error, path) {
|
|
202
|
+
return error.issues.map((issue) => `${issuePath([...path, ...issue.path])}: ${issue.message}`);
|
|
203
|
+
}
|
|
204
|
+
function getRelationTarget(property) {
|
|
205
|
+
if (isViewPropertyDefinition(property)) {
|
|
206
|
+
return getDirectRelationSource(property)?.externalId ?? null;
|
|
207
|
+
}
|
|
208
|
+
if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
|
|
209
|
+
return property.source.externalId;
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
function baseValueSchema(property) {
|
|
214
|
+
if (property === "node-string") return zod.z.string();
|
|
215
|
+
if (property === "node-number") return zod.z.number();
|
|
216
|
+
switch (property.type.type) {
|
|
217
|
+
case "text":
|
|
218
|
+
case "enum":
|
|
219
|
+
return zod.z.string();
|
|
220
|
+
case "int32":
|
|
221
|
+
case "int64":
|
|
222
|
+
return zod.z.number().int();
|
|
223
|
+
case "float32":
|
|
224
|
+
case "float64":
|
|
225
|
+
return zod.z.number();
|
|
226
|
+
case "boolean":
|
|
227
|
+
return zod.z.boolean();
|
|
228
|
+
case "date":
|
|
229
|
+
case "timestamp":
|
|
230
|
+
return zod.z.union([zod.z.string(), zod.z.date()]);
|
|
231
|
+
case "direct":
|
|
232
|
+
return nodeIdSchema;
|
|
233
|
+
default:
|
|
234
|
+
return zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean()]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function leafFilterSchema(property) {
|
|
238
|
+
const value = baseValueSchema(property);
|
|
239
|
+
const isList = typeof property !== "string" && property.type.list === true;
|
|
240
|
+
if (isList) {
|
|
241
|
+
return zod.z.object({
|
|
242
|
+
containsAny: zod.z.array(value).optional(),
|
|
243
|
+
containsAll: zod.z.array(value).optional(),
|
|
244
|
+
exists: zod.z.boolean().optional()
|
|
245
|
+
}).strict();
|
|
246
|
+
}
|
|
247
|
+
if (property === "node-string" || typeof property !== "string" && property.type.type === "text") {
|
|
248
|
+
return zod.z.object({
|
|
249
|
+
eq: zod.z.string().optional(),
|
|
250
|
+
in: zod.z.array(zod.z.string()).optional(),
|
|
251
|
+
prefix: zod.z.string().optional(),
|
|
252
|
+
exists: zod.z.boolean().optional()
|
|
253
|
+
}).strict();
|
|
254
|
+
}
|
|
255
|
+
if (typeof property !== "string" && property.type.type === "enum") {
|
|
256
|
+
return zod.z.object({
|
|
257
|
+
eq: zod.z.string().optional(),
|
|
258
|
+
in: zod.z.array(zod.z.string()).optional(),
|
|
259
|
+
prefix: zod.z.string().optional(),
|
|
260
|
+
exists: zod.z.boolean().optional()
|
|
261
|
+
}).strict();
|
|
262
|
+
}
|
|
263
|
+
if (property === "node-number" || typeof property !== "string" && ["int32", "int64", "float32", "float64"].includes(property.type.type ?? "")) {
|
|
264
|
+
return zod.z.object({
|
|
265
|
+
eq: value.optional(),
|
|
266
|
+
in: zod.z.array(value).optional(),
|
|
267
|
+
gt: value.optional(),
|
|
268
|
+
gte: value.optional(),
|
|
269
|
+
lt: value.optional(),
|
|
270
|
+
lte: value.optional(),
|
|
271
|
+
exists: zod.z.boolean().optional()
|
|
272
|
+
}).strict();
|
|
273
|
+
}
|
|
274
|
+
if (typeof property !== "string" && ["date", "timestamp"].includes(property.type.type ?? "")) {
|
|
275
|
+
return zod.z.object({
|
|
276
|
+
eq: value.optional(),
|
|
277
|
+
in: zod.z.array(value).optional(),
|
|
278
|
+
gt: value.optional(),
|
|
279
|
+
gte: value.optional(),
|
|
280
|
+
lt: value.optional(),
|
|
281
|
+
lte: value.optional(),
|
|
282
|
+
exists: zod.z.boolean().optional()
|
|
283
|
+
}).strict();
|
|
284
|
+
}
|
|
285
|
+
if (typeof property !== "string" && property.type.type === "boolean") {
|
|
286
|
+
return zod.z.object({
|
|
287
|
+
eq: zod.z.boolean().optional(),
|
|
288
|
+
exists: zod.z.boolean().optional()
|
|
289
|
+
}).strict();
|
|
290
|
+
}
|
|
291
|
+
if (typeof property !== "string" && property.type.type === "direct") {
|
|
292
|
+
return zod.z.object({
|
|
293
|
+
eq: nodeIdSchema.optional(),
|
|
294
|
+
in: zod.z.array(nodeIdSchema).optional(),
|
|
295
|
+
exists: zod.z.boolean().optional()
|
|
296
|
+
}).strict();
|
|
297
|
+
}
|
|
298
|
+
return zod.z.object({
|
|
299
|
+
eq: value.optional(),
|
|
300
|
+
in: zod.z.array(value).optional(),
|
|
301
|
+
exists: zod.z.boolean().optional()
|
|
302
|
+
}).strict();
|
|
303
|
+
}
|
|
304
|
+
var QueryValidator = class {
|
|
305
|
+
constructor(viewMapper) {
|
|
306
|
+
this.viewMapper = viewMapper;
|
|
307
|
+
}
|
|
308
|
+
async validate(options, rootView) {
|
|
309
|
+
const errors = [];
|
|
310
|
+
errors.push(...this.validateOptionsShape(options, rootView));
|
|
311
|
+
if (options.select !== void 0) {
|
|
312
|
+
errors.push(...await this.validateSelect(options.select, rootView, ["select"]));
|
|
313
|
+
}
|
|
314
|
+
if (options.filters !== void 0) {
|
|
315
|
+
errors.push(...await this.validateWhereInput(options.filters, rootView, ["filters"]));
|
|
316
|
+
}
|
|
317
|
+
if (options.sort !== void 0) {
|
|
318
|
+
errors.push(...this.validateSort(options.sort, rootView, ["sort"]));
|
|
319
|
+
}
|
|
320
|
+
if (errors.length > 0) {
|
|
321
|
+
throw new Error(`Invalid query options:
|
|
322
|
+
${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
validateOptionsShape(options, rootView) {
|
|
326
|
+
const schema = zod.z.object({
|
|
327
|
+
viewExternalId: zod.z.literal(rootView.externalId),
|
|
328
|
+
select: zod.z.unknown().optional(),
|
|
329
|
+
filters: zod.z.unknown().optional(),
|
|
330
|
+
sort: zod.z.unknown().optional(),
|
|
331
|
+
limit: zod.z.union([zod.z.literal(-1), zod.z.number().int().positive().max(MAX_LIMIT)]).optional(),
|
|
332
|
+
cursor: zod.z.string().nullable().optional()
|
|
333
|
+
}).strict();
|
|
334
|
+
const result = schema.safeParse(options);
|
|
335
|
+
return result.success ? [] : formatZodIssues(result.error, []);
|
|
336
|
+
}
|
|
337
|
+
async validateSelect(select, view, path) {
|
|
338
|
+
const shape = {
|
|
339
|
+
_all: zod.z.literal(true).optional()
|
|
340
|
+
};
|
|
341
|
+
for (const property of NODE_PROPERTIES2) {
|
|
342
|
+
shape[property] = zod.z.boolean().optional();
|
|
343
|
+
}
|
|
344
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
345
|
+
const target = getRelationTarget(property);
|
|
346
|
+
if (target != null) {
|
|
347
|
+
const nestedSelect = recordSchema;
|
|
348
|
+
shape[name] = isViewPropertyDefinition(property) ? zod.z.union([zod.z.boolean(), nestedSelect]).optional() : nestedSelect.optional();
|
|
349
|
+
} else {
|
|
350
|
+
shape[name] = zod.z.boolean().optional();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const result = zod.z.object(shape).strict().safeParse(select);
|
|
354
|
+
if (!result.success) return formatZodIssues(result.error, path);
|
|
355
|
+
if (!isRecord(select)) return [];
|
|
356
|
+
const errors = [];
|
|
357
|
+
for (const [name, value] of Object.entries(select)) {
|
|
358
|
+
if (name === "_all" || value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const property = view.properties[name];
|
|
362
|
+
if (!property) continue;
|
|
363
|
+
const target = getRelationTarget(property);
|
|
364
|
+
if (target == null) {
|
|
365
|
+
errors.push(
|
|
366
|
+
`${issuePath([...path, name])}: property "${name}" does not support nested select`
|
|
367
|
+
);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const targetView = await this.viewMapper.getView(target);
|
|
371
|
+
errors.push(...await this.validateSelect(value, targetView, [...path, name]));
|
|
372
|
+
}
|
|
373
|
+
return errors;
|
|
374
|
+
}
|
|
375
|
+
async validateWhereInput(filters, view, path) {
|
|
376
|
+
return this.validateFilters(filters, view, path);
|
|
377
|
+
}
|
|
378
|
+
async validateFilters(filters, view, path) {
|
|
379
|
+
const shape = {
|
|
380
|
+
AND: zod.z.union([recordSchema, zod.z.array(recordSchema)]).optional(),
|
|
381
|
+
OR: zod.z.array(recordSchema).optional(),
|
|
382
|
+
NOT: zod.z.union([recordSchema, zod.z.array(recordSchema)]).optional()
|
|
383
|
+
};
|
|
384
|
+
for (const property of NODE_STRING_PROPERTIES) {
|
|
385
|
+
shape[property] = zod.z.unknown().optional();
|
|
386
|
+
}
|
|
387
|
+
for (const property of NODE_NUMBER_PROPERTIES) {
|
|
388
|
+
shape[property] = zod.z.unknown().optional();
|
|
389
|
+
}
|
|
390
|
+
for (const property of Object.keys(view.properties)) {
|
|
391
|
+
shape[property] = zod.z.unknown().optional();
|
|
392
|
+
}
|
|
393
|
+
const result = zod.z.object(shape).strict().safeParse(filters);
|
|
394
|
+
if (!result.success) return formatZodIssues(result.error, path);
|
|
395
|
+
if (!isRecord(filters)) return [];
|
|
396
|
+
const errors = [];
|
|
397
|
+
for (const [name, value] of Object.entries(filters)) {
|
|
398
|
+
if (value == null) continue;
|
|
399
|
+
if (name === "AND" || name === "OR" || name === "NOT") {
|
|
400
|
+
const clauses = Array.isArray(value) ? value : [value];
|
|
401
|
+
for (const [index, clause] of clauses.entries()) {
|
|
402
|
+
errors.push(...await this.validateFilters(clause, view, [...path, name, index]));
|
|
403
|
+
}
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (!isRecord(value)) {
|
|
407
|
+
errors.push(`${issuePath([...path, name])}: Expected object`);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const nodePropertyType = NODE_STRING_PROPERTIES.includes(
|
|
411
|
+
name
|
|
412
|
+
) ? "node-string" : NODE_NUMBER_PROPERTIES.includes(name) ? "node-number" : null;
|
|
413
|
+
if (nodePropertyType != null) {
|
|
414
|
+
errors.push(...this.validateLeafFilter(value, nodePropertyType, [...path, name]));
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const property = view.properties[name];
|
|
418
|
+
if (!property) continue;
|
|
419
|
+
if (isViewPropertyDefinition(property)) {
|
|
420
|
+
const target2 = getDirectRelationSource(property);
|
|
421
|
+
if (target2 != null && !isLeafFilter(value)) {
|
|
422
|
+
const targetView = await this.viewMapper.getView(target2.externalId);
|
|
423
|
+
errors.push(...await this.validateFilters(value, targetView, [...path, name]));
|
|
424
|
+
} else {
|
|
425
|
+
errors.push(...this.validateLeafFilter(value, property, [...path, name]));
|
|
426
|
+
}
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
const target = getRelationTarget(property);
|
|
430
|
+
if (target == null) {
|
|
431
|
+
errors.push(`${issuePath([...path, name])}: property "${name}" does not support filters`);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
errors.push(
|
|
435
|
+
`${issuePath([...path, name])}: filtering through "${name}" is not supported by the query mapper`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
return errors;
|
|
439
|
+
}
|
|
440
|
+
validateLeafFilter(value, property, path) {
|
|
441
|
+
const result = leafFilterSchema(property).safeParse(value);
|
|
442
|
+
return result.success ? [] : formatZodIssues(result.error, path);
|
|
443
|
+
}
|
|
444
|
+
validateSort(sort, view, path) {
|
|
445
|
+
const shape = {};
|
|
446
|
+
for (const property of NODE_PROPERTIES2) {
|
|
447
|
+
shape[property] = SORT_DIRECTION_SCHEMA.optional();
|
|
448
|
+
}
|
|
449
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
450
|
+
if (isViewPropertyDefinition(property) && property.type.list !== true) {
|
|
451
|
+
shape[name] = SORT_DIRECTION_SCHEMA.optional();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const result = zod.z.object(shape).strict().safeParse(sort);
|
|
455
|
+
return result.success ? [] : formatZodIssues(result.error, path);
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// src/mappers/aggregate-validator.ts
|
|
460
|
+
var NODE_COUNT_PROPERTIES = /* @__PURE__ */ new Set(["externalId", "space"]);
|
|
461
|
+
function issuePath2(path) {
|
|
462
|
+
return path.length === 0 ? "aggregate" : path.map(String).join(".");
|
|
463
|
+
}
|
|
464
|
+
function formatZodIssues2(error, path) {
|
|
465
|
+
return error.issues.map((issue) => `${issuePath2([...path, ...issue.path])}: ${issue.message}`);
|
|
466
|
+
}
|
|
467
|
+
function isEmptyObject(value) {
|
|
468
|
+
return value != null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
469
|
+
}
|
|
470
|
+
var AggregateValidator = class {
|
|
471
|
+
constructor(viewMapper) {
|
|
472
|
+
this.queryValidator = new QueryValidator(viewMapper);
|
|
473
|
+
}
|
|
474
|
+
async validate(options, rootView) {
|
|
475
|
+
const errors = [];
|
|
476
|
+
errors.push(...this.validateOptionsShape(options, rootView));
|
|
477
|
+
const selectedGroupBy = options.groupBy ? getSelectedGroupByKeys(options.groupBy) : [];
|
|
478
|
+
if (selectedGroupBy.length === 0 && options.aggregate === void 0) {
|
|
479
|
+
errors.push("aggregate: either groupBy or aggregate must be provided");
|
|
480
|
+
}
|
|
481
|
+
if (options.filters !== void 0) {
|
|
482
|
+
errors.push(
|
|
483
|
+
...await this.queryValidator.validateWhereInput(options.filters, rootView, ["filters"])
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
if (options.groupBy !== void 0) {
|
|
487
|
+
errors.push(...this.validateGroupBy(options.groupBy, rootView, ["groupBy"]));
|
|
488
|
+
}
|
|
489
|
+
if (options.aggregate !== void 0) {
|
|
490
|
+
errors.push(
|
|
491
|
+
...this.validateAggregate(
|
|
492
|
+
options.aggregate,
|
|
493
|
+
rootView,
|
|
494
|
+
["aggregate"]
|
|
495
|
+
)
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
if (errors.length > 0) {
|
|
499
|
+
throw new Error(
|
|
500
|
+
`Invalid aggregate options:
|
|
501
|
+
${errors.map((error) => `- ${error}`).join("\n")}`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
validateOptionsShape(options, rootView) {
|
|
506
|
+
const schema = zod.z.object({
|
|
507
|
+
viewExternalId: zod.z.literal(rootView.externalId),
|
|
508
|
+
filters: zod.z.unknown().optional(),
|
|
509
|
+
groupBy: zod.z.unknown().optional(),
|
|
510
|
+
aggregate: zod.z.unknown().optional()
|
|
511
|
+
}).strict();
|
|
512
|
+
const result = schema.safeParse(options);
|
|
513
|
+
return result.success ? [] : formatZodIssues2(result.error, []);
|
|
514
|
+
}
|
|
515
|
+
validateGroupBy(groupBy, view, path) {
|
|
516
|
+
const shape = {};
|
|
517
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
518
|
+
if (isGroupableProperty(property)) {
|
|
519
|
+
shape[name] = zod.z.literal(true).optional();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const result = zod.z.object(shape).strict().safeParse(groupBy);
|
|
523
|
+
if (!result.success) {
|
|
524
|
+
return formatZodIssues2(result.error, path);
|
|
525
|
+
}
|
|
526
|
+
const selected = getSelectedGroupByKeys(groupBy);
|
|
527
|
+
const errors = [];
|
|
528
|
+
if (selected.length === 0) {
|
|
529
|
+
errors.push(`${issuePath2(path)}: at least one property must be set to true`);
|
|
530
|
+
}
|
|
531
|
+
if (selected.length > MAX_GROUP_BY) {
|
|
532
|
+
errors.push(`${issuePath2(path)}: at most ${MAX_GROUP_BY} properties can be grouped`);
|
|
533
|
+
}
|
|
534
|
+
for (const name of selected) {
|
|
535
|
+
const property = view.properties[name];
|
|
536
|
+
if (!property || !isGroupableProperty(property)) {
|
|
537
|
+
errors.push(`${issuePath2([...path, name])}: property "${name}" cannot be used in groupBy`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return errors;
|
|
541
|
+
}
|
|
542
|
+
validateAggregate(aggregate, view, path) {
|
|
543
|
+
if ("count" in aggregate) {
|
|
544
|
+
const property2 = aggregate.count;
|
|
545
|
+
if (isEmptyObject(property2)) {
|
|
546
|
+
return [];
|
|
547
|
+
}
|
|
548
|
+
if (typeof property2 === "string") {
|
|
549
|
+
if (NODE_COUNT_PROPERTIES.has(property2)) {
|
|
550
|
+
return [];
|
|
551
|
+
}
|
|
552
|
+
const viewProperty = view.properties[property2];
|
|
553
|
+
if (!viewProperty || !isGroupableProperty(viewProperty)) {
|
|
554
|
+
return [`${issuePath2([...path, "count"])}: property "${property2}" cannot be counted`];
|
|
555
|
+
}
|
|
556
|
+
return [];
|
|
557
|
+
}
|
|
558
|
+
return [`${issuePath2([...path, "count"])}: invalid count property`];
|
|
559
|
+
}
|
|
560
|
+
let propertyName;
|
|
561
|
+
let numericOp = null;
|
|
562
|
+
if ("avg" in aggregate) {
|
|
563
|
+
numericOp = "avg";
|
|
564
|
+
propertyName = aggregate.avg;
|
|
565
|
+
} else if ("min" in aggregate) {
|
|
566
|
+
numericOp = "min";
|
|
567
|
+
propertyName = aggregate.min;
|
|
568
|
+
} else if ("max" in aggregate) {
|
|
569
|
+
numericOp = "max";
|
|
570
|
+
propertyName = aggregate.max;
|
|
571
|
+
} else if ("sum" in aggregate) {
|
|
572
|
+
numericOp = "sum";
|
|
573
|
+
propertyName = aggregate.sum;
|
|
574
|
+
}
|
|
575
|
+
if (numericOp == null) {
|
|
576
|
+
return [`${issuePath2(path)}: unknown aggregate operation`];
|
|
577
|
+
}
|
|
578
|
+
if (typeof propertyName !== "string") {
|
|
579
|
+
return [`${issuePath2(path)}: aggregate property must be a string`];
|
|
580
|
+
}
|
|
581
|
+
const property = view.properties[propertyName];
|
|
582
|
+
if (!property || !isViewPropertyDefinition(property) || !isNumericProperty(property)) {
|
|
583
|
+
return [
|
|
584
|
+
`${issuePath2([...path, numericOp])}: property "${propertyName}" must be a numeric view property`
|
|
585
|
+
];
|
|
586
|
+
}
|
|
587
|
+
if (getDirectRelationSource(property) != null) {
|
|
588
|
+
return [
|
|
589
|
+
`${issuePath2([...path, numericOp])}: property "${propertyName}" is a relation and cannot be aggregated`
|
|
590
|
+
];
|
|
591
|
+
}
|
|
592
|
+
return [];
|
|
593
|
+
}
|
|
594
|
+
};
|
|
78
595
|
|
|
79
596
|
// src/mappers/filter-mapper.ts
|
|
80
597
|
var LEAF_OPS = /* @__PURE__ */ new Set([
|
|
@@ -89,7 +606,7 @@ var LEAF_OPS = /* @__PURE__ */ new Set([
|
|
|
89
606
|
"containsAny",
|
|
90
607
|
"containsAll"
|
|
91
608
|
]);
|
|
92
|
-
function
|
|
609
|
+
function isLeafFilter2(value) {
|
|
93
610
|
return Object.keys(value).some((k) => LEAF_OPS.has(k));
|
|
94
611
|
}
|
|
95
612
|
var FilterMapper = class {
|
|
@@ -118,7 +635,7 @@ var FilterMapper = class {
|
|
|
118
635
|
} else {
|
|
119
636
|
const filterValue = value;
|
|
120
637
|
const property = getPropertyRef(key, rootView);
|
|
121
|
-
if (
|
|
638
|
+
if (isLeafFilter2(filterValue)) {
|
|
122
639
|
result.push(...this.leafToFilterDefs(property, filterValue));
|
|
123
640
|
} else {
|
|
124
641
|
const targetView = await this.getNestedTargetView(key, rootView);
|
|
@@ -205,6 +722,85 @@ var FilterMapper = class {
|
|
|
205
722
|
}
|
|
206
723
|
};
|
|
207
724
|
|
|
725
|
+
// src/mappers/aggregate-mapper.ts
|
|
726
|
+
var AggregateMapper = class {
|
|
727
|
+
constructor(viewMapper) {
|
|
728
|
+
this.viewMapper = viewMapper;
|
|
729
|
+
this.filterMapper = new FilterMapper(viewMapper);
|
|
730
|
+
this.validator = new AggregateValidator(viewMapper);
|
|
731
|
+
}
|
|
732
|
+
async map(options) {
|
|
733
|
+
const { viewExternalId, filters, groupBy, aggregate } = options;
|
|
734
|
+
const rootView = await this.viewMapper.getView(viewExternalId);
|
|
735
|
+
await this.validator.validate(options, rootView);
|
|
736
|
+
const filterParts = filters ? await this.filterMapper.map(filters, rootView) : [];
|
|
737
|
+
const filter = filterParts.length === 0 ? void 0 : filterParts.length === 1 ? filterParts[0] : { and: filterParts };
|
|
738
|
+
return {
|
|
739
|
+
view: toViewReference(rootView),
|
|
740
|
+
instanceType: "node",
|
|
741
|
+
limit: AGGREGATE_LIMIT,
|
|
742
|
+
...filter !== void 0 ? { filter } : {},
|
|
743
|
+
...groupBy ? { groupBy: getSelectedGroupByKeys(groupBy) } : {},
|
|
744
|
+
...aggregate ? { aggregates: [mapAggregateDefinition(aggregate)] } : {}
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
function mapAggregateDefinition(aggregate) {
|
|
749
|
+
if ("count" in aggregate) {
|
|
750
|
+
const property = aggregate.count;
|
|
751
|
+
if (property != null && typeof property === "object" && !Array.isArray(property) && Object.keys(property).length === 0) {
|
|
752
|
+
return { count: {} };
|
|
753
|
+
}
|
|
754
|
+
if (typeof property === "string") {
|
|
755
|
+
return { count: { property } };
|
|
756
|
+
}
|
|
757
|
+
return { count: {} };
|
|
758
|
+
}
|
|
759
|
+
if ("avg" in aggregate) {
|
|
760
|
+
return { avg: { property: aggregate.avg } };
|
|
761
|
+
}
|
|
762
|
+
if ("min" in aggregate) {
|
|
763
|
+
return { min: { property: aggregate.min } };
|
|
764
|
+
}
|
|
765
|
+
if ("max" in aggregate) {
|
|
766
|
+
return { max: { property: aggregate.max } };
|
|
767
|
+
}
|
|
768
|
+
if ("sum" in aggregate) {
|
|
769
|
+
return { sum: { property: aggregate.sum } };
|
|
770
|
+
}
|
|
771
|
+
throw new Error("Invalid aggregate definition");
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/mappers/aggregate-result-mapper.ts
|
|
775
|
+
function isNodeId(value) {
|
|
776
|
+
return value != null && typeof value === "object" && "space" in value && "externalId" in value && typeof value.space === "string" && typeof value.externalId === "string";
|
|
777
|
+
}
|
|
778
|
+
var AggregateResultMapper = class {
|
|
779
|
+
map(response, options) {
|
|
780
|
+
const groupByKeys = options.groupBy ? getSelectedGroupByKeys(options.groupBy) : [];
|
|
781
|
+
return response.items.map((item) => {
|
|
782
|
+
let group;
|
|
783
|
+
if (item.group != null && groupByKeys.length > 0) {
|
|
784
|
+
group = {};
|
|
785
|
+
for (const key of groupByKeys) {
|
|
786
|
+
const value = item.group[key];
|
|
787
|
+
if (value === void 0) continue;
|
|
788
|
+
group[key] = isNodeId(value) ? { space: value.space, externalId: value.externalId } : value;
|
|
789
|
+
}
|
|
790
|
+
if (Object.keys(group).length === 0) {
|
|
791
|
+
group = void 0;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
const aggregateValue = item.aggregates[0];
|
|
795
|
+
const aggregate = aggregateValue?.value !== void 0 ? aggregateValue.property != null ? { property: aggregateValue.property, value: aggregateValue.value } : { value: aggregateValue.value } : void 0;
|
|
796
|
+
return {
|
|
797
|
+
...group !== void 0 ? { group } : {},
|
|
798
|
+
...aggregate !== void 0 ? { aggregate } : {}
|
|
799
|
+
};
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
|
|
208
804
|
// src/mappers/sort-mapper.ts
|
|
209
805
|
var SortMapper = class {
|
|
210
806
|
map(sort, rootView) {
|
|
@@ -229,6 +825,7 @@ var QueryMapper = class {
|
|
|
229
825
|
this.viewMapper = viewMapper;
|
|
230
826
|
this.filterMapper = new FilterMapper(viewMapper);
|
|
231
827
|
this.sortMapper = new SortMapper();
|
|
828
|
+
this.validator = new QueryValidator(viewMapper);
|
|
232
829
|
}
|
|
233
830
|
async map(options) {
|
|
234
831
|
const {
|
|
@@ -241,6 +838,7 @@ var QueryMapper = class {
|
|
|
241
838
|
} = options;
|
|
242
839
|
const limit = requestedLimit === -1 ? DEFAULT_LIMIT : requestedLimit;
|
|
243
840
|
const rootView = await this.viewMapper.getView(viewExternalId);
|
|
841
|
+
await this.validator.validate(options, rootView);
|
|
244
842
|
const rootViewRef = toViewReference(rootView);
|
|
245
843
|
const whereFilters = filters ? await this.filterMapper.map(filters, rootView) : [];
|
|
246
844
|
const baseFilters = [{ hasData: [rootViewRef] }, ...whereFilters];
|
|
@@ -531,6 +1129,92 @@ var QueryResultMapper = class {
|
|
|
531
1129
|
return entry;
|
|
532
1130
|
}
|
|
533
1131
|
};
|
|
1132
|
+
var nodeMetadataSchema = {
|
|
1133
|
+
instanceType: zod.z.literal("node").optional(),
|
|
1134
|
+
space: zod.z.string(),
|
|
1135
|
+
externalId: zod.z.string(),
|
|
1136
|
+
version: zod.z.number().optional(),
|
|
1137
|
+
createdTime: zod.z.number().optional(),
|
|
1138
|
+
deletedTime: zod.z.number().optional(),
|
|
1139
|
+
lastUpdatedTime: zod.z.number().optional(),
|
|
1140
|
+
_edges: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
1141
|
+
};
|
|
1142
|
+
function isListRelation(property) {
|
|
1143
|
+
if (isViewPropertyDefinition(property)) {
|
|
1144
|
+
return isListDirectRelation(property);
|
|
1145
|
+
}
|
|
1146
|
+
if (isReverseDirectRelation(property)) {
|
|
1147
|
+
return property.connectionType === "multi_reverse_direct_relation" || property.targetsList === true;
|
|
1148
|
+
}
|
|
1149
|
+
return isEdgeConnection(property);
|
|
1150
|
+
}
|
|
1151
|
+
function isRecord2(value) {
|
|
1152
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
1153
|
+
}
|
|
1154
|
+
var QueryResultValidator = class {
|
|
1155
|
+
constructor(viewMapper) {
|
|
1156
|
+
this.viewMapper = viewMapper;
|
|
1157
|
+
}
|
|
1158
|
+
async parseItems(rootViewExternalId, items, select) {
|
|
1159
|
+
const rootView = await this.viewMapper.getView(rootViewExternalId);
|
|
1160
|
+
const schema = await this.buildResultSchema(rootView, MAX_DEPENDENCY_DEPTH, select);
|
|
1161
|
+
const result = zod.z.array(schema).safeParse(items);
|
|
1162
|
+
if (!result.success) {
|
|
1163
|
+
throw new Error(
|
|
1164
|
+
`Invalid query result:
|
|
1165
|
+
${result.error.issues.map((issue) => `- ${issue.path.map(String).join(".")}: ${issue.message}`).join("\n")}`
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
return result.data;
|
|
1169
|
+
}
|
|
1170
|
+
async buildResultSchema(view, remainingDepth, select) {
|
|
1171
|
+
const shape = { ...nodeMetadataSchema };
|
|
1172
|
+
const includeAllProperties = select == null || select._all === true;
|
|
1173
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
1174
|
+
const isSelected = includeAllProperties || name in select;
|
|
1175
|
+
if (!isSelected) continue;
|
|
1176
|
+
const nestedSelect = isRecord2(select?.[name]) ? select[name] : void 0;
|
|
1177
|
+
if (isViewPropertyDefinition(property)) {
|
|
1178
|
+
const relationSource = getDirectRelationSource(property);
|
|
1179
|
+
if (relationSource) {
|
|
1180
|
+
shape[name] = await this.buildRelationSchema(
|
|
1181
|
+
property,
|
|
1182
|
+
relationSource.externalId,
|
|
1183
|
+
remainingDepth,
|
|
1184
|
+
nestedSelect
|
|
1185
|
+
);
|
|
1186
|
+
} else {
|
|
1187
|
+
shape[name] = propertyValueSchema(property, { dateMode: "coerce" }).optional();
|
|
1188
|
+
}
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
|
|
1192
|
+
shape[name] = await this.buildRelationSchema(
|
|
1193
|
+
property,
|
|
1194
|
+
property.source.externalId,
|
|
1195
|
+
remainingDepth,
|
|
1196
|
+
nestedSelect
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
const schema = zod.z.object(shape);
|
|
1201
|
+
return includeAllProperties ? schema.strict() : schema;
|
|
1202
|
+
}
|
|
1203
|
+
async buildRelationSchema(property, targetViewExternalId, remainingDepth, select) {
|
|
1204
|
+
const isList = isListRelation(property);
|
|
1205
|
+
const fallbackSchema = isViewPropertyDefinition(property) ? propertyValueSchema(property, { dateMode: "coerce" }) : zod.z.unknown();
|
|
1206
|
+
if (remainingDepth <= 0 || select == null) {
|
|
1207
|
+
return fallbackSchema.optional();
|
|
1208
|
+
}
|
|
1209
|
+
const targetView = await this.viewMapper.getView(targetViewExternalId);
|
|
1210
|
+
const nestedSchema = await this.buildResultSchema(targetView, remainingDepth - 1, select);
|
|
1211
|
+
if (isViewPropertyDefinition(property)) {
|
|
1212
|
+
const nestedRelationSchema = isList ? zod.z.array(nestedSchema) : nestedSchema;
|
|
1213
|
+
return zod.z.union([nestedRelationSchema, fallbackSchema]).optional();
|
|
1214
|
+
}
|
|
1215
|
+
return (isList ? zod.z.array(nestedSchema) : nestedSchema).optional();
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
534
1218
|
|
|
535
1219
|
// src/mappers/view-mapper.ts
|
|
536
1220
|
var ViewMapper = class {
|
|
@@ -681,15 +1365,33 @@ function buildDependenciesQuery(previousQuery, nodesParent, nodesChildren, leafC
|
|
|
681
1365
|
|
|
682
1366
|
// src/client.ts
|
|
683
1367
|
var IndustrialModelClient = class {
|
|
684
|
-
constructor(client, dataModelId) {
|
|
1368
|
+
constructor(client, dataModelId, options = {}) {
|
|
685
1369
|
const cognite = createCogniteAdapter(client);
|
|
686
1370
|
this.cognite = cognite;
|
|
687
1371
|
const viewMapper = new ViewMapper(cognite, dataModelId);
|
|
688
1372
|
this.queryMapper = new QueryMapper(viewMapper);
|
|
1373
|
+
this.aggregateMapper = new AggregateMapper(viewMapper);
|
|
1374
|
+
this.aggregateResultMapper = new AggregateResultMapper();
|
|
689
1375
|
this.resultMapper = new QueryResultMapper(viewMapper);
|
|
1376
|
+
this.resultValidator = new QueryResultValidator(viewMapper);
|
|
1377
|
+
this.validateResults = options.validateResults ?? false;
|
|
690
1378
|
}
|
|
691
1379
|
query() {
|
|
692
|
-
|
|
1380
|
+
const execute = (options) => this.queryInternal(options);
|
|
1381
|
+
return execute;
|
|
1382
|
+
}
|
|
1383
|
+
aggregate() {
|
|
1384
|
+
const execute = (options) => this.aggregateInternal(options);
|
|
1385
|
+
return execute;
|
|
1386
|
+
}
|
|
1387
|
+
async aggregateInternal(options) {
|
|
1388
|
+
const cogniteRequest = await this.aggregateMapper.map(options);
|
|
1389
|
+
const response = await this.cognite.aggregateInstances(cogniteRequest);
|
|
1390
|
+
const items = this.aggregateResultMapper.map(
|
|
1391
|
+
response,
|
|
1392
|
+
options
|
|
1393
|
+
);
|
|
1394
|
+
return { items };
|
|
693
1395
|
}
|
|
694
1396
|
async queryInternal(options) {
|
|
695
1397
|
const { viewExternalId, limit = DEFAULT_LIMIT } = options;
|
|
@@ -707,7 +1409,8 @@ var IndustrialModelClient = class {
|
|
|
707
1409
|
mapNodesAndEdges(queryResult),
|
|
708
1410
|
dependenciesData
|
|
709
1411
|
);
|
|
710
|
-
const
|
|
1412
|
+
const mappedPageResult = await this.resultMapper.mapNodes(viewExternalId, queryResultData);
|
|
1413
|
+
const pageResult = this.validateResults ? await this.resultValidator.parseItems(viewExternalId, mappedPageResult, options.select) : mappedPageResult;
|
|
711
1414
|
const nextCursor = queryResult.nextCursor[viewExternalId] ?? null;
|
|
712
1415
|
const isLastPage = pageResult.length < limit || !nextCursor;
|
|
713
1416
|
const resolvedCursor = isLastPage ? null : nextCursor;
|
|
@@ -739,5 +1442,7 @@ var IndustrialModelClient = class {
|
|
|
739
1442
|
};
|
|
740
1443
|
|
|
741
1444
|
exports.IndustrialModelClient = IndustrialModelClient;
|
|
1445
|
+
exports.buildViewSchema = buildViewSchema;
|
|
1446
|
+
exports.nodeIdSchema = nodeIdSchema;
|
|
742
1447
|
//# sourceMappingURL=index.cjs.map
|
|
743
1448
|
//# sourceMappingURL=index.cjs.map
|