industrial-model 0.2.0 → 0.3.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 +13 -1
- package/dist/index.cjs +438 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -5
- package/dist/index.d.ts +67 -5
- package/dist/index.js +437 -4
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -672,17 +672,29 @@ if (cursor) {
|
|
|
672
672
|
| `NodeId`, `DataModelId` | Instance and data-model identifiers |
|
|
673
673
|
| `QueryOptions`, `QuerySelect`, `WhereInput`, `SortInput` | Query input types |
|
|
674
674
|
| `QueryResult`, `QueryResultItem`, `QueryResultMetadata` | Query output types |
|
|
675
|
+
| `buildViewSchema`, `nodeIdSchema` | Zod schemas built from Cognite view metadata |
|
|
675
676
|
| `SortDirection` | `"ascending"` \| `"descending"` |
|
|
676
677
|
|
|
677
|
-
### `new IndustrialModelClient(client, dataModelId)`
|
|
678
|
+
### `new IndustrialModelClient(client, dataModelId, options?)`
|
|
678
679
|
|
|
679
680
|
| Parameter | Type | Description |
|
|
680
681
|
|-----------|------|-------------|
|
|
681
682
|
| `client` | `CogniteClient` | Authenticated Cognite SDK client |
|
|
682
683
|
| `dataModelId` | `DataModelId` | Space, externalId, and version of the data model |
|
|
684
|
+
| `options.validateResults` | `boolean` | Optional. Validate and parse query results with Zod schemas derived from Cognite view metadata |
|
|
683
685
|
|
|
684
686
|
On the first query, view definitions are loaded from CDF and cached for the lifetime of the client instance.
|
|
685
687
|
|
|
688
|
+
Query inputs are validated against the loaded view metadata before the Cognite request is built. Result validation is opt-in because it parses every returned item:
|
|
689
|
+
|
|
690
|
+
```ts
|
|
691
|
+
const model = new IndustrialModelClient(client, dataModelId, {
|
|
692
|
+
validateResults: true,
|
|
693
|
+
});
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
When `validateResults` is enabled, Cognite `date` and `timestamp` view properties are converted to JavaScript `Date` objects. Without this option, result values are returned as Cognite provides them, usually ISO strings for timestamps.
|
|
697
|
+
|
|
686
698
|
### `model.query<TModel>()(options)`
|
|
687
699
|
|
|
688
700
|
| Option | Type | Description |
|
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);
|
|
@@ -204,6 +206,345 @@ var FilterMapper = class {
|
|
|
204
206
|
return value;
|
|
205
207
|
}
|
|
206
208
|
};
|
|
209
|
+
var nodeIdSchema = zod.z.object({
|
|
210
|
+
space: zod.z.string().min(1),
|
|
211
|
+
externalId: zod.z.string().min(1)
|
|
212
|
+
});
|
|
213
|
+
function dateSchema(dateMode) {
|
|
214
|
+
if (dateMode === "coerce") {
|
|
215
|
+
return zod.z.preprocess(
|
|
216
|
+
(value) => typeof value === "string" || typeof value === "number" ? new Date(value) : value,
|
|
217
|
+
zod.z.date()
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return zod.z.union([zod.z.string(), zod.z.date()]);
|
|
221
|
+
}
|
|
222
|
+
function propertyValueSchema(property, options = {}) {
|
|
223
|
+
const type = property.type;
|
|
224
|
+
let schema;
|
|
225
|
+
switch (type.type) {
|
|
226
|
+
case "text":
|
|
227
|
+
case "enum":
|
|
228
|
+
schema = zod.z.string();
|
|
229
|
+
break;
|
|
230
|
+
case "int32":
|
|
231
|
+
case "int64":
|
|
232
|
+
schema = zod.z.number().int();
|
|
233
|
+
break;
|
|
234
|
+
case "float32":
|
|
235
|
+
case "float64":
|
|
236
|
+
schema = zod.z.number();
|
|
237
|
+
break;
|
|
238
|
+
case "boolean":
|
|
239
|
+
schema = zod.z.boolean();
|
|
240
|
+
break;
|
|
241
|
+
case "date":
|
|
242
|
+
case "timestamp":
|
|
243
|
+
schema = dateSchema(options.dateMode);
|
|
244
|
+
break;
|
|
245
|
+
case "direct":
|
|
246
|
+
schema = nodeIdSchema;
|
|
247
|
+
break;
|
|
248
|
+
case "json":
|
|
249
|
+
schema = zod.z.unknown();
|
|
250
|
+
break;
|
|
251
|
+
default:
|
|
252
|
+
schema = zod.z.unknown();
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
return type.list === true ? zod.z.array(schema) : schema;
|
|
256
|
+
}
|
|
257
|
+
function buildViewSchema(view, options = {}) {
|
|
258
|
+
const shape = {};
|
|
259
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
260
|
+
if (isViewPropertyDefinition(property)) {
|
|
261
|
+
shape[name] = propertyValueSchema(property, options).optional();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return zod.z.object(shape).strict();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/mappers/query-validator.ts
|
|
268
|
+
var NODE_STRING_PROPERTIES = ["externalId", "space"];
|
|
269
|
+
var NODE_NUMBER_PROPERTIES = ["createdTime", "deletedTime", "lastUpdatedTime"];
|
|
270
|
+
var NODE_PROPERTIES2 = /* @__PURE__ */ new Set([...NODE_STRING_PROPERTIES, ...NODE_NUMBER_PROPERTIES]);
|
|
271
|
+
var SORT_DIRECTION_SCHEMA = zod.z.enum(["ascending", "descending"]);
|
|
272
|
+
var recordSchema = zod.z.record(zod.z.string(), zod.z.unknown());
|
|
273
|
+
var leafOps = /* @__PURE__ */ new Set([
|
|
274
|
+
"eq",
|
|
275
|
+
"in",
|
|
276
|
+
"gt",
|
|
277
|
+
"gte",
|
|
278
|
+
"lt",
|
|
279
|
+
"lte",
|
|
280
|
+
"exists",
|
|
281
|
+
"prefix",
|
|
282
|
+
"containsAny",
|
|
283
|
+
"containsAll"
|
|
284
|
+
]);
|
|
285
|
+
function isRecord(value) {
|
|
286
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
287
|
+
}
|
|
288
|
+
function isLeafFilter2(value) {
|
|
289
|
+
return Object.keys(value).some((key) => leafOps.has(key));
|
|
290
|
+
}
|
|
291
|
+
function issuePath(path) {
|
|
292
|
+
return path.length === 0 ? "query" : path.map(String).join(".");
|
|
293
|
+
}
|
|
294
|
+
function formatZodIssues(error, path) {
|
|
295
|
+
return error.issues.map((issue) => `${issuePath([...path, ...issue.path])}: ${issue.message}`);
|
|
296
|
+
}
|
|
297
|
+
function getRelationTarget(property) {
|
|
298
|
+
if (isViewPropertyDefinition(property)) {
|
|
299
|
+
return getDirectRelationSource(property)?.externalId ?? null;
|
|
300
|
+
}
|
|
301
|
+
if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
|
|
302
|
+
return property.source.externalId;
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
function baseValueSchema(property) {
|
|
307
|
+
if (property === "node-string") return zod.z.string();
|
|
308
|
+
if (property === "node-number") return zod.z.number();
|
|
309
|
+
switch (property.type.type) {
|
|
310
|
+
case "text":
|
|
311
|
+
case "enum":
|
|
312
|
+
return zod.z.string();
|
|
313
|
+
case "int32":
|
|
314
|
+
case "int64":
|
|
315
|
+
return zod.z.number().int();
|
|
316
|
+
case "float32":
|
|
317
|
+
case "float64":
|
|
318
|
+
return zod.z.number();
|
|
319
|
+
case "boolean":
|
|
320
|
+
return zod.z.boolean();
|
|
321
|
+
case "date":
|
|
322
|
+
case "timestamp":
|
|
323
|
+
return zod.z.union([zod.z.string(), zod.z.date()]);
|
|
324
|
+
case "direct":
|
|
325
|
+
return nodeIdSchema;
|
|
326
|
+
default:
|
|
327
|
+
return zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean()]);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function leafFilterSchema(property) {
|
|
331
|
+
const value = baseValueSchema(property);
|
|
332
|
+
const isList = typeof property !== "string" && property.type.list === true;
|
|
333
|
+
if (isList) {
|
|
334
|
+
return zod.z.object({
|
|
335
|
+
containsAny: zod.z.array(value).optional(),
|
|
336
|
+
containsAll: zod.z.array(value).optional(),
|
|
337
|
+
exists: zod.z.boolean().optional()
|
|
338
|
+
}).strict();
|
|
339
|
+
}
|
|
340
|
+
if (property === "node-string" || typeof property !== "string" && property.type.type === "text") {
|
|
341
|
+
return zod.z.object({
|
|
342
|
+
eq: zod.z.string().optional(),
|
|
343
|
+
in: zod.z.array(zod.z.string()).optional(),
|
|
344
|
+
prefix: zod.z.string().optional(),
|
|
345
|
+
exists: zod.z.boolean().optional()
|
|
346
|
+
}).strict();
|
|
347
|
+
}
|
|
348
|
+
if (typeof property !== "string" && property.type.type === "enum") {
|
|
349
|
+
return zod.z.object({
|
|
350
|
+
eq: zod.z.string().optional(),
|
|
351
|
+
in: zod.z.array(zod.z.string()).optional(),
|
|
352
|
+
prefix: zod.z.string().optional(),
|
|
353
|
+
exists: zod.z.boolean().optional()
|
|
354
|
+
}).strict();
|
|
355
|
+
}
|
|
356
|
+
if (property === "node-number" || typeof property !== "string" && ["int32", "int64", "float32", "float64"].includes(property.type.type ?? "")) {
|
|
357
|
+
return zod.z.object({
|
|
358
|
+
eq: value.optional(),
|
|
359
|
+
in: zod.z.array(value).optional(),
|
|
360
|
+
gt: value.optional(),
|
|
361
|
+
gte: value.optional(),
|
|
362
|
+
lt: value.optional(),
|
|
363
|
+
lte: value.optional(),
|
|
364
|
+
exists: zod.z.boolean().optional()
|
|
365
|
+
}).strict();
|
|
366
|
+
}
|
|
367
|
+
if (typeof property !== "string" && ["date", "timestamp"].includes(property.type.type ?? "")) {
|
|
368
|
+
return zod.z.object({
|
|
369
|
+
eq: value.optional(),
|
|
370
|
+
in: zod.z.array(value).optional(),
|
|
371
|
+
gt: value.optional(),
|
|
372
|
+
gte: value.optional(),
|
|
373
|
+
lt: value.optional(),
|
|
374
|
+
lte: value.optional(),
|
|
375
|
+
exists: zod.z.boolean().optional()
|
|
376
|
+
}).strict();
|
|
377
|
+
}
|
|
378
|
+
if (typeof property !== "string" && property.type.type === "boolean") {
|
|
379
|
+
return zod.z.object({
|
|
380
|
+
eq: zod.z.boolean().optional(),
|
|
381
|
+
exists: zod.z.boolean().optional()
|
|
382
|
+
}).strict();
|
|
383
|
+
}
|
|
384
|
+
if (typeof property !== "string" && property.type.type === "direct") {
|
|
385
|
+
return zod.z.object({
|
|
386
|
+
eq: nodeIdSchema.optional(),
|
|
387
|
+
in: zod.z.array(nodeIdSchema).optional(),
|
|
388
|
+
exists: zod.z.boolean().optional()
|
|
389
|
+
}).strict();
|
|
390
|
+
}
|
|
391
|
+
return zod.z.object({
|
|
392
|
+
eq: value.optional(),
|
|
393
|
+
in: zod.z.array(value).optional(),
|
|
394
|
+
exists: zod.z.boolean().optional()
|
|
395
|
+
}).strict();
|
|
396
|
+
}
|
|
397
|
+
var QueryValidator = class {
|
|
398
|
+
constructor(viewMapper) {
|
|
399
|
+
this.viewMapper = viewMapper;
|
|
400
|
+
}
|
|
401
|
+
async validate(options, rootView) {
|
|
402
|
+
const errors = [];
|
|
403
|
+
errors.push(...this.validateOptionsShape(options, rootView));
|
|
404
|
+
if (options.select !== void 0) {
|
|
405
|
+
errors.push(...await this.validateSelect(options.select, rootView, ["select"]));
|
|
406
|
+
}
|
|
407
|
+
if (options.filters !== void 0) {
|
|
408
|
+
errors.push(...await this.validateFilters(options.filters, rootView, ["filters"]));
|
|
409
|
+
}
|
|
410
|
+
if (options.sort !== void 0) {
|
|
411
|
+
errors.push(...this.validateSort(options.sort, rootView, ["sort"]));
|
|
412
|
+
}
|
|
413
|
+
if (errors.length > 0) {
|
|
414
|
+
throw new Error(`Invalid query options:
|
|
415
|
+
${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
validateOptionsShape(options, rootView) {
|
|
419
|
+
const schema = zod.z.object({
|
|
420
|
+
viewExternalId: zod.z.literal(rootView.externalId),
|
|
421
|
+
select: zod.z.unknown().optional(),
|
|
422
|
+
filters: zod.z.unknown().optional(),
|
|
423
|
+
sort: zod.z.unknown().optional(),
|
|
424
|
+
limit: zod.z.union([zod.z.literal(-1), zod.z.number().int().positive().max(MAX_LIMIT)]).optional(),
|
|
425
|
+
cursor: zod.z.string().nullable().optional()
|
|
426
|
+
}).strict();
|
|
427
|
+
const result = schema.safeParse(options);
|
|
428
|
+
return result.success ? [] : formatZodIssues(result.error, []);
|
|
429
|
+
}
|
|
430
|
+
async validateSelect(select, view, path) {
|
|
431
|
+
const shape = {
|
|
432
|
+
_all: zod.z.literal(true).optional()
|
|
433
|
+
};
|
|
434
|
+
for (const property of NODE_PROPERTIES2) {
|
|
435
|
+
shape[property] = zod.z.boolean().optional();
|
|
436
|
+
}
|
|
437
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
438
|
+
const target = getRelationTarget(property);
|
|
439
|
+
if (target != null) {
|
|
440
|
+
const nestedSelect = recordSchema;
|
|
441
|
+
shape[name] = isViewPropertyDefinition(property) ? zod.z.union([zod.z.boolean(), nestedSelect]).optional() : nestedSelect.optional();
|
|
442
|
+
} else {
|
|
443
|
+
shape[name] = zod.z.boolean().optional();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const result = zod.z.object(shape).strict().safeParse(select);
|
|
447
|
+
if (!result.success) return formatZodIssues(result.error, path);
|
|
448
|
+
if (!isRecord(select)) return [];
|
|
449
|
+
const errors = [];
|
|
450
|
+
for (const [name, value] of Object.entries(select)) {
|
|
451
|
+
if (name === "_all" || value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
const property = view.properties[name];
|
|
455
|
+
if (!property) continue;
|
|
456
|
+
const target = getRelationTarget(property);
|
|
457
|
+
if (target == null) {
|
|
458
|
+
errors.push(
|
|
459
|
+
`${issuePath([...path, name])}: property "${name}" does not support nested select`
|
|
460
|
+
);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const targetView = await this.viewMapper.getView(target);
|
|
464
|
+
errors.push(...await this.validateSelect(value, targetView, [...path, name]));
|
|
465
|
+
}
|
|
466
|
+
return errors;
|
|
467
|
+
}
|
|
468
|
+
async validateFilters(filters, view, path) {
|
|
469
|
+
const shape = {
|
|
470
|
+
AND: zod.z.union([recordSchema, zod.z.array(recordSchema)]).optional(),
|
|
471
|
+
OR: zod.z.array(recordSchema).optional(),
|
|
472
|
+
NOT: zod.z.union([recordSchema, zod.z.array(recordSchema)]).optional()
|
|
473
|
+
};
|
|
474
|
+
for (const property of NODE_STRING_PROPERTIES) {
|
|
475
|
+
shape[property] = zod.z.unknown().optional();
|
|
476
|
+
}
|
|
477
|
+
for (const property of NODE_NUMBER_PROPERTIES) {
|
|
478
|
+
shape[property] = zod.z.unknown().optional();
|
|
479
|
+
}
|
|
480
|
+
for (const property of Object.keys(view.properties)) {
|
|
481
|
+
shape[property] = zod.z.unknown().optional();
|
|
482
|
+
}
|
|
483
|
+
const result = zod.z.object(shape).strict().safeParse(filters);
|
|
484
|
+
if (!result.success) return formatZodIssues(result.error, path);
|
|
485
|
+
if (!isRecord(filters)) return [];
|
|
486
|
+
const errors = [];
|
|
487
|
+
for (const [name, value] of Object.entries(filters)) {
|
|
488
|
+
if (value == null) continue;
|
|
489
|
+
if (name === "AND" || name === "OR" || name === "NOT") {
|
|
490
|
+
const clauses = Array.isArray(value) ? value : [value];
|
|
491
|
+
for (const [index, clause] of clauses.entries()) {
|
|
492
|
+
errors.push(...await this.validateFilters(clause, view, [...path, name, index]));
|
|
493
|
+
}
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (!isRecord(value)) {
|
|
497
|
+
errors.push(`${issuePath([...path, name])}: Expected object`);
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
const nodePropertyType = NODE_STRING_PROPERTIES.includes(
|
|
501
|
+
name
|
|
502
|
+
) ? "node-string" : NODE_NUMBER_PROPERTIES.includes(name) ? "node-number" : null;
|
|
503
|
+
if (nodePropertyType != null) {
|
|
504
|
+
errors.push(...this.validateLeafFilter(value, nodePropertyType, [...path, name]));
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const property = view.properties[name];
|
|
508
|
+
if (!property) continue;
|
|
509
|
+
if (isViewPropertyDefinition(property)) {
|
|
510
|
+
const target2 = getDirectRelationSource(property);
|
|
511
|
+
if (target2 != null && !isLeafFilter2(value)) {
|
|
512
|
+
const targetView = await this.viewMapper.getView(target2.externalId);
|
|
513
|
+
errors.push(...await this.validateFilters(value, targetView, [...path, name]));
|
|
514
|
+
} else {
|
|
515
|
+
errors.push(...this.validateLeafFilter(value, property, [...path, name]));
|
|
516
|
+
}
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const target = getRelationTarget(property);
|
|
520
|
+
if (target == null) {
|
|
521
|
+
errors.push(`${issuePath([...path, name])}: property "${name}" does not support filters`);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
errors.push(
|
|
525
|
+
`${issuePath([...path, name])}: filtering through "${name}" is not supported by the query mapper`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
return errors;
|
|
529
|
+
}
|
|
530
|
+
validateLeafFilter(value, property, path) {
|
|
531
|
+
const result = leafFilterSchema(property).safeParse(value);
|
|
532
|
+
return result.success ? [] : formatZodIssues(result.error, path);
|
|
533
|
+
}
|
|
534
|
+
validateSort(sort, view, path) {
|
|
535
|
+
const shape = {};
|
|
536
|
+
for (const property of NODE_PROPERTIES2) {
|
|
537
|
+
shape[property] = SORT_DIRECTION_SCHEMA.optional();
|
|
538
|
+
}
|
|
539
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
540
|
+
if (isViewPropertyDefinition(property) && property.type.list !== true) {
|
|
541
|
+
shape[name] = SORT_DIRECTION_SCHEMA.optional();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const result = zod.z.object(shape).strict().safeParse(sort);
|
|
545
|
+
return result.success ? [] : formatZodIssues(result.error, path);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
207
548
|
|
|
208
549
|
// src/mappers/sort-mapper.ts
|
|
209
550
|
var SortMapper = class {
|
|
@@ -229,6 +570,7 @@ var QueryMapper = class {
|
|
|
229
570
|
this.viewMapper = viewMapper;
|
|
230
571
|
this.filterMapper = new FilterMapper(viewMapper);
|
|
231
572
|
this.sortMapper = new SortMapper();
|
|
573
|
+
this.validator = new QueryValidator(viewMapper);
|
|
232
574
|
}
|
|
233
575
|
async map(options) {
|
|
234
576
|
const {
|
|
@@ -241,6 +583,7 @@ var QueryMapper = class {
|
|
|
241
583
|
} = options;
|
|
242
584
|
const limit = requestedLimit === -1 ? DEFAULT_LIMIT : requestedLimit;
|
|
243
585
|
const rootView = await this.viewMapper.getView(viewExternalId);
|
|
586
|
+
await this.validator.validate(options, rootView);
|
|
244
587
|
const rootViewRef = toViewReference(rootView);
|
|
245
588
|
const whereFilters = filters ? await this.filterMapper.map(filters, rootView) : [];
|
|
246
589
|
const baseFilters = [{ hasData: [rootViewRef] }, ...whereFilters];
|
|
@@ -531,6 +874,92 @@ var QueryResultMapper = class {
|
|
|
531
874
|
return entry;
|
|
532
875
|
}
|
|
533
876
|
};
|
|
877
|
+
var nodeMetadataSchema = {
|
|
878
|
+
instanceType: zod.z.literal("node").optional(),
|
|
879
|
+
space: zod.z.string(),
|
|
880
|
+
externalId: zod.z.string(),
|
|
881
|
+
version: zod.z.number().optional(),
|
|
882
|
+
createdTime: zod.z.number().optional(),
|
|
883
|
+
deletedTime: zod.z.number().optional(),
|
|
884
|
+
lastUpdatedTime: zod.z.number().optional(),
|
|
885
|
+
_edges: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
886
|
+
};
|
|
887
|
+
function isListRelation(property) {
|
|
888
|
+
if (isViewPropertyDefinition(property)) {
|
|
889
|
+
return isListDirectRelation(property);
|
|
890
|
+
}
|
|
891
|
+
if (isReverseDirectRelation(property)) {
|
|
892
|
+
return property.connectionType === "multi_reverse_direct_relation" || property.targetsList === true;
|
|
893
|
+
}
|
|
894
|
+
return isEdgeConnection(property);
|
|
895
|
+
}
|
|
896
|
+
function isRecord2(value) {
|
|
897
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
898
|
+
}
|
|
899
|
+
var QueryResultValidator = class {
|
|
900
|
+
constructor(viewMapper) {
|
|
901
|
+
this.viewMapper = viewMapper;
|
|
902
|
+
}
|
|
903
|
+
async parseItems(rootViewExternalId, items, select) {
|
|
904
|
+
const rootView = await this.viewMapper.getView(rootViewExternalId);
|
|
905
|
+
const schema = await this.buildResultSchema(rootView, MAX_DEPENDENCY_DEPTH, select);
|
|
906
|
+
const result = zod.z.array(schema).safeParse(items);
|
|
907
|
+
if (!result.success) {
|
|
908
|
+
throw new Error(
|
|
909
|
+
`Invalid query result:
|
|
910
|
+
${result.error.issues.map((issue) => `- ${issue.path.map(String).join(".")}: ${issue.message}`).join("\n")}`
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
return result.data;
|
|
914
|
+
}
|
|
915
|
+
async buildResultSchema(view, remainingDepth, select) {
|
|
916
|
+
const shape = { ...nodeMetadataSchema };
|
|
917
|
+
const includeAllProperties = select == null || select._all === true;
|
|
918
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
919
|
+
const isSelected = includeAllProperties || name in select;
|
|
920
|
+
if (!isSelected) continue;
|
|
921
|
+
const nestedSelect = isRecord2(select?.[name]) ? select[name] : void 0;
|
|
922
|
+
if (isViewPropertyDefinition(property)) {
|
|
923
|
+
const relationSource = getDirectRelationSource(property);
|
|
924
|
+
if (relationSource) {
|
|
925
|
+
shape[name] = await this.buildRelationSchema(
|
|
926
|
+
property,
|
|
927
|
+
relationSource.externalId,
|
|
928
|
+
remainingDepth,
|
|
929
|
+
nestedSelect
|
|
930
|
+
);
|
|
931
|
+
} else {
|
|
932
|
+
shape[name] = propertyValueSchema(property, { dateMode: "coerce" }).optional();
|
|
933
|
+
}
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
|
|
937
|
+
shape[name] = await this.buildRelationSchema(
|
|
938
|
+
property,
|
|
939
|
+
property.source.externalId,
|
|
940
|
+
remainingDepth,
|
|
941
|
+
nestedSelect
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
const schema = zod.z.object(shape);
|
|
946
|
+
return includeAllProperties ? schema.strict() : schema;
|
|
947
|
+
}
|
|
948
|
+
async buildRelationSchema(property, targetViewExternalId, remainingDepth, select) {
|
|
949
|
+
const isList = isListRelation(property);
|
|
950
|
+
const fallbackSchema = isViewPropertyDefinition(property) ? propertyValueSchema(property, { dateMode: "coerce" }) : zod.z.unknown();
|
|
951
|
+
if (remainingDepth <= 0 || select == null) {
|
|
952
|
+
return fallbackSchema.optional();
|
|
953
|
+
}
|
|
954
|
+
const targetView = await this.viewMapper.getView(targetViewExternalId);
|
|
955
|
+
const nestedSchema = await this.buildResultSchema(targetView, remainingDepth - 1, select);
|
|
956
|
+
if (isViewPropertyDefinition(property)) {
|
|
957
|
+
const nestedRelationSchema = isList ? zod.z.array(nestedSchema) : nestedSchema;
|
|
958
|
+
return zod.z.union([nestedRelationSchema, fallbackSchema]).optional();
|
|
959
|
+
}
|
|
960
|
+
return (isList ? zod.z.array(nestedSchema) : nestedSchema).optional();
|
|
961
|
+
}
|
|
962
|
+
};
|
|
534
963
|
|
|
535
964
|
// src/mappers/view-mapper.ts
|
|
536
965
|
var ViewMapper = class {
|
|
@@ -681,15 +1110,18 @@ function buildDependenciesQuery(previousQuery, nodesParent, nodesChildren, leafC
|
|
|
681
1110
|
|
|
682
1111
|
// src/client.ts
|
|
683
1112
|
var IndustrialModelClient = class {
|
|
684
|
-
constructor(client, dataModelId) {
|
|
1113
|
+
constructor(client, dataModelId, options = {}) {
|
|
685
1114
|
const cognite = createCogniteAdapter(client);
|
|
686
1115
|
this.cognite = cognite;
|
|
687
1116
|
const viewMapper = new ViewMapper(cognite, dataModelId);
|
|
688
1117
|
this.queryMapper = new QueryMapper(viewMapper);
|
|
689
1118
|
this.resultMapper = new QueryResultMapper(viewMapper);
|
|
1119
|
+
this.resultValidator = new QueryResultValidator(viewMapper);
|
|
1120
|
+
this.validateResults = options.validateResults ?? false;
|
|
690
1121
|
}
|
|
691
1122
|
query() {
|
|
692
|
-
|
|
1123
|
+
const execute = (options) => this.queryInternal(options);
|
|
1124
|
+
return execute;
|
|
693
1125
|
}
|
|
694
1126
|
async queryInternal(options) {
|
|
695
1127
|
const { viewExternalId, limit = DEFAULT_LIMIT } = options;
|
|
@@ -707,7 +1139,8 @@ var IndustrialModelClient = class {
|
|
|
707
1139
|
mapNodesAndEdges(queryResult),
|
|
708
1140
|
dependenciesData
|
|
709
1141
|
);
|
|
710
|
-
const
|
|
1142
|
+
const mappedPageResult = await this.resultMapper.mapNodes(viewExternalId, queryResultData);
|
|
1143
|
+
const pageResult = this.validateResults ? await this.resultValidator.parseItems(viewExternalId, mappedPageResult, options.select) : mappedPageResult;
|
|
711
1144
|
const nextCursor = queryResult.nextCursor[viewExternalId] ?? null;
|
|
712
1145
|
const isLastPage = pageResult.length < limit || !nextCursor;
|
|
713
1146
|
const resolvedCursor = isLastPage ? null : nextCursor;
|
|
@@ -739,5 +1172,7 @@ var IndustrialModelClient = class {
|
|
|
739
1172
|
};
|
|
740
1173
|
|
|
741
1174
|
exports.IndustrialModelClient = IndustrialModelClient;
|
|
1175
|
+
exports.buildViewSchema = buildViewSchema;
|
|
1176
|
+
exports.nodeIdSchema = nodeIdSchema;
|
|
742
1177
|
//# sourceMappingURL=index.cjs.map
|
|
743
1178
|
//# sourceMappingURL=index.cjs.map
|