industrial-model 0.5.0 → 0.7.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 +723 -545
- package/dist/cognite-core/index.cjs +1882 -0
- package/dist/cognite-core/index.cjs.map +1 -0
- package/dist/cognite-core/index.d.cts +535 -0
- package/dist/cognite-core/index.d.ts +535 -0
- package/dist/cognite-core/index.js +1879 -0
- package/dist/cognite-core/index.js.map +1 -0
- package/dist/index.cjs +544 -193
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -263
- package/dist/index.d.ts +8 -263
- package/dist/index.js +545 -192
- package/dist/index.js.map +1 -1
- package/dist/types-2jjbs2i7.d.cts +267 -0
- package/dist/types-2jjbs2i7.d.ts +267 -0
- package/package.json +11 -1
|
@@ -0,0 +1,1879 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
// src/cognite/adapter.ts
|
|
4
|
+
function createCogniteAdapter(client) {
|
|
5
|
+
return new CogniteSdkAdapter(client);
|
|
6
|
+
}
|
|
7
|
+
var CogniteSdkAdapter = class {
|
|
8
|
+
constructor(client) {
|
|
9
|
+
this.client = client;
|
|
10
|
+
}
|
|
11
|
+
async retrieveDataModels(ids, options) {
|
|
12
|
+
const response = await this.client.dataModels.retrieve(ids, options);
|
|
13
|
+
return {
|
|
14
|
+
items: response.items.map((item) => ({
|
|
15
|
+
createdTime: item.createdTime,
|
|
16
|
+
views: item.views ?? []
|
|
17
|
+
}))
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async queryInstances(request) {
|
|
21
|
+
const response = await this.client.instances.query(
|
|
22
|
+
request
|
|
23
|
+
);
|
|
24
|
+
return {
|
|
25
|
+
items: response.items,
|
|
26
|
+
nextCursor: response.nextCursor
|
|
27
|
+
};
|
|
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
|
+
}
|
|
44
|
+
async applyInstances(request) {
|
|
45
|
+
const apply = this.client.instances.apply;
|
|
46
|
+
const response = await apply(request);
|
|
47
|
+
return {
|
|
48
|
+
items: response.items
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/constants.ts
|
|
54
|
+
var NESTED_SEP = "|";
|
|
55
|
+
var EDGE_MARKER = "<EdgeMarker>";
|
|
56
|
+
var MAX_LIMIT = 1e4;
|
|
57
|
+
var DEFAULT_LIMIT = 1e3;
|
|
58
|
+
var MAX_DEPENDENCY_DEPTH = 3;
|
|
59
|
+
var AGGREGATE_LIMIT = 1e3;
|
|
60
|
+
var MAX_GROUP_BY = 5;
|
|
61
|
+
|
|
62
|
+
// src/utils/query.ts
|
|
63
|
+
function mapNodesAndEdges(queryResult, _query) {
|
|
64
|
+
return queryResult.items;
|
|
65
|
+
}
|
|
66
|
+
function appendNodesAndEdges(initial, additional) {
|
|
67
|
+
if (!additional) return initial;
|
|
68
|
+
for (const [key, items] of Object.entries(additional)) {
|
|
69
|
+
const existing = initial[key];
|
|
70
|
+
if (existing) {
|
|
71
|
+
existing.push(...items);
|
|
72
|
+
} else {
|
|
73
|
+
initial[key] = [...items];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return initial;
|
|
77
|
+
}
|
|
78
|
+
function getQueryForDependenciesPagination(query, queryResult, viewExternalId) {
|
|
79
|
+
const cursorKeys = new Set(Object.keys(queryResult.nextCursor));
|
|
80
|
+
const { nodesParent, nodesChildren } = getParentAndChildrenNodes(cursorKeys);
|
|
81
|
+
const leafCursors = getLeafCursors(queryResult, viewExternalId, nodesParent, nodesChildren);
|
|
82
|
+
if (Object.keys(leafCursors).length === 0) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return buildDependenciesQuery(query, nodesParent, nodesChildren, leafCursors);
|
|
86
|
+
}
|
|
87
|
+
function getParentAndChildrenNodes(keys) {
|
|
88
|
+
const nodesParent = /* @__PURE__ */ new Map();
|
|
89
|
+
const nodesChildren = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const key of keys) {
|
|
91
|
+
const keyParts = key.split(NESTED_SEP);
|
|
92
|
+
const validParents = /* @__PURE__ */ new Set();
|
|
93
|
+
for (let i = keyParts.length - 1; i > 0; i--) {
|
|
94
|
+
const parentPath = keyParts.slice(0, i).join(NESTED_SEP);
|
|
95
|
+
const parentWithEdgeMarker = `${parentPath}${NESTED_SEP}${EDGE_MARKER}`;
|
|
96
|
+
if (keys.has(parentWithEdgeMarker)) {
|
|
97
|
+
validParents.add(parentWithEdgeMarker);
|
|
98
|
+
const children = nodesChildren.get(parentWithEdgeMarker) ?? /* @__PURE__ */ new Set();
|
|
99
|
+
children.add(key);
|
|
100
|
+
nodesChildren.set(parentWithEdgeMarker, children);
|
|
101
|
+
}
|
|
102
|
+
if (keys.has(parentPath)) {
|
|
103
|
+
validParents.add(parentPath);
|
|
104
|
+
const children = nodesChildren.get(parentPath) ?? /* @__PURE__ */ new Set();
|
|
105
|
+
children.add(key);
|
|
106
|
+
nodesChildren.set(parentPath, children);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
nodesParent.set(key, validParents);
|
|
110
|
+
}
|
|
111
|
+
return { nodesParent, nodesChildren };
|
|
112
|
+
}
|
|
113
|
+
function getLeafCursors(queryResult, viewExternalId, nodesParent, nodesChildren) {
|
|
114
|
+
const targetCursors = {};
|
|
115
|
+
const targetCursorKeys = /* @__PURE__ */ new Set();
|
|
116
|
+
for (const [cursorKey, cursorValue] of Object.entries(queryResult.nextCursor)) {
|
|
117
|
+
if (cursorKey === viewExternalId || !cursorValue || (queryResult.items[cursorKey]?.length ?? 0) !== MAX_LIMIT) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const children = nodesChildren.get(cursorKey) ?? /* @__PURE__ */ new Set();
|
|
121
|
+
let skipDueToChild = false;
|
|
122
|
+
for (const c of children) {
|
|
123
|
+
if (targetCursorKeys.has(c)) {
|
|
124
|
+
skipDueToChild = true;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (skipDueToChild) continue;
|
|
129
|
+
const parent = nodesParent.get(cursorKey) ?? /* @__PURE__ */ new Set();
|
|
130
|
+
for (const key of parent) {
|
|
131
|
+
if (targetCursorKeys.has(key)) {
|
|
132
|
+
delete targetCursors[key];
|
|
133
|
+
targetCursorKeys.delete(key);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
targetCursors[cursorKey] = cursorValue;
|
|
137
|
+
targetCursorKeys.add(cursorKey);
|
|
138
|
+
}
|
|
139
|
+
return targetCursors;
|
|
140
|
+
}
|
|
141
|
+
function buildDependenciesQuery(previousQuery, nodesParent, nodesChildren, leafCursors) {
|
|
142
|
+
const withExprs = {};
|
|
143
|
+
const selectExprs = {};
|
|
144
|
+
const finalCursors = {};
|
|
145
|
+
for (const [cursorKey, cursorValue] of Object.entries(leafCursors)) {
|
|
146
|
+
const children = nodesChildren.get(cursorKey) ?? /* @__PURE__ */ new Set();
|
|
147
|
+
const parent = nodesParent.get(cursorKey) ?? /* @__PURE__ */ new Set();
|
|
148
|
+
const validKeys = /* @__PURE__ */ new Set([...parent, ...children, cursorKey]);
|
|
149
|
+
for (const [k, v] of Object.entries(previousQuery.with)) {
|
|
150
|
+
if (validKeys.has(k)) withExprs[k] = v;
|
|
151
|
+
}
|
|
152
|
+
for (const [k, v] of Object.entries(previousQuery.select)) {
|
|
153
|
+
if (validKeys.has(k)) selectExprs[k] = v;
|
|
154
|
+
}
|
|
155
|
+
for (const [k, v] of Object.entries(previousQuery.cursors ?? {})) {
|
|
156
|
+
if (parent.has(k) && v) finalCursors[k] = v;
|
|
157
|
+
}
|
|
158
|
+
finalCursors[cursorKey] = cursorValue;
|
|
159
|
+
}
|
|
160
|
+
return { with: withExprs, select: selectExprs, cursors: finalCursors };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/utils/view.ts
|
|
164
|
+
var NODE_PROPERTIES = /* @__PURE__ */ new Set([
|
|
165
|
+
"externalId",
|
|
166
|
+
"space",
|
|
167
|
+
"createdTime",
|
|
168
|
+
"deletedTime",
|
|
169
|
+
"lastUpdatedTime"
|
|
170
|
+
]);
|
|
171
|
+
function getPropertyRef(property, view, instanceType = "node") {
|
|
172
|
+
if (NODE_PROPERTIES.has(property)) return [instanceType, property];
|
|
173
|
+
return [view.space, `${view.externalId}/${view.version}`, property];
|
|
174
|
+
}
|
|
175
|
+
function toViewReference(view) {
|
|
176
|
+
return { type: "view", space: view.space, externalId: view.externalId, version: view.version };
|
|
177
|
+
}
|
|
178
|
+
function isViewPropertyDefinition(p) {
|
|
179
|
+
return "container" in p;
|
|
180
|
+
}
|
|
181
|
+
function isReverseDirectRelation(p) {
|
|
182
|
+
return "through" in p;
|
|
183
|
+
}
|
|
184
|
+
function isEdgeConnection(p) {
|
|
185
|
+
return !isViewPropertyDefinition(p) && !isReverseDirectRelation(p) && "source" in p;
|
|
186
|
+
}
|
|
187
|
+
function getDirectRelationSource(p) {
|
|
188
|
+
const type = p.type;
|
|
189
|
+
if (type.type === "direct" && type.source) return type.source;
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
function isDirectRelationWithSource(p) {
|
|
193
|
+
if (!isViewPropertyDefinition(p)) return false;
|
|
194
|
+
return getDirectRelationSource(p) !== void 0;
|
|
195
|
+
}
|
|
196
|
+
function isListDirectRelation(p) {
|
|
197
|
+
return p.type.list === true;
|
|
198
|
+
}
|
|
199
|
+
function buildSelect(source, properties) {
|
|
200
|
+
if (properties.length === 0) return {};
|
|
201
|
+
return { sources: [{ source, properties }] };
|
|
202
|
+
}
|
|
203
|
+
var GROUPABLE_PROPERTY_TYPES = /* @__PURE__ */ new Set([
|
|
204
|
+
"text",
|
|
205
|
+
"direct",
|
|
206
|
+
"int32",
|
|
207
|
+
"int64",
|
|
208
|
+
"float32",
|
|
209
|
+
"float64",
|
|
210
|
+
"boolean",
|
|
211
|
+
"enum"
|
|
212
|
+
]);
|
|
213
|
+
var NUMERIC_PROPERTY_TYPES = /* @__PURE__ */ new Set(["int32", "int64", "float32", "float64"]);
|
|
214
|
+
function isGroupableProperty(property) {
|
|
215
|
+
if (!isViewPropertyDefinition(property)) return false;
|
|
216
|
+
if (property.type.list === true) return false;
|
|
217
|
+
const type = property.type.type;
|
|
218
|
+
return type != null && GROUPABLE_PROPERTY_TYPES.has(type);
|
|
219
|
+
}
|
|
220
|
+
function isNumericProperty(property) {
|
|
221
|
+
const type = property.type.type;
|
|
222
|
+
return type != null && NUMERIC_PROPERTY_TYPES.has(type);
|
|
223
|
+
}
|
|
224
|
+
function getSelectedGroupByKeys(groupBy) {
|
|
225
|
+
return Object.entries(groupBy).filter((entry) => entry[1] === true).map(([key]) => key);
|
|
226
|
+
}
|
|
227
|
+
var nodeIdSchema = z.object({
|
|
228
|
+
space: z.string().min(1),
|
|
229
|
+
externalId: z.string().min(1)
|
|
230
|
+
});
|
|
231
|
+
function dateSchema(dateMode) {
|
|
232
|
+
if (dateMode === "coerce") {
|
|
233
|
+
return z.preprocess(
|
|
234
|
+
(value) => typeof value === "string" || typeof value === "number" ? new Date(value) : value,
|
|
235
|
+
z.date()
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return z.union([z.string(), z.date()]);
|
|
239
|
+
}
|
|
240
|
+
function propertyValueSchema(property, options = {}) {
|
|
241
|
+
const type = property.type;
|
|
242
|
+
let schema;
|
|
243
|
+
switch (type.type) {
|
|
244
|
+
case "text":
|
|
245
|
+
case "enum":
|
|
246
|
+
schema = z.string();
|
|
247
|
+
break;
|
|
248
|
+
case "int32":
|
|
249
|
+
case "int64":
|
|
250
|
+
schema = z.number().int();
|
|
251
|
+
break;
|
|
252
|
+
case "float32":
|
|
253
|
+
case "float64":
|
|
254
|
+
schema = z.number();
|
|
255
|
+
break;
|
|
256
|
+
case "boolean":
|
|
257
|
+
schema = z.boolean();
|
|
258
|
+
break;
|
|
259
|
+
case "date":
|
|
260
|
+
case "timestamp":
|
|
261
|
+
schema = dateSchema(options.dateMode);
|
|
262
|
+
break;
|
|
263
|
+
case "direct":
|
|
264
|
+
schema = nodeIdSchema;
|
|
265
|
+
break;
|
|
266
|
+
case "json":
|
|
267
|
+
schema = z.unknown();
|
|
268
|
+
break;
|
|
269
|
+
default:
|
|
270
|
+
schema = z.unknown();
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
return type.list === true ? z.array(schema) : schema;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/validators/query-validator.ts
|
|
277
|
+
var NODE_STRING_PROPERTIES = ["externalId", "space"];
|
|
278
|
+
var NODE_NUMBER_PROPERTIES = ["createdTime", "deletedTime", "lastUpdatedTime"];
|
|
279
|
+
var NODE_PROPERTIES2 = /* @__PURE__ */ new Set([...NODE_STRING_PROPERTIES, ...NODE_NUMBER_PROPERTIES]);
|
|
280
|
+
var SORT_DIRECTION_SCHEMA = z.enum(["ascending", "descending"]);
|
|
281
|
+
var recordSchema = z.record(z.string(), z.unknown());
|
|
282
|
+
var leafOps = /* @__PURE__ */ new Set([
|
|
283
|
+
"eq",
|
|
284
|
+
"in",
|
|
285
|
+
"gt",
|
|
286
|
+
"gte",
|
|
287
|
+
"lt",
|
|
288
|
+
"lte",
|
|
289
|
+
"exists",
|
|
290
|
+
"prefix",
|
|
291
|
+
"search",
|
|
292
|
+
"containsAny",
|
|
293
|
+
"containsAll"
|
|
294
|
+
]);
|
|
295
|
+
function isRecord(value) {
|
|
296
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
297
|
+
}
|
|
298
|
+
function isLeafFilter(value) {
|
|
299
|
+
return Object.keys(value).some((key) => leafOps.has(key));
|
|
300
|
+
}
|
|
301
|
+
function issuePath(path) {
|
|
302
|
+
return path.length === 0 ? "query" : path.map(String).join(".");
|
|
303
|
+
}
|
|
304
|
+
function formatZodIssues(error, path) {
|
|
305
|
+
return error.issues.map((issue) => `${issuePath([...path, ...issue.path])}: ${issue.message}`);
|
|
306
|
+
}
|
|
307
|
+
function getRelationTarget(property) {
|
|
308
|
+
if (isViewPropertyDefinition(property)) {
|
|
309
|
+
return getDirectRelationSource(property)?.externalId ?? null;
|
|
310
|
+
}
|
|
311
|
+
if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
|
|
312
|
+
return property.source.externalId;
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
function baseValueSchema(property) {
|
|
317
|
+
if (property === "node-string") return z.string();
|
|
318
|
+
if (property === "node-number") return z.number();
|
|
319
|
+
switch (property.type.type) {
|
|
320
|
+
case "text":
|
|
321
|
+
case "enum":
|
|
322
|
+
return z.string();
|
|
323
|
+
case "int32":
|
|
324
|
+
case "int64":
|
|
325
|
+
return z.number().int();
|
|
326
|
+
case "float32":
|
|
327
|
+
case "float64":
|
|
328
|
+
return z.number();
|
|
329
|
+
case "boolean":
|
|
330
|
+
return z.boolean();
|
|
331
|
+
case "date":
|
|
332
|
+
case "timestamp":
|
|
333
|
+
return z.union([z.string(), z.date()]);
|
|
334
|
+
case "direct":
|
|
335
|
+
return nodeIdSchema;
|
|
336
|
+
default:
|
|
337
|
+
return z.union([z.string(), z.number(), z.boolean()]);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function leafFilterSchema(property) {
|
|
341
|
+
const value = baseValueSchema(property);
|
|
342
|
+
const isList = typeof property !== "string" && property.type.list === true;
|
|
343
|
+
if (isList) {
|
|
344
|
+
const shape = {
|
|
345
|
+
containsAny: z.array(value).optional(),
|
|
346
|
+
containsAll: z.array(value).optional(),
|
|
347
|
+
exists: z.boolean().optional()
|
|
348
|
+
};
|
|
349
|
+
if (property.type.type === "text") {
|
|
350
|
+
shape.search = z.object({
|
|
351
|
+
query: z.string(),
|
|
352
|
+
operator: z.enum(["OR", "AND"]).optional()
|
|
353
|
+
}).strict().optional();
|
|
354
|
+
}
|
|
355
|
+
return z.object(shape).strict();
|
|
356
|
+
}
|
|
357
|
+
if (property === "node-string" || typeof property !== "string" && property.type.type === "text") {
|
|
358
|
+
const shape = {
|
|
359
|
+
eq: z.string().optional(),
|
|
360
|
+
in: z.array(z.string()).optional(),
|
|
361
|
+
prefix: z.string().optional(),
|
|
362
|
+
exists: z.boolean().optional()
|
|
363
|
+
};
|
|
364
|
+
if (property !== "node-string") {
|
|
365
|
+
shape.search = z.object({
|
|
366
|
+
query: z.string(),
|
|
367
|
+
operator: z.enum(["OR", "AND"]).optional()
|
|
368
|
+
}).strict().optional();
|
|
369
|
+
}
|
|
370
|
+
return z.object(shape).strict();
|
|
371
|
+
}
|
|
372
|
+
if (typeof property !== "string" && property.type.type === "enum") {
|
|
373
|
+
return z.object({
|
|
374
|
+
eq: z.string().optional(),
|
|
375
|
+
in: z.array(z.string()).optional(),
|
|
376
|
+
prefix: z.string().optional(),
|
|
377
|
+
exists: z.boolean().optional()
|
|
378
|
+
}).strict();
|
|
379
|
+
}
|
|
380
|
+
if (property === "node-number" || typeof property !== "string" && ["int32", "int64", "float32", "float64"].includes(property.type.type ?? "")) {
|
|
381
|
+
return z.object({
|
|
382
|
+
eq: value.optional(),
|
|
383
|
+
in: z.array(value).optional(),
|
|
384
|
+
gt: value.optional(),
|
|
385
|
+
gte: value.optional(),
|
|
386
|
+
lt: value.optional(),
|
|
387
|
+
lte: value.optional(),
|
|
388
|
+
exists: z.boolean().optional()
|
|
389
|
+
}).strict();
|
|
390
|
+
}
|
|
391
|
+
if (typeof property !== "string" && ["date", "timestamp"].includes(property.type.type ?? "")) {
|
|
392
|
+
return z.object({
|
|
393
|
+
eq: value.optional(),
|
|
394
|
+
in: z.array(value).optional(),
|
|
395
|
+
gt: value.optional(),
|
|
396
|
+
gte: value.optional(),
|
|
397
|
+
lt: value.optional(),
|
|
398
|
+
lte: value.optional(),
|
|
399
|
+
exists: z.boolean().optional()
|
|
400
|
+
}).strict();
|
|
401
|
+
}
|
|
402
|
+
if (typeof property !== "string" && property.type.type === "boolean") {
|
|
403
|
+
return z.object({
|
|
404
|
+
eq: z.boolean().optional(),
|
|
405
|
+
exists: z.boolean().optional()
|
|
406
|
+
}).strict();
|
|
407
|
+
}
|
|
408
|
+
if (typeof property !== "string" && property.type.type === "direct") {
|
|
409
|
+
return z.object({
|
|
410
|
+
eq: nodeIdSchema.optional(),
|
|
411
|
+
in: z.array(nodeIdSchema).optional(),
|
|
412
|
+
exists: z.boolean().optional()
|
|
413
|
+
}).strict();
|
|
414
|
+
}
|
|
415
|
+
return z.object({
|
|
416
|
+
eq: value.optional(),
|
|
417
|
+
in: z.array(value).optional(),
|
|
418
|
+
exists: z.boolean().optional()
|
|
419
|
+
}).strict();
|
|
420
|
+
}
|
|
421
|
+
var QueryValidator = class {
|
|
422
|
+
constructor(viewMapper) {
|
|
423
|
+
this.viewMapper = viewMapper;
|
|
424
|
+
}
|
|
425
|
+
async validate(options, rootView) {
|
|
426
|
+
const errors = [];
|
|
427
|
+
errors.push(...this.validateOptionsShape(options, rootView));
|
|
428
|
+
if (options.select !== void 0) {
|
|
429
|
+
errors.push(...await this.validateSelect(options.select, rootView, ["select"]));
|
|
430
|
+
}
|
|
431
|
+
if (options.filters !== void 0) {
|
|
432
|
+
errors.push(...await this.validateWhereInput(options.filters, rootView, ["filters"]));
|
|
433
|
+
}
|
|
434
|
+
if (options.sort !== void 0) {
|
|
435
|
+
errors.push(...this.validateSort(options.sort, rootView, ["sort"]));
|
|
436
|
+
}
|
|
437
|
+
if (errors.length > 0) {
|
|
438
|
+
throw new Error(`Invalid query options:
|
|
439
|
+
${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
validateOptionsShape(options, rootView) {
|
|
443
|
+
const schema = z.object({
|
|
444
|
+
viewExternalId: z.literal(rootView.externalId),
|
|
445
|
+
select: z.unknown().optional(),
|
|
446
|
+
filters: z.unknown().optional(),
|
|
447
|
+
sort: z.unknown().optional(),
|
|
448
|
+
limit: z.union([z.literal(-1), z.number().int().positive().max(MAX_LIMIT)]).optional(),
|
|
449
|
+
cursor: z.string().nullable().optional()
|
|
450
|
+
}).strict();
|
|
451
|
+
const result = schema.safeParse(options);
|
|
452
|
+
return result.success ? [] : formatZodIssues(result.error, []);
|
|
453
|
+
}
|
|
454
|
+
async validateSelect(select, view, path) {
|
|
455
|
+
const shape = {
|
|
456
|
+
_all: z.literal(true).optional()
|
|
457
|
+
};
|
|
458
|
+
for (const property of NODE_PROPERTIES2) {
|
|
459
|
+
shape[property] = z.boolean().optional();
|
|
460
|
+
}
|
|
461
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
462
|
+
const target = getRelationTarget(property);
|
|
463
|
+
if (target != null) {
|
|
464
|
+
const nestedSelect = recordSchema;
|
|
465
|
+
shape[name] = isViewPropertyDefinition(property) ? z.union([z.boolean(), nestedSelect]).optional() : nestedSelect.optional();
|
|
466
|
+
} else {
|
|
467
|
+
shape[name] = z.boolean().optional();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const result = z.object(shape).strict().safeParse(select);
|
|
471
|
+
if (!result.success) return formatZodIssues(result.error, path);
|
|
472
|
+
if (!isRecord(select)) return [];
|
|
473
|
+
const errors = [];
|
|
474
|
+
for (const [name, value] of Object.entries(select)) {
|
|
475
|
+
if (name === "_all" || value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const property = view.properties[name];
|
|
479
|
+
if (!property) continue;
|
|
480
|
+
const target = getRelationTarget(property);
|
|
481
|
+
if (target == null) {
|
|
482
|
+
errors.push(
|
|
483
|
+
`${issuePath([...path, name])}: property "${name}" does not support nested select`
|
|
484
|
+
);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
if (isReverseDirectRelation(property) && property.targetsList) {
|
|
488
|
+
errors.push(
|
|
489
|
+
`${issuePath([...path, name])}: cannot select "${name}" \u2014 Cognite does not support inward traversal of list direct relations. Query "${property.source.externalId}" directly and filter by the "${property.through.identifier}" field instead.`
|
|
490
|
+
);
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const targetView = await this.viewMapper.getView(target);
|
|
494
|
+
errors.push(...await this.validateSelect(value, targetView, [...path, name]));
|
|
495
|
+
}
|
|
496
|
+
return errors;
|
|
497
|
+
}
|
|
498
|
+
async validateWhereInput(filters, view, path) {
|
|
499
|
+
return this.validateFilters(filters, view, path);
|
|
500
|
+
}
|
|
501
|
+
async validateFilters(filters, view, path) {
|
|
502
|
+
const shape = {
|
|
503
|
+
AND: z.union([recordSchema, z.array(recordSchema)]).optional(),
|
|
504
|
+
OR: z.array(recordSchema).optional(),
|
|
505
|
+
NOT: z.union([recordSchema, z.array(recordSchema)]).optional()
|
|
506
|
+
};
|
|
507
|
+
for (const property of NODE_STRING_PROPERTIES) {
|
|
508
|
+
shape[property] = z.unknown().optional();
|
|
509
|
+
}
|
|
510
|
+
for (const property of NODE_NUMBER_PROPERTIES) {
|
|
511
|
+
shape[property] = z.unknown().optional();
|
|
512
|
+
}
|
|
513
|
+
for (const property of Object.keys(view.properties)) {
|
|
514
|
+
shape[property] = z.unknown().optional();
|
|
515
|
+
}
|
|
516
|
+
const result = z.object(shape).strict().safeParse(filters);
|
|
517
|
+
if (!result.success) return formatZodIssues(result.error, path);
|
|
518
|
+
if (!isRecord(filters)) return [];
|
|
519
|
+
const errors = [];
|
|
520
|
+
for (const [name, value] of Object.entries(filters)) {
|
|
521
|
+
if (value == null) continue;
|
|
522
|
+
if (name === "AND" || name === "OR" || name === "NOT") {
|
|
523
|
+
const clauses = Array.isArray(value) ? value : [value];
|
|
524
|
+
for (const [index, clause] of clauses.entries()) {
|
|
525
|
+
errors.push(...await this.validateFilters(clause, view, [...path, name, index]));
|
|
526
|
+
}
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (!isRecord(value)) {
|
|
530
|
+
errors.push(`${issuePath([...path, name])}: Expected object`);
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
const nodePropertyType = NODE_STRING_PROPERTIES.includes(
|
|
534
|
+
name
|
|
535
|
+
) ? "node-string" : NODE_NUMBER_PROPERTIES.includes(name) ? "node-number" : null;
|
|
536
|
+
if (nodePropertyType != null) {
|
|
537
|
+
errors.push(...this.validateLeafFilter(value, nodePropertyType, [...path, name]));
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
const property = view.properties[name];
|
|
541
|
+
if (!property) continue;
|
|
542
|
+
if (isViewPropertyDefinition(property)) {
|
|
543
|
+
const target2 = getDirectRelationSource(property);
|
|
544
|
+
if (target2 != null && !isLeafFilter(value)) {
|
|
545
|
+
const targetView = await this.viewMapper.getView(target2.externalId);
|
|
546
|
+
errors.push(...await this.validateFilters(value, targetView, [...path, name]));
|
|
547
|
+
} else {
|
|
548
|
+
errors.push(...this.validateLeafFilter(value, property, [...path, name]));
|
|
549
|
+
}
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const target = getRelationTarget(property);
|
|
553
|
+
if (target == null) {
|
|
554
|
+
errors.push(`${issuePath([...path, name])}: property "${name}" does not support filters`);
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
errors.push(
|
|
558
|
+
`${issuePath([...path, name])}: filtering through "${name}" is not supported by the query mapper`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
return errors;
|
|
562
|
+
}
|
|
563
|
+
validateLeafFilter(value, property, path) {
|
|
564
|
+
const result = leafFilterSchema(property).safeParse(value);
|
|
565
|
+
return result.success ? [] : formatZodIssues(result.error, path);
|
|
566
|
+
}
|
|
567
|
+
validateSort(sort, view, path) {
|
|
568
|
+
const shape = {};
|
|
569
|
+
for (const property of NODE_PROPERTIES2) {
|
|
570
|
+
shape[property] = SORT_DIRECTION_SCHEMA.optional();
|
|
571
|
+
}
|
|
572
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
573
|
+
if (isViewPropertyDefinition(property) && property.type.list !== true) {
|
|
574
|
+
shape[name] = SORT_DIRECTION_SCHEMA.optional();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const result = z.object(shape).strict().safeParse(sort);
|
|
578
|
+
return result.success ? [] : formatZodIssues(result.error, path);
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
// src/validators/aggregate-validator.ts
|
|
583
|
+
var NODE_COUNT_PROPERTIES = /* @__PURE__ */ new Set(["externalId", "space"]);
|
|
584
|
+
function issuePath2(path) {
|
|
585
|
+
return path.length === 0 ? "aggregate" : path.map(String).join(".");
|
|
586
|
+
}
|
|
587
|
+
function formatZodIssues2(error, path) {
|
|
588
|
+
return error.issues.map((issue) => `${issuePath2([...path, ...issue.path])}: ${issue.message}`);
|
|
589
|
+
}
|
|
590
|
+
function isEmptyObject(value) {
|
|
591
|
+
return value != null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
592
|
+
}
|
|
593
|
+
var AggregateValidator = class {
|
|
594
|
+
constructor(viewMapper) {
|
|
595
|
+
this.queryValidator = new QueryValidator(viewMapper);
|
|
596
|
+
}
|
|
597
|
+
async validate(options, rootView) {
|
|
598
|
+
const errors = [];
|
|
599
|
+
errors.push(...this.validateOptionsShape(options, rootView));
|
|
600
|
+
const selectedGroupBy = options.groupBy ? getSelectedGroupByKeys(options.groupBy) : [];
|
|
601
|
+
if (selectedGroupBy.length === 0 && options.aggregate === void 0) {
|
|
602
|
+
errors.push("aggregate: either groupBy or aggregate must be provided");
|
|
603
|
+
}
|
|
604
|
+
if (options.filters !== void 0) {
|
|
605
|
+
errors.push(
|
|
606
|
+
...await this.queryValidator.validateWhereInput(options.filters, rootView, ["filters"])
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
if (options.groupBy !== void 0) {
|
|
610
|
+
errors.push(...this.validateGroupBy(options.groupBy, rootView, ["groupBy"]));
|
|
611
|
+
}
|
|
612
|
+
if (options.aggregate !== void 0) {
|
|
613
|
+
errors.push(
|
|
614
|
+
...this.validateAggregate(
|
|
615
|
+
options.aggregate,
|
|
616
|
+
rootView,
|
|
617
|
+
["aggregate"]
|
|
618
|
+
)
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
if (errors.length > 0) {
|
|
622
|
+
throw new Error(
|
|
623
|
+
`Invalid aggregate options:
|
|
624
|
+
${errors.map((error) => `- ${error}`).join("\n")}`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
validateOptionsShape(options, rootView) {
|
|
629
|
+
const schema = z.object({
|
|
630
|
+
viewExternalId: z.literal(rootView.externalId),
|
|
631
|
+
filters: z.unknown().optional(),
|
|
632
|
+
groupBy: z.unknown().optional(),
|
|
633
|
+
aggregate: z.unknown().optional()
|
|
634
|
+
}).strict();
|
|
635
|
+
const result = schema.safeParse(options);
|
|
636
|
+
return result.success ? [] : formatZodIssues2(result.error, []);
|
|
637
|
+
}
|
|
638
|
+
validateGroupBy(groupBy, view, path) {
|
|
639
|
+
const shape = {};
|
|
640
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
641
|
+
if (isGroupableProperty(property)) {
|
|
642
|
+
shape[name] = z.literal(true).optional();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const result = z.object(shape).strict().safeParse(groupBy);
|
|
646
|
+
if (!result.success) {
|
|
647
|
+
return formatZodIssues2(result.error, path);
|
|
648
|
+
}
|
|
649
|
+
const selected = getSelectedGroupByKeys(groupBy);
|
|
650
|
+
const errors = [];
|
|
651
|
+
if (selected.length === 0) {
|
|
652
|
+
errors.push(`${issuePath2(path)}: at least one property must be set to true`);
|
|
653
|
+
}
|
|
654
|
+
if (selected.length > MAX_GROUP_BY) {
|
|
655
|
+
errors.push(`${issuePath2(path)}: at most ${MAX_GROUP_BY} properties can be grouped`);
|
|
656
|
+
}
|
|
657
|
+
for (const name of selected) {
|
|
658
|
+
const property = view.properties[name];
|
|
659
|
+
if (!property || !isGroupableProperty(property)) {
|
|
660
|
+
errors.push(`${issuePath2([...path, name])}: property "${name}" cannot be used in groupBy`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return errors;
|
|
664
|
+
}
|
|
665
|
+
validateAggregate(aggregate, view, path) {
|
|
666
|
+
if ("count" in aggregate) {
|
|
667
|
+
const property2 = aggregate.count;
|
|
668
|
+
if (isEmptyObject(property2)) {
|
|
669
|
+
return [];
|
|
670
|
+
}
|
|
671
|
+
if (typeof property2 === "string") {
|
|
672
|
+
if (NODE_COUNT_PROPERTIES.has(property2)) {
|
|
673
|
+
return [];
|
|
674
|
+
}
|
|
675
|
+
const viewProperty = view.properties[property2];
|
|
676
|
+
if (!viewProperty || !isGroupableProperty(viewProperty)) {
|
|
677
|
+
return [`${issuePath2([...path, "count"])}: property "${property2}" cannot be counted`];
|
|
678
|
+
}
|
|
679
|
+
return [];
|
|
680
|
+
}
|
|
681
|
+
return [`${issuePath2([...path, "count"])}: invalid count property`];
|
|
682
|
+
}
|
|
683
|
+
let propertyName;
|
|
684
|
+
let numericOp = null;
|
|
685
|
+
if ("avg" in aggregate) {
|
|
686
|
+
numericOp = "avg";
|
|
687
|
+
propertyName = aggregate.avg;
|
|
688
|
+
} else if ("min" in aggregate) {
|
|
689
|
+
numericOp = "min";
|
|
690
|
+
propertyName = aggregate.min;
|
|
691
|
+
} else if ("max" in aggregate) {
|
|
692
|
+
numericOp = "max";
|
|
693
|
+
propertyName = aggregate.max;
|
|
694
|
+
} else if ("sum" in aggregate) {
|
|
695
|
+
numericOp = "sum";
|
|
696
|
+
propertyName = aggregate.sum;
|
|
697
|
+
}
|
|
698
|
+
if (numericOp == null) {
|
|
699
|
+
return [`${issuePath2(path)}: unknown aggregate operation`];
|
|
700
|
+
}
|
|
701
|
+
if (typeof propertyName !== "string") {
|
|
702
|
+
return [`${issuePath2(path)}: aggregate property must be a string`];
|
|
703
|
+
}
|
|
704
|
+
const property = view.properties[propertyName];
|
|
705
|
+
if (!property || !isViewPropertyDefinition(property) || !isNumericProperty(property)) {
|
|
706
|
+
return [
|
|
707
|
+
`${issuePath2([...path, numericOp])}: property "${propertyName}" must be a numeric view property`
|
|
708
|
+
];
|
|
709
|
+
}
|
|
710
|
+
if (getDirectRelationSource(property) != null) {
|
|
711
|
+
return [
|
|
712
|
+
`${issuePath2([...path, numericOp])}: property "${propertyName}" is a relation and cannot be aggregated`
|
|
713
|
+
];
|
|
714
|
+
}
|
|
715
|
+
return [];
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
var nodeMetadataSchema = {
|
|
719
|
+
instanceType: z.literal("node").optional(),
|
|
720
|
+
space: z.string(),
|
|
721
|
+
externalId: z.string(),
|
|
722
|
+
version: z.number().optional(),
|
|
723
|
+
createdTime: z.number().optional(),
|
|
724
|
+
deletedTime: z.number().optional(),
|
|
725
|
+
lastUpdatedTime: z.number().optional(),
|
|
726
|
+
_edges: z.record(z.string(), z.unknown()).optional()
|
|
727
|
+
};
|
|
728
|
+
function isListRelation(property) {
|
|
729
|
+
if (isViewPropertyDefinition(property)) {
|
|
730
|
+
return isListDirectRelation(property);
|
|
731
|
+
}
|
|
732
|
+
if (isReverseDirectRelation(property)) {
|
|
733
|
+
return property.connectionType === "multi_reverse_direct_relation" || property.targetsList === true;
|
|
734
|
+
}
|
|
735
|
+
return isEdgeConnection(property);
|
|
736
|
+
}
|
|
737
|
+
function isRecord2(value) {
|
|
738
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
739
|
+
}
|
|
740
|
+
var QueryResultValidator = class {
|
|
741
|
+
constructor(viewMapper) {
|
|
742
|
+
this.viewMapper = viewMapper;
|
|
743
|
+
}
|
|
744
|
+
async parseItems(rootViewExternalId, items, select) {
|
|
745
|
+
const rootView = await this.viewMapper.getView(rootViewExternalId);
|
|
746
|
+
const schema = await this.buildResultSchema(rootView, MAX_DEPENDENCY_DEPTH, select);
|
|
747
|
+
const result = z.array(schema).safeParse(items);
|
|
748
|
+
if (!result.success) {
|
|
749
|
+
throw new Error(
|
|
750
|
+
`Invalid query result:
|
|
751
|
+
${result.error.issues.map((issue) => `- ${issue.path.map(String).join(".")}: ${issue.message}`).join("\n")}`
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
return result.data;
|
|
755
|
+
}
|
|
756
|
+
async buildResultSchema(view, remainingDepth, select) {
|
|
757
|
+
const shape = { ...nodeMetadataSchema };
|
|
758
|
+
const includeAllProperties = select == null || select._all === true;
|
|
759
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
760
|
+
const isSelected = includeAllProperties || name in select;
|
|
761
|
+
if (!isSelected) continue;
|
|
762
|
+
const nestedSelect = isRecord2(select?.[name]) ? select[name] : void 0;
|
|
763
|
+
if (isViewPropertyDefinition(property)) {
|
|
764
|
+
const relationSource = getDirectRelationSource(property);
|
|
765
|
+
if (relationSource) {
|
|
766
|
+
shape[name] = await this.buildRelationSchema(
|
|
767
|
+
property,
|
|
768
|
+
relationSource.externalId,
|
|
769
|
+
remainingDepth,
|
|
770
|
+
nestedSelect
|
|
771
|
+
);
|
|
772
|
+
} else {
|
|
773
|
+
shape[name] = propertyValueSchema(property, { dateMode: "coerce" }).optional();
|
|
774
|
+
}
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
|
|
778
|
+
shape[name] = await this.buildRelationSchema(
|
|
779
|
+
property,
|
|
780
|
+
property.source.externalId,
|
|
781
|
+
remainingDepth,
|
|
782
|
+
nestedSelect
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const schema = z.object(shape);
|
|
787
|
+
return includeAllProperties ? schema.strict() : schema;
|
|
788
|
+
}
|
|
789
|
+
async buildRelationSchema(property, targetViewExternalId, remainingDepth, select) {
|
|
790
|
+
const isList = isListRelation(property);
|
|
791
|
+
const fallbackSchema = isViewPropertyDefinition(property) ? propertyValueSchema(property, { dateMode: "coerce" }) : z.unknown();
|
|
792
|
+
if (remainingDepth <= 0 || select == null) {
|
|
793
|
+
return fallbackSchema.optional();
|
|
794
|
+
}
|
|
795
|
+
const targetView = await this.viewMapper.getView(targetViewExternalId);
|
|
796
|
+
const nestedSchema = await this.buildResultSchema(targetView, remainingDepth - 1, select);
|
|
797
|
+
if (isViewPropertyDefinition(property)) {
|
|
798
|
+
const nestedRelationSchema = isList ? z.array(nestedSchema) : nestedSchema;
|
|
799
|
+
return z.union([nestedRelationSchema, fallbackSchema]).optional();
|
|
800
|
+
}
|
|
801
|
+
return (isList ? z.array(nestedSchema) : nestedSchema).optional();
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
var strictNodeIdSchema = z.object({
|
|
805
|
+
space: z.string().min(1),
|
|
806
|
+
externalId: z.string().min(1)
|
|
807
|
+
}).strict();
|
|
808
|
+
var nodeIdLikeSchema = z.object({
|
|
809
|
+
space: z.string().min(1),
|
|
810
|
+
externalId: z.string().min(1)
|
|
811
|
+
}).loose();
|
|
812
|
+
var optionsSchema = z.object({
|
|
813
|
+
viewExternalId: z.string().min(1),
|
|
814
|
+
items: z.array(z.record(z.string(), z.unknown())),
|
|
815
|
+
onEdgeCreation: z.record(z.string(), z.function()).optional(),
|
|
816
|
+
replace: z.boolean().optional(),
|
|
817
|
+
edgeMode: z.enum(["append", "replace"]).optional()
|
|
818
|
+
}).strict();
|
|
819
|
+
function issuePath3(path) {
|
|
820
|
+
return path.length === 0 ? "upsert" : path.map(String).join(".");
|
|
821
|
+
}
|
|
822
|
+
function formatZodIssues3(error, path) {
|
|
823
|
+
return error.issues.map((issue) => `${issuePath3([...path, ...issue.path])}: ${issue.message}`);
|
|
824
|
+
}
|
|
825
|
+
function relationValueSchema(property) {
|
|
826
|
+
if (isReverseDirectRelation(property) && property.targetsList === true) {
|
|
827
|
+
return z.never();
|
|
828
|
+
}
|
|
829
|
+
return z.union([nodeIdLikeSchema, z.array(nodeIdLikeSchema)]);
|
|
830
|
+
}
|
|
831
|
+
var UpsertValidator = class {
|
|
832
|
+
validate(options, rootView) {
|
|
833
|
+
const errors = [];
|
|
834
|
+
const optionsResult = optionsSchema.safeParse(options);
|
|
835
|
+
if (!optionsResult.success) {
|
|
836
|
+
errors.push(...formatZodIssues3(optionsResult.error, []));
|
|
837
|
+
}
|
|
838
|
+
if (options.viewExternalId !== rootView.externalId) {
|
|
839
|
+
errors.push(
|
|
840
|
+
`viewExternalId: expected "${rootView.externalId}", received "${options.viewExternalId}"`
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
for (const [index, item] of options.items.entries()) {
|
|
844
|
+
errors.push(
|
|
845
|
+
...this.validateItem(item, rootView, ["items", index])
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
if (errors.length > 0) {
|
|
849
|
+
throw new Error(`Invalid upsert options:
|
|
850
|
+
${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
validateItem(item, view, path) {
|
|
854
|
+
const errors = [];
|
|
855
|
+
const identityResult = strictNodeIdSchema.safeParse({
|
|
856
|
+
space: item.space,
|
|
857
|
+
externalId: item.externalId
|
|
858
|
+
});
|
|
859
|
+
if (!identityResult.success) {
|
|
860
|
+
errors.push(...formatZodIssues3(identityResult.error, path));
|
|
861
|
+
}
|
|
862
|
+
for (const [name, value] of Object.entries(item)) {
|
|
863
|
+
if (name === "space" || name === "externalId") continue;
|
|
864
|
+
const property = view.properties[name];
|
|
865
|
+
if (!property) {
|
|
866
|
+
errors.push(`${issuePath3([...path, name])}: unknown view property`);
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (isViewPropertyDefinition(property)) {
|
|
870
|
+
const schema = property.type.type === "direct" ? property.type.list === true ? z.array(nodeIdLikeSchema) : nodeIdLikeSchema : propertyValueSchema(property);
|
|
871
|
+
const result = schema.safeParse(value);
|
|
872
|
+
if (!result.success) errors.push(...formatZodIssues3(result.error, [...path, name]));
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
|
|
876
|
+
const result = relationValueSchema(property).safeParse(value);
|
|
877
|
+
if (!result.success) errors.push(...formatZodIssues3(result.error, [...path, name]));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return errors;
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
// src/mappers/filter-mapper.ts
|
|
885
|
+
var LEAF_OPS = /* @__PURE__ */ new Set([
|
|
886
|
+
"eq",
|
|
887
|
+
"in",
|
|
888
|
+
"gt",
|
|
889
|
+
"gte",
|
|
890
|
+
"lt",
|
|
891
|
+
"lte",
|
|
892
|
+
"exists",
|
|
893
|
+
"prefix",
|
|
894
|
+
"containsAny",
|
|
895
|
+
"containsAll"
|
|
896
|
+
]);
|
|
897
|
+
function isLeafFilter2(value) {
|
|
898
|
+
return Object.keys(value).some((k) => LEAF_OPS.has(k));
|
|
899
|
+
}
|
|
900
|
+
var FilterMapper = class {
|
|
901
|
+
constructor(viewMapper, cognite) {
|
|
902
|
+
this.viewMapper = viewMapper;
|
|
903
|
+
this.cognite = cognite;
|
|
904
|
+
}
|
|
905
|
+
async map(input, rootView) {
|
|
906
|
+
const result = [];
|
|
907
|
+
const searchQueries = {};
|
|
908
|
+
for (const [key, value] of Object.entries(input)) {
|
|
909
|
+
if (value == null) continue;
|
|
910
|
+
if (key === "AND") {
|
|
911
|
+
const clauses = Array.isArray(value) ? value : [value];
|
|
912
|
+
const inner = await Promise.all(clauses.map((c) => this.whereInputToSingle(c, rootView)));
|
|
913
|
+
result.push({ and: inner });
|
|
914
|
+
} else if (key === "OR") {
|
|
915
|
+
const clauses = value;
|
|
916
|
+
const branches = await Promise.all(
|
|
917
|
+
clauses.map((c) => this.whereInputToSingle(c, rootView))
|
|
918
|
+
);
|
|
919
|
+
result.push({ or: branches });
|
|
920
|
+
} else if (key === "NOT") {
|
|
921
|
+
const clauses = Array.isArray(value) ? value : [value];
|
|
922
|
+
const [firstClause, ...restClauses] = clauses;
|
|
923
|
+
const combined = restClauses.length === 0 && firstClause !== void 0 ? firstClause : { AND: clauses };
|
|
924
|
+
result.push({ not: await this.whereInputToSingle(combined, rootView) });
|
|
925
|
+
} else {
|
|
926
|
+
const filterValue = value;
|
|
927
|
+
const property = getPropertyRef(key, rootView);
|
|
928
|
+
const hasLeafFilter = isLeafFilter2(filterValue);
|
|
929
|
+
if (hasLeafFilter) {
|
|
930
|
+
result.push(...this.leafToFilterDefs(property, filterValue));
|
|
931
|
+
}
|
|
932
|
+
if ("search" in filterValue && filterValue.search != null) {
|
|
933
|
+
searchQueries[key] = filterValue.search;
|
|
934
|
+
} else if (!hasLeafFilter) {
|
|
935
|
+
const targetView = await this.getNestedTargetView(key, rootView);
|
|
936
|
+
const innerFilter = await this.whereInputToSingle(filterValue, targetView);
|
|
937
|
+
result.push({ nested: { scope: property, filter: innerFilter } });
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (Object.keys(searchQueries).length > 0) {
|
|
942
|
+
const searchFilters = await Promise.all(
|
|
943
|
+
Object.entries(searchQueries).map(
|
|
944
|
+
([key, filterValue]) => this.searchToFilterDef(key, filterValue, rootView)
|
|
945
|
+
)
|
|
946
|
+
);
|
|
947
|
+
result.push(...searchFilters);
|
|
948
|
+
}
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
async searchToFilterDef(propertyName, search, rootView) {
|
|
952
|
+
const response = await this.cognite.searchInstances({
|
|
953
|
+
view: toViewReference(rootView),
|
|
954
|
+
query: search.query,
|
|
955
|
+
instanceType: "node",
|
|
956
|
+
properties: [propertyName],
|
|
957
|
+
operator: search.operator ?? "OR",
|
|
958
|
+
limit: 1e3
|
|
959
|
+
});
|
|
960
|
+
const instanceRefs = response.items.map((item) => ({
|
|
961
|
+
space: item.space,
|
|
962
|
+
externalId: item.externalId
|
|
963
|
+
}));
|
|
964
|
+
return { instanceReferences: instanceRefs };
|
|
965
|
+
}
|
|
966
|
+
async whereInputToSingle(input, rootView) {
|
|
967
|
+
const filters = await this.map(input, rootView);
|
|
968
|
+
const [firstFilter, ...restFilters] = filters;
|
|
969
|
+
if (restFilters.length === 0 && firstFilter !== void 0) {
|
|
970
|
+
return firstFilter;
|
|
971
|
+
}
|
|
972
|
+
return { and: filters };
|
|
973
|
+
}
|
|
974
|
+
leafToFilterDefs(property, filter) {
|
|
975
|
+
const result = [];
|
|
976
|
+
if ("eq" in filter && filter.eq !== void 0) {
|
|
977
|
+
result.push({
|
|
978
|
+
equals: { property, value: this.coerceValue(filter.eq) }
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
if ("in" in filter && filter.in !== void 0) {
|
|
982
|
+
result.push({
|
|
983
|
+
in: { property, values: this.coerceValue(filter.in) }
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
if ("gt" in filter && filter.gt !== void 0) {
|
|
987
|
+
result.push({ range: { property, gt: this.coerceValue(filter.gt) } });
|
|
988
|
+
}
|
|
989
|
+
if ("gte" in filter && filter.gte !== void 0) {
|
|
990
|
+
result.push({ range: { property, gte: this.coerceValue(filter.gte) } });
|
|
991
|
+
}
|
|
992
|
+
if ("lt" in filter && filter.lt !== void 0) {
|
|
993
|
+
result.push({ range: { property, lt: this.coerceValue(filter.lt) } });
|
|
994
|
+
}
|
|
995
|
+
if ("lte" in filter && filter.lte !== void 0) {
|
|
996
|
+
result.push({ range: { property, lte: this.coerceValue(filter.lte) } });
|
|
997
|
+
}
|
|
998
|
+
if ("exists" in filter) {
|
|
999
|
+
if (filter.exists === true) {
|
|
1000
|
+
result.push({ exists: { property } });
|
|
1001
|
+
} else if (filter.exists === false) {
|
|
1002
|
+
result.push({ not: { exists: { property } } });
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if ("prefix" in filter && filter.prefix !== void 0) {
|
|
1006
|
+
result.push({ prefix: { property, value: this.coerceValue(filter.prefix) } });
|
|
1007
|
+
}
|
|
1008
|
+
if ("containsAll" in filter && filter.containsAll !== void 0) {
|
|
1009
|
+
result.push({
|
|
1010
|
+
containsAll: {
|
|
1011
|
+
property,
|
|
1012
|
+
values: this.coerceValue(filter.containsAll)
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
if ("containsAny" in filter && filter.containsAny !== void 0) {
|
|
1017
|
+
result.push({
|
|
1018
|
+
containsAny: {
|
|
1019
|
+
property,
|
|
1020
|
+
values: this.coerceValue(filter.containsAny)
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
return result;
|
|
1025
|
+
}
|
|
1026
|
+
async getNestedTargetView(property, rootView) {
|
|
1027
|
+
const viewProp = rootView.properties[property];
|
|
1028
|
+
if (!viewProp || !isViewPropertyDefinition(viewProp)) {
|
|
1029
|
+
throw new Error(`Property "${property}" is not a mapped property`);
|
|
1030
|
+
}
|
|
1031
|
+
const source = getDirectRelationSource(viewProp);
|
|
1032
|
+
if (!source) throw new Error(`Property "${property}" has no relation source`);
|
|
1033
|
+
return this.viewMapper.getView(source.externalId);
|
|
1034
|
+
}
|
|
1035
|
+
coerceValue(value) {
|
|
1036
|
+
if (value instanceof Date) return value.toISOString();
|
|
1037
|
+
if (Array.isArray(value)) return value.map((v) => this.coerceValue(v));
|
|
1038
|
+
return value;
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// src/mappers/aggregate-mapper.ts
|
|
1043
|
+
var AggregateMapper = class {
|
|
1044
|
+
constructor(viewMapper, cognite) {
|
|
1045
|
+
this.viewMapper = viewMapper;
|
|
1046
|
+
this.filterMapper = new FilterMapper(viewMapper, cognite);
|
|
1047
|
+
this.validator = new AggregateValidator(viewMapper);
|
|
1048
|
+
}
|
|
1049
|
+
async map(options) {
|
|
1050
|
+
const { viewExternalId, filters, groupBy, aggregate } = options;
|
|
1051
|
+
const rootView = await this.viewMapper.getView(viewExternalId);
|
|
1052
|
+
await this.validator.validate(options, rootView);
|
|
1053
|
+
const filterParts = filters ? await this.filterMapper.map(filters, rootView) : [];
|
|
1054
|
+
const filter = filterParts.length === 0 ? void 0 : filterParts.length === 1 ? filterParts[0] : { and: filterParts };
|
|
1055
|
+
return {
|
|
1056
|
+
view: toViewReference(rootView),
|
|
1057
|
+
instanceType: "node",
|
|
1058
|
+
limit: AGGREGATE_LIMIT,
|
|
1059
|
+
...filter !== void 0 ? { filter } : {},
|
|
1060
|
+
...groupBy ? { groupBy: getSelectedGroupByKeys(groupBy) } : {},
|
|
1061
|
+
...aggregate ? { aggregates: [mapAggregateDefinition(aggregate)] } : {}
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
function mapAggregateDefinition(aggregate) {
|
|
1066
|
+
if ("count" in aggregate) {
|
|
1067
|
+
const property = aggregate.count;
|
|
1068
|
+
if (property != null && typeof property === "object" && !Array.isArray(property) && Object.keys(property).length === 0) {
|
|
1069
|
+
return { count: {} };
|
|
1070
|
+
}
|
|
1071
|
+
if (typeof property === "string") {
|
|
1072
|
+
return { count: { property } };
|
|
1073
|
+
}
|
|
1074
|
+
return { count: {} };
|
|
1075
|
+
}
|
|
1076
|
+
if ("avg" in aggregate) {
|
|
1077
|
+
return { avg: { property: aggregate.avg } };
|
|
1078
|
+
}
|
|
1079
|
+
if ("min" in aggregate) {
|
|
1080
|
+
return { min: { property: aggregate.min } };
|
|
1081
|
+
}
|
|
1082
|
+
if ("max" in aggregate) {
|
|
1083
|
+
return { max: { property: aggregate.max } };
|
|
1084
|
+
}
|
|
1085
|
+
if ("sum" in aggregate) {
|
|
1086
|
+
return { sum: { property: aggregate.sum } };
|
|
1087
|
+
}
|
|
1088
|
+
throw new Error("Invalid aggregate definition");
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// src/mappers/aggregate-result-mapper.ts
|
|
1092
|
+
function isNodeId(value) {
|
|
1093
|
+
return value != null && typeof value === "object" && "space" in value && "externalId" in value && typeof value.space === "string" && typeof value.externalId === "string";
|
|
1094
|
+
}
|
|
1095
|
+
var AggregateResultMapper = class {
|
|
1096
|
+
map(response, options) {
|
|
1097
|
+
const groupByKeys = options.groupBy ? getSelectedGroupByKeys(options.groupBy) : [];
|
|
1098
|
+
return response.items.map((item) => {
|
|
1099
|
+
let group;
|
|
1100
|
+
if (item.group != null && groupByKeys.length > 0) {
|
|
1101
|
+
group = {};
|
|
1102
|
+
for (const key of groupByKeys) {
|
|
1103
|
+
const value = item.group[key];
|
|
1104
|
+
if (value === void 0) continue;
|
|
1105
|
+
group[key] = isNodeId(value) ? { space: value.space, externalId: value.externalId } : value;
|
|
1106
|
+
}
|
|
1107
|
+
if (Object.keys(group).length === 0) {
|
|
1108
|
+
group = void 0;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
const aggregateValue = item.aggregates[0];
|
|
1112
|
+
const aggregate = aggregateValue?.value !== void 0 ? aggregateValue.property != null ? { property: aggregateValue.property, value: aggregateValue.value } : { value: aggregateValue.value } : void 0;
|
|
1113
|
+
return {
|
|
1114
|
+
...group !== void 0 ? { group } : {},
|
|
1115
|
+
...aggregate !== void 0 ? { aggregate } : {}
|
|
1116
|
+
};
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
// src/mappers/sort-mapper.ts
|
|
1122
|
+
var SortMapper = class {
|
|
1123
|
+
map(sort, rootView) {
|
|
1124
|
+
return Object.entries(sort).map(([property, direction]) => ({
|
|
1125
|
+
property: getPropertyRef(property, rootView),
|
|
1126
|
+
direction,
|
|
1127
|
+
nullsFirst: this.isNullsFirst(property, rootView, direction)
|
|
1128
|
+
}));
|
|
1129
|
+
}
|
|
1130
|
+
isNullsFirst(property, view, direction) {
|
|
1131
|
+
const prop = view.properties[property];
|
|
1132
|
+
if (prop && isDirectRelationWithSource(prop)) {
|
|
1133
|
+
return direction === "ascending";
|
|
1134
|
+
}
|
|
1135
|
+
return direction === "descending";
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
// src/mappers/query-mapper.ts
|
|
1140
|
+
var QueryMapper = class {
|
|
1141
|
+
constructor(viewMapper, cognite) {
|
|
1142
|
+
this.viewMapper = viewMapper;
|
|
1143
|
+
this.filterMapper = new FilterMapper(viewMapper, cognite);
|
|
1144
|
+
this.sortMapper = new SortMapper();
|
|
1145
|
+
this.validator = new QueryValidator(viewMapper);
|
|
1146
|
+
}
|
|
1147
|
+
async map(options) {
|
|
1148
|
+
const {
|
|
1149
|
+
viewExternalId,
|
|
1150
|
+
select = { _all: true },
|
|
1151
|
+
filters,
|
|
1152
|
+
sort = {},
|
|
1153
|
+
limit: requestedLimit = DEFAULT_LIMIT,
|
|
1154
|
+
cursor = null
|
|
1155
|
+
} = options;
|
|
1156
|
+
const limit = requestedLimit === -1 ? DEFAULT_LIMIT : requestedLimit;
|
|
1157
|
+
const rootView = await this.viewMapper.getView(viewExternalId);
|
|
1158
|
+
await this.validator.validate(options, rootView);
|
|
1159
|
+
const rootViewRef = toViewReference(rootView);
|
|
1160
|
+
const whereFilters = filters ? await this.filterMapper.map(filters, rootView) : [];
|
|
1161
|
+
const baseFilters = [{ hasData: [rootViewRef] }, ...whereFilters];
|
|
1162
|
+
const withExprs = {
|
|
1163
|
+
[viewExternalId]: {
|
|
1164
|
+
nodes: {
|
|
1165
|
+
filter: { and: baseFilters }
|
|
1166
|
+
},
|
|
1167
|
+
sort: this.sortMapper.map(sort, rootView),
|
|
1168
|
+
limit
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
const selectExprs = {};
|
|
1172
|
+
const properties = await this.includeStatements(
|
|
1173
|
+
viewExternalId,
|
|
1174
|
+
rootView,
|
|
1175
|
+
select,
|
|
1176
|
+
withExprs,
|
|
1177
|
+
selectExprs
|
|
1178
|
+
);
|
|
1179
|
+
selectExprs[viewExternalId] = buildSelect(rootViewRef, properties);
|
|
1180
|
+
const cursors = {};
|
|
1181
|
+
if (cursor != null) cursors[viewExternalId] = cursor;
|
|
1182
|
+
return { with: withExprs, select: selectExprs, cursors };
|
|
1183
|
+
}
|
|
1184
|
+
async includeStatements(key, view, select, withExprs, selectExprs) {
|
|
1185
|
+
const selectProperties = [];
|
|
1186
|
+
const selectRecord = select;
|
|
1187
|
+
for (const [propertyName, property] of Object.entries(view.properties)) {
|
|
1188
|
+
const propertyKey = `${key}${NESTED_SEP}${propertyName}`;
|
|
1189
|
+
const canIncludeProperty = select._all === true || propertyName in select;
|
|
1190
|
+
if (!canIncludeProperty) {
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
const relationToInclude = propertyName in select && selectRecord[propertyName] != null && typeof selectRecord[propertyName] === "object" ? selectRecord[propertyName] : null;
|
|
1194
|
+
if (isViewPropertyDefinition(property)) {
|
|
1195
|
+
const relSource = getDirectRelationSource(property);
|
|
1196
|
+
if (!relSource) {
|
|
1197
|
+
selectProperties.push(propertyName);
|
|
1198
|
+
} else {
|
|
1199
|
+
selectProperties.push(propertyName);
|
|
1200
|
+
const nestedView = await this.viewMapper.getView(relSource.externalId);
|
|
1201
|
+
const props = relationToInclude != null ? await this.includeStatements(
|
|
1202
|
+
propertyKey,
|
|
1203
|
+
nestedView,
|
|
1204
|
+
relationToInclude,
|
|
1205
|
+
withExprs,
|
|
1206
|
+
selectExprs
|
|
1207
|
+
) : [];
|
|
1208
|
+
if (props.length > 0) {
|
|
1209
|
+
withExprs[propertyKey] = {
|
|
1210
|
+
nodes: {
|
|
1211
|
+
from: key,
|
|
1212
|
+
direction: "outwards",
|
|
1213
|
+
through: { view: toViewReference(view), identifier: propertyName }
|
|
1214
|
+
},
|
|
1215
|
+
limit: MAX_LIMIT
|
|
1216
|
+
};
|
|
1217
|
+
selectExprs[propertyKey] = buildSelect(relSource, props);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
} else if (isReverseDirectRelation(property) && relationToInclude != null) {
|
|
1221
|
+
const nestedView = await this.viewMapper.getView(property.source.externalId);
|
|
1222
|
+
const props = await this.includeStatements(
|
|
1223
|
+
propertyKey,
|
|
1224
|
+
nestedView,
|
|
1225
|
+
relationToInclude,
|
|
1226
|
+
withExprs,
|
|
1227
|
+
selectExprs
|
|
1228
|
+
);
|
|
1229
|
+
if (!props.includes(property.through.identifier)) {
|
|
1230
|
+
props.push(property.through.identifier);
|
|
1231
|
+
}
|
|
1232
|
+
withExprs[propertyKey] = {
|
|
1233
|
+
nodes: {
|
|
1234
|
+
from: key,
|
|
1235
|
+
direction: "inwards",
|
|
1236
|
+
through: {
|
|
1237
|
+
source: property.through.source,
|
|
1238
|
+
identifier: property.through.identifier
|
|
1239
|
+
}
|
|
1240
|
+
},
|
|
1241
|
+
limit: MAX_LIMIT
|
|
1242
|
+
};
|
|
1243
|
+
selectExprs[propertyKey] = buildSelect(property.source, props);
|
|
1244
|
+
} else if (isEdgeConnection(property) && relationToInclude != null) {
|
|
1245
|
+
const edgePropertyKey = `${propertyKey}${NESTED_SEP}${EDGE_MARKER}`;
|
|
1246
|
+
withExprs[edgePropertyKey] = {
|
|
1247
|
+
edges: {
|
|
1248
|
+
from: key,
|
|
1249
|
+
maxDistance: 1,
|
|
1250
|
+
filter: {
|
|
1251
|
+
equals: { property: ["edge", "type"], value: property.type }
|
|
1252
|
+
},
|
|
1253
|
+
direction: property.direction ?? "outwards"
|
|
1254
|
+
},
|
|
1255
|
+
limit: MAX_LIMIT
|
|
1256
|
+
};
|
|
1257
|
+
withExprs[propertyKey] = {
|
|
1258
|
+
nodes: { from: edgePropertyKey },
|
|
1259
|
+
limit: MAX_LIMIT
|
|
1260
|
+
};
|
|
1261
|
+
selectExprs[edgePropertyKey] = {};
|
|
1262
|
+
const nestedView = await this.viewMapper.getView(property.source.externalId);
|
|
1263
|
+
const props = await this.includeStatements(
|
|
1264
|
+
propertyKey,
|
|
1265
|
+
nestedView,
|
|
1266
|
+
relationToInclude,
|
|
1267
|
+
withExprs,
|
|
1268
|
+
selectExprs
|
|
1269
|
+
);
|
|
1270
|
+
selectExprs[propertyKey] = buildSelect(property.source, props);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return selectProperties;
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
// src/mappers/result-mapper.ts
|
|
1278
|
+
function nodeInstanceId(node) {
|
|
1279
|
+
return `${node.space}:${node.externalId}`;
|
|
1280
|
+
}
|
|
1281
|
+
function getElementKeys(node, element) {
|
|
1282
|
+
if (element && typeof element === "object" && !Array.isArray(element)) {
|
|
1283
|
+
const ref = element;
|
|
1284
|
+
return [`${ref.space ?? ""}:${ref.externalId ?? ""}`];
|
|
1285
|
+
}
|
|
1286
|
+
if (Array.isArray(element)) {
|
|
1287
|
+
return element.filter((item) => item && typeof item === "object").map((item) => {
|
|
1288
|
+
const ref = item;
|
|
1289
|
+
return `${ref.space ?? ""}:${ref.externalId ?? ""}`;
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
return [nodeInstanceId(node)];
|
|
1293
|
+
}
|
|
1294
|
+
var QueryResultMapper = class {
|
|
1295
|
+
constructor(viewMapper) {
|
|
1296
|
+
this.viewMapper = viewMapper;
|
|
1297
|
+
}
|
|
1298
|
+
async mapNodes(rootNode, queryResult) {
|
|
1299
|
+
if (!(rootNode in queryResult)) {
|
|
1300
|
+
throw new Error(`"${rootNode}" is not available in the query result`);
|
|
1301
|
+
}
|
|
1302
|
+
const rootView = await this.viewMapper.getView(rootNode);
|
|
1303
|
+
const values = await this.mapNodeProperty(rootNode, rootView, queryResult);
|
|
1304
|
+
if (!values) return [];
|
|
1305
|
+
return [...values.values()].flatMap((nodes) => nodes.map((n) => this.nodeToDict(n)));
|
|
1306
|
+
}
|
|
1307
|
+
async mapNodeProperty(key, view, queryResult, resultPropertyKey) {
|
|
1308
|
+
if (!(key in queryResult)) return null;
|
|
1309
|
+
const mappings = await this.getPropertyMappings(key, view, queryResult);
|
|
1310
|
+
const viewKey = `${view.externalId}/${view.version}`;
|
|
1311
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1312
|
+
const result = /* @__PURE__ */ new Map();
|
|
1313
|
+
for (const item of queryResult[key] ?? []) {
|
|
1314
|
+
if (item.instanceType !== "node") continue;
|
|
1315
|
+
const node = item;
|
|
1316
|
+
const id = nodeInstanceId(node);
|
|
1317
|
+
if (visited.has(id)) continue;
|
|
1318
|
+
visited.add(id);
|
|
1319
|
+
const spaceProps = node.properties?.[view.space];
|
|
1320
|
+
if (!spaceProps || !(viewKey in spaceProps)) continue;
|
|
1321
|
+
const properties = { ...spaceProps[viewKey] ?? {} };
|
|
1322
|
+
const getResultId = () => {
|
|
1323
|
+
if (!resultPropertyKey) return id;
|
|
1324
|
+
const entry = properties[resultPropertyKey];
|
|
1325
|
+
if (!entry || typeof entry !== "object") {
|
|
1326
|
+
throw new Error(`Invalid result property key "${resultPropertyKey}"`);
|
|
1327
|
+
}
|
|
1328
|
+
const ref = entry;
|
|
1329
|
+
return `${ref.space ?? ""}:${ref.externalId ?? ""}`;
|
|
1330
|
+
};
|
|
1331
|
+
const edgesMapping = {};
|
|
1332
|
+
const resultId = getResultId();
|
|
1333
|
+
for (const [mappingKey, mapping] of Object.entries(mappings)) {
|
|
1334
|
+
const element = properties[mappingKey];
|
|
1335
|
+
const { isList, connectionType, nodes: mappingNodes, edges: mappingEdges } = mapping;
|
|
1336
|
+
if (element === void 0 && connectionType === "DirectRelation") continue;
|
|
1337
|
+
const elementKeys = getElementKeys(node, element);
|
|
1338
|
+
const nodeEntries = elementKeys.flatMap((k) => mappingNodes.get(k) ?? []);
|
|
1339
|
+
if (nodeEntries.length === 0) {
|
|
1340
|
+
delete properties[mappingKey];
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
const entryData = nodeEntries.map((n) => this.nodeToDict(n));
|
|
1344
|
+
properties[mappingKey] = isList ? entryData : entryData[0];
|
|
1345
|
+
const edgeEntries = elementKeys.flatMap((k) => mappingEdges.get(k) ?? []);
|
|
1346
|
+
if (edgeEntries.length > 0) edgesMapping[mappingKey] = edgeEntries;
|
|
1347
|
+
}
|
|
1348
|
+
properties._edges = edgesMapping;
|
|
1349
|
+
if (node.properties?.[view.space]) {
|
|
1350
|
+
node.properties[view.space] = {
|
|
1351
|
+
...node.properties[view.space],
|
|
1352
|
+
[viewKey]: properties
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
const existing = result.get(resultId);
|
|
1356
|
+
if (existing) {
|
|
1357
|
+
existing.push(node);
|
|
1358
|
+
} else {
|
|
1359
|
+
result.set(resultId, [node]);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
return result;
|
|
1363
|
+
}
|
|
1364
|
+
async getPropertyMappings(key, view, queryResult) {
|
|
1365
|
+
const mappings = {};
|
|
1366
|
+
for (const [propertyName, property] of Object.entries(view.properties)) {
|
|
1367
|
+
const propertyKey = `${key}${NESTED_SEP}${propertyName}`;
|
|
1368
|
+
let nodes = null;
|
|
1369
|
+
let edges = /* @__PURE__ */ new Map();
|
|
1370
|
+
let isList = false;
|
|
1371
|
+
let connectionType = "DirectRelation";
|
|
1372
|
+
if (isViewPropertyDefinition(property)) {
|
|
1373
|
+
const source = getDirectRelationSource(property);
|
|
1374
|
+
if (source) {
|
|
1375
|
+
const nestedView = await this.viewMapper.getView(source.externalId);
|
|
1376
|
+
nodes = await this.mapNodeProperty(propertyKey, nestedView, queryResult);
|
|
1377
|
+
isList = isListDirectRelation(property);
|
|
1378
|
+
connectionType = "DirectRelation";
|
|
1379
|
+
}
|
|
1380
|
+
} else if (isReverseDirectRelation(property)) {
|
|
1381
|
+
const rel = property;
|
|
1382
|
+
const nestedView = await this.viewMapper.getView(rel.source.externalId);
|
|
1383
|
+
nodes = await this.mapNodeProperty(
|
|
1384
|
+
propertyKey,
|
|
1385
|
+
nestedView,
|
|
1386
|
+
queryResult,
|
|
1387
|
+
rel.through.identifier
|
|
1388
|
+
);
|
|
1389
|
+
isList = rel.connectionType === "multi_reverse_direct_relation" || rel.targetsList === true;
|
|
1390
|
+
connectionType = "ReverseDirectRelation";
|
|
1391
|
+
} else if (isEdgeConnection(property)) {
|
|
1392
|
+
const nestedView = await this.viewMapper.getView(property.source.externalId);
|
|
1393
|
+
const [edgeNodes, edgeEdges] = await this.mapEdgeProperty(
|
|
1394
|
+
propertyKey,
|
|
1395
|
+
nestedView,
|
|
1396
|
+
queryResult,
|
|
1397
|
+
property.direction ?? "outwards"
|
|
1398
|
+
);
|
|
1399
|
+
nodes = edgeNodes;
|
|
1400
|
+
edges = edgeEdges ?? /* @__PURE__ */ new Map();
|
|
1401
|
+
isList = true;
|
|
1402
|
+
connectionType = "Edge";
|
|
1403
|
+
}
|
|
1404
|
+
if (nodes !== null) {
|
|
1405
|
+
mappings[propertyName] = { isList, connectionType, nodes, edges };
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
return mappings;
|
|
1409
|
+
}
|
|
1410
|
+
async mapEdgeProperty(key, view, queryResult, edgeDirection) {
|
|
1411
|
+
const edgeKey = `${key}${NESTED_SEP}${EDGE_MARKER}`;
|
|
1412
|
+
if (!(key in queryResult) || !(edgeKey in queryResult)) return [null, null];
|
|
1413
|
+
const nodes = await this.mapNodeProperty(key, view, queryResult);
|
|
1414
|
+
if (!nodes) return [null, null];
|
|
1415
|
+
const visitedEdges = /* @__PURE__ */ new Set();
|
|
1416
|
+
const nodesResult = /* @__PURE__ */ new Map();
|
|
1417
|
+
const edgesResult = /* @__PURE__ */ new Map();
|
|
1418
|
+
for (const item of queryResult[edgeKey] ?? []) {
|
|
1419
|
+
if (item.instanceType !== "edge") continue;
|
|
1420
|
+
const edge = item;
|
|
1421
|
+
const edgeId = `${edge.space}:${edge.externalId}`;
|
|
1422
|
+
if (visitedEdges.has(edgeId)) continue;
|
|
1423
|
+
visitedEdges.add(edgeId);
|
|
1424
|
+
const entryKey = edgeDirection === "inwards" ? `${edge.endNode.space}:${edge.endNode.externalId}` : `${edge.startNode.space}:${edge.startNode.externalId}`;
|
|
1425
|
+
const nodeKey = edgeDirection === "inwards" ? `${edge.startNode.space}:${edge.startNode.externalId}` : `${edge.endNode.space}:${edge.endNode.externalId}`;
|
|
1426
|
+
const existingEdges = edgesResult.get(entryKey);
|
|
1427
|
+
if (existingEdges) existingEdges.push(edge);
|
|
1428
|
+
else edgesResult.set(entryKey, [edge]);
|
|
1429
|
+
const relatedNodes = nodes.get(nodeKey);
|
|
1430
|
+
if (relatedNodes) {
|
|
1431
|
+
const existingNodes = nodesResult.get(entryKey);
|
|
1432
|
+
if (existingNodes) existingNodes.push(...relatedNodes);
|
|
1433
|
+
else nodesResult.set(entryKey, relatedNodes.slice());
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return [nodesResult, edgesResult];
|
|
1437
|
+
}
|
|
1438
|
+
nodeToDict(node) {
|
|
1439
|
+
const { properties, ...rest } = node;
|
|
1440
|
+
const entry = { ...rest };
|
|
1441
|
+
for (const spaceProp of Object.values(properties ?? {})) {
|
|
1442
|
+
for (const viewProp of Object.values(spaceProp)) {
|
|
1443
|
+
Object.assign(entry, viewProp);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return entry;
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1450
|
+
// src/mappers/upsert-mapper.ts
|
|
1451
|
+
var IDENTITY_KEYS = /* @__PURE__ */ new Set(["space", "externalId"]);
|
|
1452
|
+
var EDGE_QUERY_LIMIT = 1e3;
|
|
1453
|
+
var UpsertMapper = class {
|
|
1454
|
+
constructor(viewMapper, cognite) {
|
|
1455
|
+
this.viewMapper = viewMapper;
|
|
1456
|
+
this.cognite = cognite;
|
|
1457
|
+
this.validator = new UpsertValidator();
|
|
1458
|
+
}
|
|
1459
|
+
async map(options) {
|
|
1460
|
+
const rootView = await this.viewMapper.getView(options.viewExternalId);
|
|
1461
|
+
this.validator.validate(options, rootView);
|
|
1462
|
+
const edgeMode = options.edgeMode ?? "append";
|
|
1463
|
+
const mappedItems = options.items.map(
|
|
1464
|
+
(item) => this.mapItem(item, rootView, options.onEdgeCreation, edgeMode)
|
|
1465
|
+
);
|
|
1466
|
+
const items = mappedItems.flatMap((item) => item.writes);
|
|
1467
|
+
const edgeReplacements = mappedItems.flatMap((item) => item.edgeReplacements);
|
|
1468
|
+
const deleteItems = edgeMode === "replace" ? await this.mapEdgeReplacementDeletes(edgeReplacements) : [];
|
|
1469
|
+
return {
|
|
1470
|
+
items,
|
|
1471
|
+
...deleteItems.length > 0 ? { delete: deleteItems } : {},
|
|
1472
|
+
...options.replace === true ? { replace: true } : {}
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
mapItem(item, rootView, onEdgeCreation, edgeMode) {
|
|
1476
|
+
const node = { space: item.space, externalId: item.externalId };
|
|
1477
|
+
const nodeProperties = {};
|
|
1478
|
+
const inferredItems = [];
|
|
1479
|
+
const edgeReplacements = [];
|
|
1480
|
+
for (const [name, value] of Object.entries(item)) {
|
|
1481
|
+
if (IDENTITY_KEYS.has(name)) continue;
|
|
1482
|
+
const property = rootView.properties[name];
|
|
1483
|
+
if (!property) continue;
|
|
1484
|
+
if (isViewPropertyDefinition(property)) {
|
|
1485
|
+
nodeProperties[name] = normalizeViewPropertyValue(value, property);
|
|
1486
|
+
} else if (isReverseDirectRelation(property)) {
|
|
1487
|
+
inferredItems.push(...this.mapReverseDirectRelation(node, value, property));
|
|
1488
|
+
} else if (isEdgeConnection(property)) {
|
|
1489
|
+
const desiredEdges = this.mapEdgeConnection(node, name, value, property, onEdgeCreation);
|
|
1490
|
+
inferredItems.push(...desiredEdges);
|
|
1491
|
+
if (edgeMode === "replace") {
|
|
1492
|
+
edgeReplacements.push({ rootNode: node, propertyName: name, property, desiredEdges });
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
const applyNode = {
|
|
1497
|
+
instanceType: "node",
|
|
1498
|
+
...node
|
|
1499
|
+
};
|
|
1500
|
+
if (Object.keys(nodeProperties).length > 0) {
|
|
1501
|
+
applyNode.sources = [{ source: toViewReference(rootView), properties: nodeProperties }];
|
|
1502
|
+
}
|
|
1503
|
+
return { writes: [applyNode, ...inferredItems], edgeReplacements };
|
|
1504
|
+
}
|
|
1505
|
+
async mapEdgeReplacementDeletes(replacements) {
|
|
1506
|
+
const deletes = await Promise.all(
|
|
1507
|
+
replacements.map(async (replacement) => {
|
|
1508
|
+
const existingEdges = await this.queryExistingEdges(replacement);
|
|
1509
|
+
const desiredEdgeKeys = new Set(replacement.desiredEdges.map((edge) => instanceKey(edge)));
|
|
1510
|
+
return existingEdges.filter((edge) => !desiredEdgeKeys.has(instanceKey(edge))).map((edge) => ({
|
|
1511
|
+
instanceType: "edge",
|
|
1512
|
+
space: edge.space,
|
|
1513
|
+
externalId: edge.externalId
|
|
1514
|
+
}));
|
|
1515
|
+
})
|
|
1516
|
+
);
|
|
1517
|
+
return uniqueDeletes(deletes.flat());
|
|
1518
|
+
}
|
|
1519
|
+
async queryExistingEdges(replacement) {
|
|
1520
|
+
const rootKey = `${replacement.propertyName}Root`;
|
|
1521
|
+
const edgeKey = `${replacement.propertyName}Edges`;
|
|
1522
|
+
const direction = replacement.property.direction ?? "outwards";
|
|
1523
|
+
const query = {
|
|
1524
|
+
with: {
|
|
1525
|
+
[rootKey]: {
|
|
1526
|
+
nodes: {
|
|
1527
|
+
filter: {
|
|
1528
|
+
instanceReferences: [replacement.rootNode]
|
|
1529
|
+
}
|
|
1530
|
+
},
|
|
1531
|
+
limit: 1
|
|
1532
|
+
},
|
|
1533
|
+
[edgeKey]: {
|
|
1534
|
+
edges: {
|
|
1535
|
+
from: rootKey,
|
|
1536
|
+
maxDistance: 1,
|
|
1537
|
+
direction,
|
|
1538
|
+
filter: {
|
|
1539
|
+
equals: { property: ["edge", "type"], value: replacement.property.type }
|
|
1540
|
+
}
|
|
1541
|
+
},
|
|
1542
|
+
limit: EDGE_QUERY_LIMIT
|
|
1543
|
+
}
|
|
1544
|
+
},
|
|
1545
|
+
select: {
|
|
1546
|
+
[rootKey]: {},
|
|
1547
|
+
[edgeKey]: {}
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
const edges = [];
|
|
1551
|
+
let cursor;
|
|
1552
|
+
do {
|
|
1553
|
+
const response = await this.cognite.queryInstances({
|
|
1554
|
+
...query,
|
|
1555
|
+
...cursor ? { cursors: { [edgeKey]: cursor } } : {}
|
|
1556
|
+
});
|
|
1557
|
+
edges.push(
|
|
1558
|
+
...(response.items[edgeKey] ?? []).filter(
|
|
1559
|
+
(item) => item.instanceType === "edge"
|
|
1560
|
+
)
|
|
1561
|
+
);
|
|
1562
|
+
cursor = response.nextCursor[edgeKey];
|
|
1563
|
+
} while (cursor);
|
|
1564
|
+
return edges;
|
|
1565
|
+
}
|
|
1566
|
+
mapReverseDirectRelation(node, value, property) {
|
|
1567
|
+
const targets = asNodeIdArray(value);
|
|
1568
|
+
return targets.map((target) => ({
|
|
1569
|
+
instanceType: "node",
|
|
1570
|
+
...target,
|
|
1571
|
+
sources: [
|
|
1572
|
+
{
|
|
1573
|
+
source: property.through.source,
|
|
1574
|
+
properties: {
|
|
1575
|
+
[property.through.identifier]: normalizeReverseDirectRelationValue(node, property)
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
]
|
|
1579
|
+
}));
|
|
1580
|
+
}
|
|
1581
|
+
mapEdgeConnection(node, propertyName, value, property, onEdgeCreation) {
|
|
1582
|
+
const direction = property.direction ?? "outwards";
|
|
1583
|
+
return asNodeIdArray(value).map((target) => {
|
|
1584
|
+
const startNode = direction === "inwards" ? target : node;
|
|
1585
|
+
const endNode = direction === "inwards" ? node : target;
|
|
1586
|
+
const edgeType = toNodeId(property.type, `edge type for "${propertyName}"`);
|
|
1587
|
+
const createEdgeId = onEdgeCreation?.[propertyName];
|
|
1588
|
+
if (!createEdgeId) {
|
|
1589
|
+
throw new Error(
|
|
1590
|
+
`Invalid upsert options:
|
|
1591
|
+
- onEdgeCreation.${propertyName}: required when ingesting edge connection "${propertyName}"`
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
const edgeId = createEdgeId({
|
|
1595
|
+
startNode,
|
|
1596
|
+
endNode,
|
|
1597
|
+
edgeType
|
|
1598
|
+
});
|
|
1599
|
+
assertNodeId(edgeId, `onEdgeCreation(${propertyName})`);
|
|
1600
|
+
return {
|
|
1601
|
+
instanceType: "edge",
|
|
1602
|
+
...edgeId,
|
|
1603
|
+
type: property.type,
|
|
1604
|
+
startNode,
|
|
1605
|
+
endNode
|
|
1606
|
+
};
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
function normalizeReverseDirectRelationValue(node, property) {
|
|
1611
|
+
return property.targetsList === true ? [node] : node;
|
|
1612
|
+
}
|
|
1613
|
+
function normalizeViewPropertyValue(value, property) {
|
|
1614
|
+
if (property.type.type !== "direct") return normalizePropertyValue(value);
|
|
1615
|
+
if (Array.isArray(value)) return value.map((item) => toNodeId(item));
|
|
1616
|
+
return toNodeId(value);
|
|
1617
|
+
}
|
|
1618
|
+
function normalizePropertyValue(value) {
|
|
1619
|
+
if (value instanceof Date) return value.toISOString();
|
|
1620
|
+
if (Array.isArray(value)) return value.map(normalizePropertyValue);
|
|
1621
|
+
if (isPlainObject(value)) {
|
|
1622
|
+
return Object.fromEntries(
|
|
1623
|
+
Object.entries(value).map(([key, nestedValue]) => [key, normalizePropertyValue(nestedValue)])
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
return value;
|
|
1627
|
+
}
|
|
1628
|
+
function asNodeIdArray(value) {
|
|
1629
|
+
return Array.isArray(value) ? value.map((item) => toNodeId(item)) : [toNodeId(value)];
|
|
1630
|
+
}
|
|
1631
|
+
function toNodeId(value, label = "relation reference") {
|
|
1632
|
+
if (!isPlainObject(value) || typeof value.space !== "string" || typeof value.externalId !== "string") {
|
|
1633
|
+
throw new Error(`Invalid upsert options:
|
|
1634
|
+
- ${label}: expected a NodeId`);
|
|
1635
|
+
}
|
|
1636
|
+
return { space: value.space, externalId: value.externalId };
|
|
1637
|
+
}
|
|
1638
|
+
function instanceKey(instance) {
|
|
1639
|
+
return `${instance.space}\0${instance.externalId}`;
|
|
1640
|
+
}
|
|
1641
|
+
function uniqueDeletes(deletes) {
|
|
1642
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1643
|
+
return deletes.filter((item) => {
|
|
1644
|
+
const key = instanceKey(item);
|
|
1645
|
+
if (seen.has(key)) return false;
|
|
1646
|
+
seen.add(key);
|
|
1647
|
+
return true;
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
function assertNodeId(value, label) {
|
|
1651
|
+
if (!isPlainObject(value) || typeof value.space !== "string" || typeof value.externalId !== "string") {
|
|
1652
|
+
throw new Error(`Invalid upsert options:
|
|
1653
|
+
- ${label}: expected a NodeId`);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
function isPlainObject(value) {
|
|
1657
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// src/mappers/view-mapper.ts
|
|
1661
|
+
var ViewMapper = class {
|
|
1662
|
+
constructor(cognite, dataModelId) {
|
|
1663
|
+
this.cognite = cognite;
|
|
1664
|
+
this.dataModelId = dataModelId;
|
|
1665
|
+
this.cachePromise = null;
|
|
1666
|
+
}
|
|
1667
|
+
async getView(externalId) {
|
|
1668
|
+
const views = await this.loadViews();
|
|
1669
|
+
const view = views.get(externalId);
|
|
1670
|
+
if (!view) {
|
|
1671
|
+
throw new Error(
|
|
1672
|
+
`View "${externalId}" not found in data model "${this.dataModelId.externalId}"`
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
return view;
|
|
1676
|
+
}
|
|
1677
|
+
loadViews() {
|
|
1678
|
+
if (this.cachePromise == null) {
|
|
1679
|
+
this.cachePromise = this.fetchViews();
|
|
1680
|
+
}
|
|
1681
|
+
return this.cachePromise;
|
|
1682
|
+
}
|
|
1683
|
+
async fetchViews() {
|
|
1684
|
+
const response = await this.cognite.retrieveDataModels(
|
|
1685
|
+
[
|
|
1686
|
+
{
|
|
1687
|
+
space: this.dataModelId.space,
|
|
1688
|
+
externalId: this.dataModelId.externalId,
|
|
1689
|
+
version: this.dataModelId.version
|
|
1690
|
+
}
|
|
1691
|
+
],
|
|
1692
|
+
{ inlineViews: true }
|
|
1693
|
+
);
|
|
1694
|
+
const dm = response.items.sort((a, b) => b.createdTime - a.createdTime)[0];
|
|
1695
|
+
if (!dm) {
|
|
1696
|
+
throw new Error(`Data model "${this.dataModelId.externalId}" not found`);
|
|
1697
|
+
}
|
|
1698
|
+
const views = /* @__PURE__ */ new Map();
|
|
1699
|
+
for (const view of dm.views ?? []) {
|
|
1700
|
+
views.set(view.externalId, view);
|
|
1701
|
+
}
|
|
1702
|
+
return views;
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
// src/client.ts
|
|
1707
|
+
var APPLY_ITEM_LIMIT = 1e3;
|
|
1708
|
+
var IndustrialModelClient = class {
|
|
1709
|
+
constructor(client, dataModelId, options = {}) {
|
|
1710
|
+
const cognite = createCogniteAdapter(client);
|
|
1711
|
+
this.cognite = cognite;
|
|
1712
|
+
const viewMapper = new ViewMapper(cognite, dataModelId);
|
|
1713
|
+
this.queryMapper = new QueryMapper(viewMapper, cognite);
|
|
1714
|
+
this.aggregateMapper = new AggregateMapper(viewMapper, cognite);
|
|
1715
|
+
this.upsertMapper = new UpsertMapper(viewMapper, cognite);
|
|
1716
|
+
this.aggregateResultMapper = new AggregateResultMapper();
|
|
1717
|
+
this.resultMapper = new QueryResultMapper(viewMapper);
|
|
1718
|
+
this.resultValidator = new QueryResultValidator(viewMapper);
|
|
1719
|
+
this.validateResults = options.validateResults ?? false;
|
|
1720
|
+
}
|
|
1721
|
+
query() {
|
|
1722
|
+
const execute = (options) => this.queryInternal(options);
|
|
1723
|
+
return execute;
|
|
1724
|
+
}
|
|
1725
|
+
aggregate() {
|
|
1726
|
+
const execute = (options) => this.aggregateInternal(options);
|
|
1727
|
+
return execute;
|
|
1728
|
+
}
|
|
1729
|
+
upsert() {
|
|
1730
|
+
const execute = (options) => this.upsertInternal(options);
|
|
1731
|
+
return execute;
|
|
1732
|
+
}
|
|
1733
|
+
async delete(items) {
|
|
1734
|
+
const deleteItems = items.map((item) => {
|
|
1735
|
+
assertNodeId2(item);
|
|
1736
|
+
return {
|
|
1737
|
+
instanceType: "node",
|
|
1738
|
+
space: item.space,
|
|
1739
|
+
externalId: item.externalId
|
|
1740
|
+
};
|
|
1741
|
+
});
|
|
1742
|
+
const response = await this.applyInstancesInChunks({
|
|
1743
|
+
items: [],
|
|
1744
|
+
delete: deleteItems
|
|
1745
|
+
});
|
|
1746
|
+
return { items: response.items };
|
|
1747
|
+
}
|
|
1748
|
+
async upsertInternal(options) {
|
|
1749
|
+
const cogniteRequest = await this.upsertMapper.map(options);
|
|
1750
|
+
const response = await this.applyInstancesInChunks(cogniteRequest);
|
|
1751
|
+
return { items: response.items };
|
|
1752
|
+
}
|
|
1753
|
+
async applyInstancesInChunks(request) {
|
|
1754
|
+
const deleteItems = request.delete ?? [];
|
|
1755
|
+
const totalItems = request.items.length + deleteItems.length;
|
|
1756
|
+
if (totalItems === 0) return { items: [] };
|
|
1757
|
+
if (totalItems <= APPLY_ITEM_LIMIT) {
|
|
1758
|
+
return this.cognite.applyInstances(request);
|
|
1759
|
+
}
|
|
1760
|
+
const responses = [];
|
|
1761
|
+
for (const deleteChunk of chunks(deleteItems, APPLY_ITEM_LIMIT)) {
|
|
1762
|
+
const response = await this.cognite.applyInstances({
|
|
1763
|
+
items: [],
|
|
1764
|
+
delete: deleteChunk
|
|
1765
|
+
});
|
|
1766
|
+
responses.push(...response.items);
|
|
1767
|
+
}
|
|
1768
|
+
for (const itemChunk of chunks(request.items, APPLY_ITEM_LIMIT)) {
|
|
1769
|
+
const response = await this.cognite.applyInstances({
|
|
1770
|
+
items: itemChunk,
|
|
1771
|
+
...request.replace === true ? { replace: true } : {}
|
|
1772
|
+
});
|
|
1773
|
+
responses.push(...response.items);
|
|
1774
|
+
}
|
|
1775
|
+
return { items: responses };
|
|
1776
|
+
}
|
|
1777
|
+
async aggregateInternal(options) {
|
|
1778
|
+
const cogniteRequest = await this.aggregateMapper.map(options);
|
|
1779
|
+
const response = await this.cognite.aggregateInstances(cogniteRequest);
|
|
1780
|
+
const items = this.aggregateResultMapper.map(
|
|
1781
|
+
response,
|
|
1782
|
+
options
|
|
1783
|
+
);
|
|
1784
|
+
return { items };
|
|
1785
|
+
}
|
|
1786
|
+
async queryInternal(options) {
|
|
1787
|
+
const { viewExternalId, limit = DEFAULT_LIMIT } = options;
|
|
1788
|
+
const allPages = options.limit === -1;
|
|
1789
|
+
const cogniteQuery = await this.queryMapper.map(options);
|
|
1790
|
+
const data = [];
|
|
1791
|
+
while (true) {
|
|
1792
|
+
const queryResult = await this.cognite.queryInstances(cogniteQuery);
|
|
1793
|
+
const dependenciesData = await this.queryDependenciesPages(
|
|
1794
|
+
cogniteQuery,
|
|
1795
|
+
queryResult,
|
|
1796
|
+
viewExternalId
|
|
1797
|
+
);
|
|
1798
|
+
const queryResultData = appendNodesAndEdges(
|
|
1799
|
+
mapNodesAndEdges(queryResult),
|
|
1800
|
+
dependenciesData
|
|
1801
|
+
);
|
|
1802
|
+
const mappedPageResult = await this.resultMapper.mapNodes(viewExternalId, queryResultData);
|
|
1803
|
+
const pageResult = this.validateResults ? await this.resultValidator.parseItems(viewExternalId, mappedPageResult, options.select) : mappedPageResult;
|
|
1804
|
+
const nextCursor = queryResult.nextCursor[viewExternalId] ?? null;
|
|
1805
|
+
const isLastPage = pageResult.length < limit || !nextCursor;
|
|
1806
|
+
const resolvedCursor = isLastPage ? null : nextCursor;
|
|
1807
|
+
data.push(...pageResult);
|
|
1808
|
+
if (!isLastPage && resolvedCursor !== null) {
|
|
1809
|
+
cogniteQuery.cursors = { [viewExternalId]: resolvedCursor };
|
|
1810
|
+
}
|
|
1811
|
+
if (!allPages || isLastPage) {
|
|
1812
|
+
return { items: data, cursor: resolvedCursor };
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
async queryDependenciesPages(cogniteQuery, queryResult, viewExternalId, remainingDepth = MAX_DEPENDENCY_DEPTH) {
|
|
1817
|
+
if (remainingDepth <= 0) {
|
|
1818
|
+
return null;
|
|
1819
|
+
}
|
|
1820
|
+
const newQuery = getQueryForDependenciesPagination(cogniteQuery, queryResult, viewExternalId);
|
|
1821
|
+
if (!newQuery) return null;
|
|
1822
|
+
const newQueryResult = await this.cognite.queryInstances(newQuery);
|
|
1823
|
+
const result = mapNodesAndEdges(newQueryResult);
|
|
1824
|
+
const nestedResults = await this.queryDependenciesPages(
|
|
1825
|
+
newQuery,
|
|
1826
|
+
newQueryResult,
|
|
1827
|
+
viewExternalId,
|
|
1828
|
+
remainingDepth - 1
|
|
1829
|
+
);
|
|
1830
|
+
return appendNodesAndEdges(result, nestedResults);
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
function chunks(items, size) {
|
|
1834
|
+
const result = [];
|
|
1835
|
+
for (let index = 0; index < items.length; index += size) {
|
|
1836
|
+
result.push(items.slice(index, index + size));
|
|
1837
|
+
}
|
|
1838
|
+
return result;
|
|
1839
|
+
}
|
|
1840
|
+
function assertNodeId2(value) {
|
|
1841
|
+
if (value == null || typeof value !== "object" || Array.isArray(value) || typeof value.space !== "string" || value.space?.length === 0 || typeof value.externalId !== "string" || value.externalId?.length === 0) {
|
|
1842
|
+
throw new Error("Invalid delete options:\n- items: expected NodeId values");
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// src/cognite-core/client.ts
|
|
1847
|
+
var COGNITE_CORE_DATA_MODEL = {
|
|
1848
|
+
space: "cdf_cdm",
|
|
1849
|
+
externalId: "CogniteCore",
|
|
1850
|
+
version: "v1"
|
|
1851
|
+
};
|
|
1852
|
+
var CogniteCoreClient = class {
|
|
1853
|
+
constructor(client, options = {}) {
|
|
1854
|
+
this.model = new IndustrialModelClient(client, COGNITE_CORE_DATA_MODEL, options);
|
|
1855
|
+
}
|
|
1856
|
+
query(viewExternalId) {
|
|
1857
|
+
const query = this.model.query();
|
|
1858
|
+
const queryWithView = query;
|
|
1859
|
+
const execute = (options = {}) => queryWithView({ ...options, viewExternalId });
|
|
1860
|
+
return execute;
|
|
1861
|
+
}
|
|
1862
|
+
aggregate(viewExternalId) {
|
|
1863
|
+
const aggregate = this.model.aggregate();
|
|
1864
|
+
const execute = (options = {}) => aggregate({ ...options, viewExternalId });
|
|
1865
|
+
return execute;
|
|
1866
|
+
}
|
|
1867
|
+
upsert(viewExternalId) {
|
|
1868
|
+
const upsert = this.model.upsert();
|
|
1869
|
+
const execute = (options) => upsert({ ...options, viewExternalId });
|
|
1870
|
+
return execute;
|
|
1871
|
+
}
|
|
1872
|
+
delete(items) {
|
|
1873
|
+
return this.model.delete(items);
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
|
|
1877
|
+
export { COGNITE_CORE_DATA_MODEL, CogniteCoreClient };
|
|
1878
|
+
//# sourceMappingURL=index.js.map
|
|
1879
|
+
//# sourceMappingURL=index.js.map
|