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/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,46 @@
|
|
|
1
1
|
import { CogniteClient } from '@cognite/sdk';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
|
|
4
|
+
interface ViewReference {
|
|
5
|
+
type: "view";
|
|
6
|
+
space: string;
|
|
7
|
+
externalId: string;
|
|
8
|
+
version: string;
|
|
9
|
+
}
|
|
10
|
+
interface ViewPropertyType {
|
|
11
|
+
type?: string;
|
|
12
|
+
source?: ViewReference;
|
|
13
|
+
list?: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface ViewPropertyDefinition {
|
|
16
|
+
container: unknown;
|
|
17
|
+
containerPropertyIdentifier: string;
|
|
18
|
+
type: ViewPropertyType;
|
|
19
|
+
}
|
|
20
|
+
interface ReverseDirectRelationConnection {
|
|
21
|
+
through: {
|
|
22
|
+
source: ViewReference;
|
|
23
|
+
identifier: string;
|
|
24
|
+
};
|
|
25
|
+
source: ViewReference;
|
|
26
|
+
connectionType?: string;
|
|
27
|
+
targetsList?: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface EdgeConnection {
|
|
30
|
+
type: unknown;
|
|
31
|
+
source: ViewReference;
|
|
32
|
+
direction?: "outwards" | "inwards";
|
|
33
|
+
}
|
|
34
|
+
type ViewDefinitionProperty = ViewPropertyDefinition | ReverseDirectRelationConnection | EdgeConnection;
|
|
35
|
+
interface ViewDefinition {
|
|
36
|
+
space: string;
|
|
37
|
+
externalId: string;
|
|
38
|
+
version: string;
|
|
39
|
+
properties: Record<string, ViewDefinitionProperty>;
|
|
40
|
+
}
|
|
3
41
|
interface NodeDefinition {
|
|
4
42
|
instanceType: "node";
|
|
43
|
+
version?: number;
|
|
5
44
|
space: string;
|
|
6
45
|
externalId: string;
|
|
7
46
|
properties?: Record<string, Record<string, Record<string, unknown>>>;
|
|
@@ -18,6 +57,9 @@ type DataModelId = NodeId & {
|
|
|
18
57
|
version: string;
|
|
19
58
|
};
|
|
20
59
|
type SortDirection = "ascending" | "descending";
|
|
60
|
+
interface IndustrialModelClientOptions {
|
|
61
|
+
validateResults?: boolean;
|
|
62
|
+
}
|
|
21
63
|
type Simplify<T> = {
|
|
22
64
|
[K in keyof T]: T[K];
|
|
23
65
|
} & {};
|
|
@@ -61,11 +103,12 @@ interface QueryOptions<TModel, TSelect extends QuerySelect<TModel> | undefined =
|
|
|
61
103
|
limit?: number;
|
|
62
104
|
cursor?: string | null;
|
|
63
105
|
}
|
|
64
|
-
type QueryResultMetadata = Pick<NodeDefinition, "space" | "externalId" | "createdTime" | "deletedTime" | "lastUpdatedTime">;
|
|
106
|
+
type QueryResultMetadata = Pick<NodeDefinition, "space" | "externalId" | "version" | "createdTime" | "deletedTime" | "lastUpdatedTime">;
|
|
65
107
|
type ResultShapeForKey<TModel, K extends PropertyKey> = K extends keyof ModelProps<TModel> ? ModelProps<TModel>[K] : K extends RelationKeys<TModel> ? ModelRelations<TModel>[K] : never;
|
|
66
108
|
type ResultEntityForKey<TModel, K extends PropertyKey> = K extends RelationKeys<TModel> ? ModelRelations<TModel>[K] : K extends keyof ModelProps<TModel> ? ModelProps<TModel>[K] : never;
|
|
67
109
|
type WrapResultValue<TShape, TValue> = [NonNull<TShape>] extends [readonly unknown[]] ? TValue[] : TValue;
|
|
68
|
-
type
|
|
110
|
+
type AsQuerySelect<TModel, TSelect> = TSelect extends QuerySelect<TModel> ? TSelect : never;
|
|
111
|
+
type SelectedValue<TModel, K extends PropertyKey, TValue, TDepth extends QueryDepth> = TValue extends true ? K extends keyof ModelProps<TModel> ? ModelProps<TModel>[K] : never : TDepth extends 0 ? never : TValue extends object ? WrapResultValue<ResultShapeForKey<TModel, K>, QueryResultItem<UnwrapRelationTarget<ResultEntityForKey<TModel, K>>, AsQuerySelect<UnwrapRelationTarget<ResultEntityForKey<TModel, K>>, TValue>, PrevDepth[TDepth]>> : never;
|
|
69
112
|
type ExplicitSelectionResult<TModel, TSelect, TDepth extends QueryDepth> = Simplify<{
|
|
70
113
|
[K in keyof NonNull<TSelect> as K extends "_all" ? never : SelectedValue<TModel, K, NonNull<TSelect>[K], TDepth> extends never ? never : IsOptionalKey<ModelProps<TModel>, K> extends true ? never : IsOptionalKey<ModelRelations<TModel>, K> extends true ? never : K]-?: SelectedValue<TModel, K, NonNull<TSelect>[K], TDepth>;
|
|
71
114
|
} & {
|
|
@@ -78,6 +121,14 @@ interface QueryResult<TItem = Record<string, unknown>> {
|
|
|
78
121
|
items: TItem[];
|
|
79
122
|
cursor: string | null;
|
|
80
123
|
}
|
|
124
|
+
type QueryExecutor<TModel> = {
|
|
125
|
+
<const TSelect extends QuerySelect<TModel>>(options: Omit<QueryOptions<TModel, TSelect>, "select"> & {
|
|
126
|
+
select: TSelect & QuerySelect<TModel>;
|
|
127
|
+
}): Promise<QueryResult<QueryResultItem<TModel, TSelect>>>;
|
|
128
|
+
(options: Omit<QueryOptions<TModel, undefined>, "select"> & {
|
|
129
|
+
select?: undefined;
|
|
130
|
+
}): Promise<QueryResult<QueryResultItem<TModel, undefined>>>;
|
|
131
|
+
};
|
|
81
132
|
type StringFilters = {
|
|
82
133
|
eq?: string;
|
|
83
134
|
in?: string[];
|
|
@@ -131,10 +182,21 @@ declare class IndustrialModelClient {
|
|
|
131
182
|
private readonly cognite;
|
|
132
183
|
private readonly queryMapper;
|
|
133
184
|
private readonly resultMapper;
|
|
134
|
-
|
|
135
|
-
|
|
185
|
+
private readonly resultValidator;
|
|
186
|
+
private readonly validateResults;
|
|
187
|
+
constructor(client: CogniteClient, dataModelId: DataModelId, options?: IndustrialModelClientOptions);
|
|
188
|
+
query<TModel>(): QueryExecutor<TModel>;
|
|
136
189
|
private queryInternal;
|
|
137
190
|
private queryDependenciesPages;
|
|
138
191
|
}
|
|
139
192
|
|
|
140
|
-
|
|
193
|
+
interface BuildViewSchemaOptions {
|
|
194
|
+
dateMode?: "preserve" | "coerce";
|
|
195
|
+
}
|
|
196
|
+
declare const nodeIdSchema: z.ZodObject<{
|
|
197
|
+
space: z.ZodString;
|
|
198
|
+
externalId: z.ZodString;
|
|
199
|
+
}, z.core.$strip>;
|
|
200
|
+
declare function buildViewSchema(view: ViewDefinition, options?: BuildViewSchemaOptions): z.ZodObject<Record<string, z.ZodType>>;
|
|
201
|
+
|
|
202
|
+
export { type BuildViewSchemaOptions, type DataModelId, type IndustrialModel, IndustrialModelClient, type IndustrialModelClientOptions, type ModelProps, type ModelRelations, type NodeId, type QueryOptions, type QueryResult, type QueryResultItem, type QueryResultMetadata, type QuerySelect, buildViewSchema, nodeIdSchema };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
1
3
|
// src/cognite/adapter.ts
|
|
2
4
|
function createCogniteAdapter(client) {
|
|
3
5
|
return new CogniteSdkAdapter(client);
|
|
@@ -202,6 +204,345 @@ var FilterMapper = class {
|
|
|
202
204
|
return value;
|
|
203
205
|
}
|
|
204
206
|
};
|
|
207
|
+
var nodeIdSchema = z.object({
|
|
208
|
+
space: z.string().min(1),
|
|
209
|
+
externalId: z.string().min(1)
|
|
210
|
+
});
|
|
211
|
+
function dateSchema(dateMode) {
|
|
212
|
+
if (dateMode === "coerce") {
|
|
213
|
+
return z.preprocess(
|
|
214
|
+
(value) => typeof value === "string" || typeof value === "number" ? new Date(value) : value,
|
|
215
|
+
z.date()
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return z.union([z.string(), z.date()]);
|
|
219
|
+
}
|
|
220
|
+
function propertyValueSchema(property, options = {}) {
|
|
221
|
+
const type = property.type;
|
|
222
|
+
let schema;
|
|
223
|
+
switch (type.type) {
|
|
224
|
+
case "text":
|
|
225
|
+
case "enum":
|
|
226
|
+
schema = z.string();
|
|
227
|
+
break;
|
|
228
|
+
case "int32":
|
|
229
|
+
case "int64":
|
|
230
|
+
schema = z.number().int();
|
|
231
|
+
break;
|
|
232
|
+
case "float32":
|
|
233
|
+
case "float64":
|
|
234
|
+
schema = z.number();
|
|
235
|
+
break;
|
|
236
|
+
case "boolean":
|
|
237
|
+
schema = z.boolean();
|
|
238
|
+
break;
|
|
239
|
+
case "date":
|
|
240
|
+
case "timestamp":
|
|
241
|
+
schema = dateSchema(options.dateMode);
|
|
242
|
+
break;
|
|
243
|
+
case "direct":
|
|
244
|
+
schema = nodeIdSchema;
|
|
245
|
+
break;
|
|
246
|
+
case "json":
|
|
247
|
+
schema = z.unknown();
|
|
248
|
+
break;
|
|
249
|
+
default:
|
|
250
|
+
schema = z.unknown();
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
return type.list === true ? z.array(schema) : schema;
|
|
254
|
+
}
|
|
255
|
+
function buildViewSchema(view, options = {}) {
|
|
256
|
+
const shape = {};
|
|
257
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
258
|
+
if (isViewPropertyDefinition(property)) {
|
|
259
|
+
shape[name] = propertyValueSchema(property, options).optional();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return z.object(shape).strict();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/mappers/query-validator.ts
|
|
266
|
+
var NODE_STRING_PROPERTIES = ["externalId", "space"];
|
|
267
|
+
var NODE_NUMBER_PROPERTIES = ["createdTime", "deletedTime", "lastUpdatedTime"];
|
|
268
|
+
var NODE_PROPERTIES2 = /* @__PURE__ */ new Set([...NODE_STRING_PROPERTIES, ...NODE_NUMBER_PROPERTIES]);
|
|
269
|
+
var SORT_DIRECTION_SCHEMA = z.enum(["ascending", "descending"]);
|
|
270
|
+
var recordSchema = z.record(z.string(), z.unknown());
|
|
271
|
+
var leafOps = /* @__PURE__ */ new Set([
|
|
272
|
+
"eq",
|
|
273
|
+
"in",
|
|
274
|
+
"gt",
|
|
275
|
+
"gte",
|
|
276
|
+
"lt",
|
|
277
|
+
"lte",
|
|
278
|
+
"exists",
|
|
279
|
+
"prefix",
|
|
280
|
+
"containsAny",
|
|
281
|
+
"containsAll"
|
|
282
|
+
]);
|
|
283
|
+
function isRecord(value) {
|
|
284
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
285
|
+
}
|
|
286
|
+
function isLeafFilter2(value) {
|
|
287
|
+
return Object.keys(value).some((key) => leafOps.has(key));
|
|
288
|
+
}
|
|
289
|
+
function issuePath(path) {
|
|
290
|
+
return path.length === 0 ? "query" : path.map(String).join(".");
|
|
291
|
+
}
|
|
292
|
+
function formatZodIssues(error, path) {
|
|
293
|
+
return error.issues.map((issue) => `${issuePath([...path, ...issue.path])}: ${issue.message}`);
|
|
294
|
+
}
|
|
295
|
+
function getRelationTarget(property) {
|
|
296
|
+
if (isViewPropertyDefinition(property)) {
|
|
297
|
+
return getDirectRelationSource(property)?.externalId ?? null;
|
|
298
|
+
}
|
|
299
|
+
if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
|
|
300
|
+
return property.source.externalId;
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
function baseValueSchema(property) {
|
|
305
|
+
if (property === "node-string") return z.string();
|
|
306
|
+
if (property === "node-number") return z.number();
|
|
307
|
+
switch (property.type.type) {
|
|
308
|
+
case "text":
|
|
309
|
+
case "enum":
|
|
310
|
+
return z.string();
|
|
311
|
+
case "int32":
|
|
312
|
+
case "int64":
|
|
313
|
+
return z.number().int();
|
|
314
|
+
case "float32":
|
|
315
|
+
case "float64":
|
|
316
|
+
return z.number();
|
|
317
|
+
case "boolean":
|
|
318
|
+
return z.boolean();
|
|
319
|
+
case "date":
|
|
320
|
+
case "timestamp":
|
|
321
|
+
return z.union([z.string(), z.date()]);
|
|
322
|
+
case "direct":
|
|
323
|
+
return nodeIdSchema;
|
|
324
|
+
default:
|
|
325
|
+
return z.union([z.string(), z.number(), z.boolean()]);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function leafFilterSchema(property) {
|
|
329
|
+
const value = baseValueSchema(property);
|
|
330
|
+
const isList = typeof property !== "string" && property.type.list === true;
|
|
331
|
+
if (isList) {
|
|
332
|
+
return z.object({
|
|
333
|
+
containsAny: z.array(value).optional(),
|
|
334
|
+
containsAll: z.array(value).optional(),
|
|
335
|
+
exists: z.boolean().optional()
|
|
336
|
+
}).strict();
|
|
337
|
+
}
|
|
338
|
+
if (property === "node-string" || typeof property !== "string" && property.type.type === "text") {
|
|
339
|
+
return z.object({
|
|
340
|
+
eq: z.string().optional(),
|
|
341
|
+
in: z.array(z.string()).optional(),
|
|
342
|
+
prefix: z.string().optional(),
|
|
343
|
+
exists: z.boolean().optional()
|
|
344
|
+
}).strict();
|
|
345
|
+
}
|
|
346
|
+
if (typeof property !== "string" && property.type.type === "enum") {
|
|
347
|
+
return z.object({
|
|
348
|
+
eq: z.string().optional(),
|
|
349
|
+
in: z.array(z.string()).optional(),
|
|
350
|
+
prefix: z.string().optional(),
|
|
351
|
+
exists: z.boolean().optional()
|
|
352
|
+
}).strict();
|
|
353
|
+
}
|
|
354
|
+
if (property === "node-number" || typeof property !== "string" && ["int32", "int64", "float32", "float64"].includes(property.type.type ?? "")) {
|
|
355
|
+
return z.object({
|
|
356
|
+
eq: value.optional(),
|
|
357
|
+
in: z.array(value).optional(),
|
|
358
|
+
gt: value.optional(),
|
|
359
|
+
gte: value.optional(),
|
|
360
|
+
lt: value.optional(),
|
|
361
|
+
lte: value.optional(),
|
|
362
|
+
exists: z.boolean().optional()
|
|
363
|
+
}).strict();
|
|
364
|
+
}
|
|
365
|
+
if (typeof property !== "string" && ["date", "timestamp"].includes(property.type.type ?? "")) {
|
|
366
|
+
return z.object({
|
|
367
|
+
eq: value.optional(),
|
|
368
|
+
in: z.array(value).optional(),
|
|
369
|
+
gt: value.optional(),
|
|
370
|
+
gte: value.optional(),
|
|
371
|
+
lt: value.optional(),
|
|
372
|
+
lte: value.optional(),
|
|
373
|
+
exists: z.boolean().optional()
|
|
374
|
+
}).strict();
|
|
375
|
+
}
|
|
376
|
+
if (typeof property !== "string" && property.type.type === "boolean") {
|
|
377
|
+
return z.object({
|
|
378
|
+
eq: z.boolean().optional(),
|
|
379
|
+
exists: z.boolean().optional()
|
|
380
|
+
}).strict();
|
|
381
|
+
}
|
|
382
|
+
if (typeof property !== "string" && property.type.type === "direct") {
|
|
383
|
+
return z.object({
|
|
384
|
+
eq: nodeIdSchema.optional(),
|
|
385
|
+
in: z.array(nodeIdSchema).optional(),
|
|
386
|
+
exists: z.boolean().optional()
|
|
387
|
+
}).strict();
|
|
388
|
+
}
|
|
389
|
+
return z.object({
|
|
390
|
+
eq: value.optional(),
|
|
391
|
+
in: z.array(value).optional(),
|
|
392
|
+
exists: z.boolean().optional()
|
|
393
|
+
}).strict();
|
|
394
|
+
}
|
|
395
|
+
var QueryValidator = class {
|
|
396
|
+
constructor(viewMapper) {
|
|
397
|
+
this.viewMapper = viewMapper;
|
|
398
|
+
}
|
|
399
|
+
async validate(options, rootView) {
|
|
400
|
+
const errors = [];
|
|
401
|
+
errors.push(...this.validateOptionsShape(options, rootView));
|
|
402
|
+
if (options.select !== void 0) {
|
|
403
|
+
errors.push(...await this.validateSelect(options.select, rootView, ["select"]));
|
|
404
|
+
}
|
|
405
|
+
if (options.filters !== void 0) {
|
|
406
|
+
errors.push(...await this.validateFilters(options.filters, rootView, ["filters"]));
|
|
407
|
+
}
|
|
408
|
+
if (options.sort !== void 0) {
|
|
409
|
+
errors.push(...this.validateSort(options.sort, rootView, ["sort"]));
|
|
410
|
+
}
|
|
411
|
+
if (errors.length > 0) {
|
|
412
|
+
throw new Error(`Invalid query options:
|
|
413
|
+
${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
validateOptionsShape(options, rootView) {
|
|
417
|
+
const schema = z.object({
|
|
418
|
+
viewExternalId: z.literal(rootView.externalId),
|
|
419
|
+
select: z.unknown().optional(),
|
|
420
|
+
filters: z.unknown().optional(),
|
|
421
|
+
sort: z.unknown().optional(),
|
|
422
|
+
limit: z.union([z.literal(-1), z.number().int().positive().max(MAX_LIMIT)]).optional(),
|
|
423
|
+
cursor: z.string().nullable().optional()
|
|
424
|
+
}).strict();
|
|
425
|
+
const result = schema.safeParse(options);
|
|
426
|
+
return result.success ? [] : formatZodIssues(result.error, []);
|
|
427
|
+
}
|
|
428
|
+
async validateSelect(select, view, path) {
|
|
429
|
+
const shape = {
|
|
430
|
+
_all: z.literal(true).optional()
|
|
431
|
+
};
|
|
432
|
+
for (const property of NODE_PROPERTIES2) {
|
|
433
|
+
shape[property] = z.boolean().optional();
|
|
434
|
+
}
|
|
435
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
436
|
+
const target = getRelationTarget(property);
|
|
437
|
+
if (target != null) {
|
|
438
|
+
const nestedSelect = recordSchema;
|
|
439
|
+
shape[name] = isViewPropertyDefinition(property) ? z.union([z.boolean(), nestedSelect]).optional() : nestedSelect.optional();
|
|
440
|
+
} else {
|
|
441
|
+
shape[name] = z.boolean().optional();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const result = z.object(shape).strict().safeParse(select);
|
|
445
|
+
if (!result.success) return formatZodIssues(result.error, path);
|
|
446
|
+
if (!isRecord(select)) return [];
|
|
447
|
+
const errors = [];
|
|
448
|
+
for (const [name, value] of Object.entries(select)) {
|
|
449
|
+
if (name === "_all" || value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const property = view.properties[name];
|
|
453
|
+
if (!property) continue;
|
|
454
|
+
const target = getRelationTarget(property);
|
|
455
|
+
if (target == null) {
|
|
456
|
+
errors.push(
|
|
457
|
+
`${issuePath([...path, name])}: property "${name}" does not support nested select`
|
|
458
|
+
);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const targetView = await this.viewMapper.getView(target);
|
|
462
|
+
errors.push(...await this.validateSelect(value, targetView, [...path, name]));
|
|
463
|
+
}
|
|
464
|
+
return errors;
|
|
465
|
+
}
|
|
466
|
+
async validateFilters(filters, view, path) {
|
|
467
|
+
const shape = {
|
|
468
|
+
AND: z.union([recordSchema, z.array(recordSchema)]).optional(),
|
|
469
|
+
OR: z.array(recordSchema).optional(),
|
|
470
|
+
NOT: z.union([recordSchema, z.array(recordSchema)]).optional()
|
|
471
|
+
};
|
|
472
|
+
for (const property of NODE_STRING_PROPERTIES) {
|
|
473
|
+
shape[property] = z.unknown().optional();
|
|
474
|
+
}
|
|
475
|
+
for (const property of NODE_NUMBER_PROPERTIES) {
|
|
476
|
+
shape[property] = z.unknown().optional();
|
|
477
|
+
}
|
|
478
|
+
for (const property of Object.keys(view.properties)) {
|
|
479
|
+
shape[property] = z.unknown().optional();
|
|
480
|
+
}
|
|
481
|
+
const result = z.object(shape).strict().safeParse(filters);
|
|
482
|
+
if (!result.success) return formatZodIssues(result.error, path);
|
|
483
|
+
if (!isRecord(filters)) return [];
|
|
484
|
+
const errors = [];
|
|
485
|
+
for (const [name, value] of Object.entries(filters)) {
|
|
486
|
+
if (value == null) continue;
|
|
487
|
+
if (name === "AND" || name === "OR" || name === "NOT") {
|
|
488
|
+
const clauses = Array.isArray(value) ? value : [value];
|
|
489
|
+
for (const [index, clause] of clauses.entries()) {
|
|
490
|
+
errors.push(...await this.validateFilters(clause, view, [...path, name, index]));
|
|
491
|
+
}
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (!isRecord(value)) {
|
|
495
|
+
errors.push(`${issuePath([...path, name])}: Expected object`);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const nodePropertyType = NODE_STRING_PROPERTIES.includes(
|
|
499
|
+
name
|
|
500
|
+
) ? "node-string" : NODE_NUMBER_PROPERTIES.includes(name) ? "node-number" : null;
|
|
501
|
+
if (nodePropertyType != null) {
|
|
502
|
+
errors.push(...this.validateLeafFilter(value, nodePropertyType, [...path, name]));
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const property = view.properties[name];
|
|
506
|
+
if (!property) continue;
|
|
507
|
+
if (isViewPropertyDefinition(property)) {
|
|
508
|
+
const target2 = getDirectRelationSource(property);
|
|
509
|
+
if (target2 != null && !isLeafFilter2(value)) {
|
|
510
|
+
const targetView = await this.viewMapper.getView(target2.externalId);
|
|
511
|
+
errors.push(...await this.validateFilters(value, targetView, [...path, name]));
|
|
512
|
+
} else {
|
|
513
|
+
errors.push(...this.validateLeafFilter(value, property, [...path, name]));
|
|
514
|
+
}
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const target = getRelationTarget(property);
|
|
518
|
+
if (target == null) {
|
|
519
|
+
errors.push(`${issuePath([...path, name])}: property "${name}" does not support filters`);
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
errors.push(
|
|
523
|
+
`${issuePath([...path, name])}: filtering through "${name}" is not supported by the query mapper`
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
return errors;
|
|
527
|
+
}
|
|
528
|
+
validateLeafFilter(value, property, path) {
|
|
529
|
+
const result = leafFilterSchema(property).safeParse(value);
|
|
530
|
+
return result.success ? [] : formatZodIssues(result.error, path);
|
|
531
|
+
}
|
|
532
|
+
validateSort(sort, view, path) {
|
|
533
|
+
const shape = {};
|
|
534
|
+
for (const property of NODE_PROPERTIES2) {
|
|
535
|
+
shape[property] = SORT_DIRECTION_SCHEMA.optional();
|
|
536
|
+
}
|
|
537
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
538
|
+
if (isViewPropertyDefinition(property) && property.type.list !== true) {
|
|
539
|
+
shape[name] = SORT_DIRECTION_SCHEMA.optional();
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const result = z.object(shape).strict().safeParse(sort);
|
|
543
|
+
return result.success ? [] : formatZodIssues(result.error, path);
|
|
544
|
+
}
|
|
545
|
+
};
|
|
205
546
|
|
|
206
547
|
// src/mappers/sort-mapper.ts
|
|
207
548
|
var SortMapper = class {
|
|
@@ -227,6 +568,7 @@ var QueryMapper = class {
|
|
|
227
568
|
this.viewMapper = viewMapper;
|
|
228
569
|
this.filterMapper = new FilterMapper(viewMapper);
|
|
229
570
|
this.sortMapper = new SortMapper();
|
|
571
|
+
this.validator = new QueryValidator(viewMapper);
|
|
230
572
|
}
|
|
231
573
|
async map(options) {
|
|
232
574
|
const {
|
|
@@ -239,6 +581,7 @@ var QueryMapper = class {
|
|
|
239
581
|
} = options;
|
|
240
582
|
const limit = requestedLimit === -1 ? DEFAULT_LIMIT : requestedLimit;
|
|
241
583
|
const rootView = await this.viewMapper.getView(viewExternalId);
|
|
584
|
+
await this.validator.validate(options, rootView);
|
|
242
585
|
const rootViewRef = toViewReference(rootView);
|
|
243
586
|
const whereFilters = filters ? await this.filterMapper.map(filters, rootView) : [];
|
|
244
587
|
const baseFilters = [{ hasData: [rootViewRef] }, ...whereFilters];
|
|
@@ -529,6 +872,92 @@ var QueryResultMapper = class {
|
|
|
529
872
|
return entry;
|
|
530
873
|
}
|
|
531
874
|
};
|
|
875
|
+
var nodeMetadataSchema = {
|
|
876
|
+
instanceType: z.literal("node").optional(),
|
|
877
|
+
space: z.string(),
|
|
878
|
+
externalId: z.string(),
|
|
879
|
+
version: z.number().optional(),
|
|
880
|
+
createdTime: z.number().optional(),
|
|
881
|
+
deletedTime: z.number().optional(),
|
|
882
|
+
lastUpdatedTime: z.number().optional(),
|
|
883
|
+
_edges: z.record(z.string(), z.unknown()).optional()
|
|
884
|
+
};
|
|
885
|
+
function isListRelation(property) {
|
|
886
|
+
if (isViewPropertyDefinition(property)) {
|
|
887
|
+
return isListDirectRelation(property);
|
|
888
|
+
}
|
|
889
|
+
if (isReverseDirectRelation(property)) {
|
|
890
|
+
return property.connectionType === "multi_reverse_direct_relation" || property.targetsList === true;
|
|
891
|
+
}
|
|
892
|
+
return isEdgeConnection(property);
|
|
893
|
+
}
|
|
894
|
+
function isRecord2(value) {
|
|
895
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
896
|
+
}
|
|
897
|
+
var QueryResultValidator = class {
|
|
898
|
+
constructor(viewMapper) {
|
|
899
|
+
this.viewMapper = viewMapper;
|
|
900
|
+
}
|
|
901
|
+
async parseItems(rootViewExternalId, items, select) {
|
|
902
|
+
const rootView = await this.viewMapper.getView(rootViewExternalId);
|
|
903
|
+
const schema = await this.buildResultSchema(rootView, MAX_DEPENDENCY_DEPTH, select);
|
|
904
|
+
const result = z.array(schema).safeParse(items);
|
|
905
|
+
if (!result.success) {
|
|
906
|
+
throw new Error(
|
|
907
|
+
`Invalid query result:
|
|
908
|
+
${result.error.issues.map((issue) => `- ${issue.path.map(String).join(".")}: ${issue.message}`).join("\n")}`
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
return result.data;
|
|
912
|
+
}
|
|
913
|
+
async buildResultSchema(view, remainingDepth, select) {
|
|
914
|
+
const shape = { ...nodeMetadataSchema };
|
|
915
|
+
const includeAllProperties = select == null || select._all === true;
|
|
916
|
+
for (const [name, property] of Object.entries(view.properties)) {
|
|
917
|
+
const isSelected = includeAllProperties || name in select;
|
|
918
|
+
if (!isSelected) continue;
|
|
919
|
+
const nestedSelect = isRecord2(select?.[name]) ? select[name] : void 0;
|
|
920
|
+
if (isViewPropertyDefinition(property)) {
|
|
921
|
+
const relationSource = getDirectRelationSource(property);
|
|
922
|
+
if (relationSource) {
|
|
923
|
+
shape[name] = await this.buildRelationSchema(
|
|
924
|
+
property,
|
|
925
|
+
relationSource.externalId,
|
|
926
|
+
remainingDepth,
|
|
927
|
+
nestedSelect
|
|
928
|
+
);
|
|
929
|
+
} else {
|
|
930
|
+
shape[name] = propertyValueSchema(property, { dateMode: "coerce" }).optional();
|
|
931
|
+
}
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
|
|
935
|
+
shape[name] = await this.buildRelationSchema(
|
|
936
|
+
property,
|
|
937
|
+
property.source.externalId,
|
|
938
|
+
remainingDepth,
|
|
939
|
+
nestedSelect
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
const schema = z.object(shape);
|
|
944
|
+
return includeAllProperties ? schema.strict() : schema;
|
|
945
|
+
}
|
|
946
|
+
async buildRelationSchema(property, targetViewExternalId, remainingDepth, select) {
|
|
947
|
+
const isList = isListRelation(property);
|
|
948
|
+
const fallbackSchema = isViewPropertyDefinition(property) ? propertyValueSchema(property, { dateMode: "coerce" }) : z.unknown();
|
|
949
|
+
if (remainingDepth <= 0 || select == null) {
|
|
950
|
+
return fallbackSchema.optional();
|
|
951
|
+
}
|
|
952
|
+
const targetView = await this.viewMapper.getView(targetViewExternalId);
|
|
953
|
+
const nestedSchema = await this.buildResultSchema(targetView, remainingDepth - 1, select);
|
|
954
|
+
if (isViewPropertyDefinition(property)) {
|
|
955
|
+
const nestedRelationSchema = isList ? z.array(nestedSchema) : nestedSchema;
|
|
956
|
+
return z.union([nestedRelationSchema, fallbackSchema]).optional();
|
|
957
|
+
}
|
|
958
|
+
return (isList ? z.array(nestedSchema) : nestedSchema).optional();
|
|
959
|
+
}
|
|
960
|
+
};
|
|
532
961
|
|
|
533
962
|
// src/mappers/view-mapper.ts
|
|
534
963
|
var ViewMapper = class {
|
|
@@ -679,15 +1108,18 @@ function buildDependenciesQuery(previousQuery, nodesParent, nodesChildren, leafC
|
|
|
679
1108
|
|
|
680
1109
|
// src/client.ts
|
|
681
1110
|
var IndustrialModelClient = class {
|
|
682
|
-
constructor(client, dataModelId) {
|
|
1111
|
+
constructor(client, dataModelId, options = {}) {
|
|
683
1112
|
const cognite = createCogniteAdapter(client);
|
|
684
1113
|
this.cognite = cognite;
|
|
685
1114
|
const viewMapper = new ViewMapper(cognite, dataModelId);
|
|
686
1115
|
this.queryMapper = new QueryMapper(viewMapper);
|
|
687
1116
|
this.resultMapper = new QueryResultMapper(viewMapper);
|
|
1117
|
+
this.resultValidator = new QueryResultValidator(viewMapper);
|
|
1118
|
+
this.validateResults = options.validateResults ?? false;
|
|
688
1119
|
}
|
|
689
1120
|
query() {
|
|
690
|
-
|
|
1121
|
+
const execute = (options) => this.queryInternal(options);
|
|
1122
|
+
return execute;
|
|
691
1123
|
}
|
|
692
1124
|
async queryInternal(options) {
|
|
693
1125
|
const { viewExternalId, limit = DEFAULT_LIMIT } = options;
|
|
@@ -705,7 +1137,8 @@ var IndustrialModelClient = class {
|
|
|
705
1137
|
mapNodesAndEdges(queryResult),
|
|
706
1138
|
dependenciesData
|
|
707
1139
|
);
|
|
708
|
-
const
|
|
1140
|
+
const mappedPageResult = await this.resultMapper.mapNodes(viewExternalId, queryResultData);
|
|
1141
|
+
const pageResult = this.validateResults ? await this.resultValidator.parseItems(viewExternalId, mappedPageResult, options.select) : mappedPageResult;
|
|
709
1142
|
const nextCursor = queryResult.nextCursor[viewExternalId] ?? null;
|
|
710
1143
|
const isLastPage = pageResult.length < limit || !nextCursor;
|
|
711
1144
|
const resolvedCursor = isLastPage ? null : nextCursor;
|
|
@@ -736,6 +1169,6 @@ var IndustrialModelClient = class {
|
|
|
736
1169
|
}
|
|
737
1170
|
};
|
|
738
1171
|
|
|
739
|
-
export { IndustrialModelClient };
|
|
1172
|
+
export { IndustrialModelClient, buildViewSchema, nodeIdSchema };
|
|
740
1173
|
//# sourceMappingURL=index.js.map
|
|
741
1174
|
//# sourceMappingURL=index.js.map
|