industrial-model 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -2
- package/dist/index.cjs +64 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +64 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ TypeScript SDK for querying [Cognite Flexible Data Models (FDM)](https://docs.co
|
|
|
6
6
|
|
|
7
7
|
- **Type-safe queries** — define your data model types once, get compile-time validation on filters, selects, and sorts
|
|
8
8
|
- **Relation traversal** — query nested relations (edges/nodes) up to 3 levels deep with automatic pagination
|
|
9
|
+
- **Text search filters** — use Cognite `instances.search` from query and aggregate filters on text fields
|
|
9
10
|
- **Dual CJS/ESM** — works in Node.js and bundlers out of the box
|
|
10
11
|
- **Cursor-based pagination** — built-in support for iterating large result sets
|
|
11
12
|
|
|
@@ -60,7 +61,7 @@ const { items } = await model.query<{ name: string; description: string }>()({
|
|
|
60
61
|
| Setup & types | [Shared type definitions](#shared-type-definitions) |
|
|
61
62
|
| Basic queries | [Query assets](#query-assets), [Single asset](#query-a-single-asset-by-externalid) |
|
|
62
63
|
| Relations | [Parent/root](#query-assets-with-parent-and-root-relations), [Path](#query-assets-with-their-full-path), [Children](#query-child-assets-reverse-relation), [Edges](#traverse-edge-relations-360-images-on-3d-objects) |
|
|
63
|
-
| Filters | [AND/OR/NOT](#combine-filters-with-and--or--not), [Nested](#filter-on-related-nodes), [Tags](#filter-assets-by-tags), [Batch IDs](#filter-by-multiple-external-ids) |
|
|
64
|
+
| Filters | [AND/OR/NOT](#combine-filters-with-and--or--not), [Text search](#search-text-fields), [Nested](#filter-on-related-nodes), [Tags](#filter-assets-by-tags), [Batch IDs](#filter-by-multiple-external-ids) |
|
|
64
65
|
| Select & sort | [Select all scalars](#select-all-scalar-fields), [Multi-field sort](#sort-by-multiple-fields) |
|
|
65
66
|
| Pagination | [Manual cursor loop](#paginate-through-all-assets), [Fetch all pages](#fetch-all-pages-automatically) |
|
|
66
67
|
| Aggregation | [Count by group](#count-assets-by-source-id), [Distinct values](#list-distinct-source-ids), [Numeric aggregates](#average-volume-by-type), [Global count](#count-all-matching-assets) |
|
|
@@ -469,6 +470,52 @@ const fullyTagged = await model.query<CogniteAsset>()({
|
|
|
469
470
|
|
|
470
471
|
---
|
|
471
472
|
|
|
473
|
+
### Search text fields
|
|
474
|
+
|
|
475
|
+
Use `search` on text properties and string-list text properties when you want Cognite full-text matching instead of exact or prefix filters. The optional `operator` is passed to Cognite search and defaults to `"OR"`; use `"AND"` when every term should match.
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
const { items } = await model.query<CogniteAsset>()({
|
|
479
|
+
viewExternalId: "CogniteAsset",
|
|
480
|
+
select: { name: true, description: true, tags: true },
|
|
481
|
+
filters: {
|
|
482
|
+
name: { search: { query: "root pump", operator: "AND" } },
|
|
483
|
+
tags: { search: { query: "critical" } },
|
|
484
|
+
},
|
|
485
|
+
limit: 100,
|
|
486
|
+
});
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Search filters can be combined with normal field operators. The SDK first calls Cognite `instances.search`, then adds the returned node references to the regular query filter.
|
|
490
|
+
|
|
491
|
+
```ts
|
|
492
|
+
const pumps = await model.query<CogniteAsset>()({
|
|
493
|
+
viewExternalId: "CogniteAsset",
|
|
494
|
+
select: { name: true, sourceId: true },
|
|
495
|
+
filters: {
|
|
496
|
+
name: {
|
|
497
|
+
prefix: "Pump",
|
|
498
|
+
search: { query: "motor" },
|
|
499
|
+
},
|
|
500
|
+
sourceId: { exists: true },
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
The same filter syntax is also supported by `aggregate`:
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
const { items } = await model.aggregate<CogniteAsset>()({
|
|
509
|
+
viewExternalId: "CogniteAsset",
|
|
510
|
+
aggregate: { count: {} },
|
|
511
|
+
filters: {
|
|
512
|
+
name: { search: { query: "compressor seal" } },
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
472
519
|
### Filter on related nodes
|
|
473
520
|
|
|
474
521
|
Filter the root view based on properties of a direct or nested relation. This uses Cognite nested filters under the hood.
|
|
@@ -912,15 +959,18 @@ See [Count assets by source ID](#count-assets-by-source-id), [List distinct sour
|
|
|
912
959
|
|
|
913
960
|
| Type | Operators |
|
|
914
961
|
|------|-----------|
|
|
915
|
-
| `string` | `eq`, `in`, `prefix`, `exists` |
|
|
962
|
+
| `string` | `eq`, `in`, `prefix`, `search`, `exists` |
|
|
916
963
|
| `number` | `eq`, `in`, `gt`, `gte`, `lt`, `lte`, `exists` |
|
|
917
964
|
| `boolean` | `eq`, `exists` |
|
|
918
965
|
| timestamp / `Date` | `eq`, `in`, `gt`, `gte`, `lt`, `lte`, `exists` — use ISO strings or `Date` values (coerced to ISO) |
|
|
919
966
|
| `NodeId` | `eq`, `in`, `exists` |
|
|
967
|
+
| `string[]` | `containsAny`, `containsAll`, `search`, `exists` |
|
|
920
968
|
| `T[]` | `containsAny`, `containsAll`, `exists` |
|
|
921
969
|
|
|
922
970
|
Logical combinators `AND`, `OR`, and `NOT` are supported at any nesting level, including inside nested relation filters (e.g. `parent: { OR: [...] }`).
|
|
923
971
|
|
|
972
|
+
`search` is available for Cognite text properties and string-list text properties. It is not accepted on node metadata fields such as `externalId` or `space`. Each `search` filter uses `instances.search` with `limit: 1000`, maps the matched nodes to `instanceReferences`, and applies those references to the query or aggregate request.
|
|
973
|
+
|
|
924
974
|
### Relation traversal
|
|
925
975
|
|
|
926
976
|
- **Direct relations** — `parent`, `asset`, `unit` (outwards from the current node). List relations such as `path` return arrays when expanded.
|
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,13 @@ var CogniteSdkAdapter = class {
|
|
|
28
28
|
nextCursor: response.nextCursor
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
+
async searchInstances(request) {
|
|
32
|
+
const search = this.client.instances.search;
|
|
33
|
+
const response = await search(request);
|
|
34
|
+
return {
|
|
35
|
+
items: response.items
|
|
36
|
+
};
|
|
37
|
+
}
|
|
31
38
|
async aggregateInstances(request) {
|
|
32
39
|
const response = await this.client.instances.aggregate(
|
|
33
40
|
request
|
|
@@ -186,6 +193,7 @@ var leafOps = /* @__PURE__ */ new Set([
|
|
|
186
193
|
"lte",
|
|
187
194
|
"exists",
|
|
188
195
|
"prefix",
|
|
196
|
+
"search",
|
|
189
197
|
"containsAny",
|
|
190
198
|
"containsAll"
|
|
191
199
|
]);
|
|
@@ -238,19 +246,33 @@ function leafFilterSchema(property) {
|
|
|
238
246
|
const value = baseValueSchema(property);
|
|
239
247
|
const isList = typeof property !== "string" && property.type.list === true;
|
|
240
248
|
if (isList) {
|
|
241
|
-
|
|
249
|
+
const shape = {
|
|
242
250
|
containsAny: zod.z.array(value).optional(),
|
|
243
251
|
containsAll: zod.z.array(value).optional(),
|
|
244
252
|
exists: zod.z.boolean().optional()
|
|
245
|
-
}
|
|
253
|
+
};
|
|
254
|
+
if (property.type.type === "text") {
|
|
255
|
+
shape.search = zod.z.object({
|
|
256
|
+
query: zod.z.string(),
|
|
257
|
+
operator: zod.z.enum(["OR", "AND"]).optional()
|
|
258
|
+
}).strict().optional();
|
|
259
|
+
}
|
|
260
|
+
return zod.z.object(shape).strict();
|
|
246
261
|
}
|
|
247
262
|
if (property === "node-string" || typeof property !== "string" && property.type.type === "text") {
|
|
248
|
-
|
|
263
|
+
const shape = {
|
|
249
264
|
eq: zod.z.string().optional(),
|
|
250
265
|
in: zod.z.array(zod.z.string()).optional(),
|
|
251
266
|
prefix: zod.z.string().optional(),
|
|
252
267
|
exists: zod.z.boolean().optional()
|
|
253
|
-
}
|
|
268
|
+
};
|
|
269
|
+
if (property !== "node-string") {
|
|
270
|
+
shape.search = zod.z.object({
|
|
271
|
+
query: zod.z.string(),
|
|
272
|
+
operator: zod.z.enum(["OR", "AND"]).optional()
|
|
273
|
+
}).strict().optional();
|
|
274
|
+
}
|
|
275
|
+
return zod.z.object(shape).strict();
|
|
254
276
|
}
|
|
255
277
|
if (typeof property !== "string" && property.type.type === "enum") {
|
|
256
278
|
return zod.z.object({
|
|
@@ -610,11 +632,13 @@ function isLeafFilter2(value) {
|
|
|
610
632
|
return Object.keys(value).some((k) => LEAF_OPS.has(k));
|
|
611
633
|
}
|
|
612
634
|
var FilterMapper = class {
|
|
613
|
-
constructor(viewMapper) {
|
|
635
|
+
constructor(viewMapper, cognite) {
|
|
614
636
|
this.viewMapper = viewMapper;
|
|
637
|
+
this.cognite = cognite;
|
|
615
638
|
}
|
|
616
639
|
async map(input, rootView) {
|
|
617
640
|
const result = [];
|
|
641
|
+
const searchQueries = {};
|
|
618
642
|
for (const [key, value] of Object.entries(input)) {
|
|
619
643
|
if (value == null) continue;
|
|
620
644
|
if (key === "AND") {
|
|
@@ -635,17 +659,44 @@ var FilterMapper = class {
|
|
|
635
659
|
} else {
|
|
636
660
|
const filterValue = value;
|
|
637
661
|
const property = getPropertyRef(key, rootView);
|
|
638
|
-
|
|
662
|
+
const hasLeafFilter = isLeafFilter2(filterValue);
|
|
663
|
+
if (hasLeafFilter) {
|
|
639
664
|
result.push(...this.leafToFilterDefs(property, filterValue));
|
|
640
|
-
}
|
|
665
|
+
}
|
|
666
|
+
if ("search" in filterValue && filterValue.search != null) {
|
|
667
|
+
searchQueries[key] = filterValue.search;
|
|
668
|
+
} else if (!hasLeafFilter) {
|
|
641
669
|
const targetView = await this.getNestedTargetView(key, rootView);
|
|
642
670
|
const innerFilter = await this.whereInputToSingle(filterValue, targetView);
|
|
643
671
|
result.push({ nested: { scope: property, filter: innerFilter } });
|
|
644
672
|
}
|
|
645
673
|
}
|
|
646
674
|
}
|
|
675
|
+
if (Object.keys(searchQueries).length > 0) {
|
|
676
|
+
const searchFilters = await Promise.all(
|
|
677
|
+
Object.entries(searchQueries).map(
|
|
678
|
+
([key, filterValue]) => this.searchToFilterDef(key, filterValue, rootView)
|
|
679
|
+
)
|
|
680
|
+
);
|
|
681
|
+
result.push(...searchFilters);
|
|
682
|
+
}
|
|
647
683
|
return result;
|
|
648
684
|
}
|
|
685
|
+
async searchToFilterDef(propertyName, search, rootView) {
|
|
686
|
+
const response = await this.cognite.searchInstances({
|
|
687
|
+
view: toViewReference(rootView),
|
|
688
|
+
query: search.query,
|
|
689
|
+
instanceType: "node",
|
|
690
|
+
properties: [propertyName],
|
|
691
|
+
operator: search.operator ?? "OR",
|
|
692
|
+
limit: 1e3
|
|
693
|
+
});
|
|
694
|
+
const instanceRefs = response.items.map((item) => ({
|
|
695
|
+
space: item.space,
|
|
696
|
+
externalId: item.externalId
|
|
697
|
+
}));
|
|
698
|
+
return { instanceReferences: instanceRefs };
|
|
699
|
+
}
|
|
649
700
|
async whereInputToSingle(input, rootView) {
|
|
650
701
|
const filters = await this.map(input, rootView);
|
|
651
702
|
const [firstFilter, ...restFilters] = filters;
|
|
@@ -724,9 +775,9 @@ var FilterMapper = class {
|
|
|
724
775
|
|
|
725
776
|
// src/mappers/aggregate-mapper.ts
|
|
726
777
|
var AggregateMapper = class {
|
|
727
|
-
constructor(viewMapper) {
|
|
778
|
+
constructor(viewMapper, cognite) {
|
|
728
779
|
this.viewMapper = viewMapper;
|
|
729
|
-
this.filterMapper = new FilterMapper(viewMapper);
|
|
780
|
+
this.filterMapper = new FilterMapper(viewMapper, cognite);
|
|
730
781
|
this.validator = new AggregateValidator(viewMapper);
|
|
731
782
|
}
|
|
732
783
|
async map(options) {
|
|
@@ -821,9 +872,9 @@ var SortMapper = class {
|
|
|
821
872
|
|
|
822
873
|
// src/mappers/query-mapper.ts
|
|
823
874
|
var QueryMapper = class {
|
|
824
|
-
constructor(viewMapper) {
|
|
875
|
+
constructor(viewMapper, cognite) {
|
|
825
876
|
this.viewMapper = viewMapper;
|
|
826
|
-
this.filterMapper = new FilterMapper(viewMapper);
|
|
877
|
+
this.filterMapper = new FilterMapper(viewMapper, cognite);
|
|
827
878
|
this.sortMapper = new SortMapper();
|
|
828
879
|
this.validator = new QueryValidator(viewMapper);
|
|
829
880
|
}
|
|
@@ -1369,8 +1420,8 @@ var IndustrialModelClient = class {
|
|
|
1369
1420
|
const cognite = createCogniteAdapter(client);
|
|
1370
1421
|
this.cognite = cognite;
|
|
1371
1422
|
const viewMapper = new ViewMapper(cognite, dataModelId);
|
|
1372
|
-
this.queryMapper = new QueryMapper(viewMapper);
|
|
1373
|
-
this.aggregateMapper = new AggregateMapper(viewMapper);
|
|
1423
|
+
this.queryMapper = new QueryMapper(viewMapper, cognite);
|
|
1424
|
+
this.aggregateMapper = new AggregateMapper(viewMapper, cognite);
|
|
1374
1425
|
this.aggregateResultMapper = new AggregateResultMapper();
|
|
1375
1426
|
this.resultMapper = new QueryResultMapper(viewMapper);
|
|
1376
1427
|
this.resultValidator = new QueryResultValidator(viewMapper);
|