industrial-model 0.3.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 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,9 +61,10 @@ 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) |
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) |
66
68
  | Advanced | [Custom data model](#use-a-custom-data-model), [Full query](#full-example-assets-equipment-and-filters) |
67
69
 
68
70
  All examples below use the [Cognite Core Data Model](https://docs.cognite.com/cdf/data_modeling/reference_data_models/cognite_core/), space `cdf_cdm`, version `v1`.
@@ -468,6 +470,52 @@ const fullyTagged = await model.query<CogniteAsset>()({
468
470
 
469
471
  ---
470
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
+
471
519
  ### Filter on related nodes
472
520
 
473
521
  Filter the root view based on properties of a direct or nested relation. This uses Cognite nested filters under the hood.
@@ -661,6 +709,135 @@ if (cursor) {
661
709
 
662
710
  ---
663
711
 
712
+ ### Count assets by source ID
713
+
714
+ Group assets and count how many share each `sourceId`. Uses the same `filters` syntax as `query`.
715
+
716
+ ```ts
717
+ const { items } = await model.aggregate<CogniteAsset>()({
718
+ viewExternalId: "CogniteAsset",
719
+ groupBy: { sourceId: true },
720
+ aggregate: { count: {} },
721
+ filters: { name: { prefix: "WMT" } },
722
+ });
723
+
724
+ for (const row of items) {
725
+ console.log(row.group?.sourceId, row.aggregate?.value);
726
+ }
727
+ ```
728
+
729
+ ---
730
+
731
+ ### List distinct source IDs
732
+
733
+ Omit `aggregate` to return unique combinations of the `groupBy` fields (up to 1000 groups).
734
+
735
+ ```ts
736
+ const { items } = await model.aggregate<CogniteAsset>()({
737
+ viewExternalId: "CogniteAsset",
738
+ groupBy: { sourceId: true },
739
+ });
740
+
741
+ const sourceIds = items.map((row) => row.group?.sourceId);
742
+ ```
743
+
744
+ ---
745
+
746
+ ### Average volume by type
747
+
748
+ Use `avg`, `min`, `max`, or `sum` on numeric view properties. Only one aggregate operation per call.
749
+
750
+ ```ts
751
+ type PointCloudVolume = IndustrialModel<{
752
+ volume: number;
753
+ volumeType: string;
754
+ }>;
755
+
756
+ const { items } = await model.aggregate<PointCloudVolume>()({
757
+ viewExternalId: "CognitePointCloudVolume",
758
+ groupBy: { volumeType: true },
759
+ aggregate: { avg: "volume" },
760
+ });
761
+
762
+ items[0]?.group?.volumeType;
763
+ items[0]?.aggregate?.property; // "volume"
764
+ items[0]?.aggregate?.value;
765
+ ```
766
+
767
+ Other numeric aggregates:
768
+
769
+ ```ts
770
+ await model.aggregate<PointCloudVolume>()({
771
+ viewExternalId: "CognitePointCloudVolume",
772
+ aggregate: { min: "volume" },
773
+ });
774
+
775
+ await model.aggregate<PointCloudVolume>()({
776
+ viewExternalId: "CognitePointCloudVolume",
777
+ aggregate: { max: "volume" },
778
+ });
779
+
780
+ await model.aggregate<PointCloudVolume>()({
781
+ viewExternalId: "CognitePointCloudVolume",
782
+ aggregate: { sum: "volume" },
783
+ });
784
+ ```
785
+
786
+ ---
787
+
788
+ ### Count non-null values for a property
789
+
790
+ ```ts
791
+ const { items } = await model.aggregate<CogniteAsset>()({
792
+ viewExternalId: "CogniteAsset",
793
+ aggregate: { count: "name" },
794
+ });
795
+
796
+ items[0]?.aggregate?.property; // "name"
797
+ items[0]?.aggregate?.value;
798
+ ```
799
+
800
+ ---
801
+
802
+ ### Count all matching assets
803
+
804
+ A global count with no `groupBy`:
805
+
806
+ ```ts
807
+ const { items } = await model.aggregate<CogniteAsset>()({
808
+ viewExternalId: "CogniteAsset",
809
+ aggregate: { count: {} },
810
+ filters: {
811
+ OR: [{ tags: { containsAny: ["critical"] } }, { sourceId: { eq: "sap" } }],
812
+ },
813
+ });
814
+
815
+ items[0]?.aggregate?.value;
816
+ ```
817
+
818
+ ---
819
+
820
+ ### Group by a direct relation
821
+
822
+ `groupBy` supports direct relations; results are returned as `NodeId` objects.
823
+
824
+ ```ts
825
+ type PointCloudVolume = IndustrialModel<{
826
+ volume: number;
827
+ object3D?: NodeId;
828
+ }>;
829
+
830
+ const { items } = await model.aggregate<PointCloudVolume>()({
831
+ viewExternalId: "CognitePointCloudVolume",
832
+ groupBy: { object3D: true },
833
+ aggregate: { sum: "volume" },
834
+ });
835
+
836
+ items[0]?.group?.object3D?.externalId;
837
+ ```
838
+
839
+ ---
840
+
664
841
  ## API
665
842
 
666
843
  ### Exports
@@ -672,6 +849,8 @@ if (cursor) {
672
849
  | `NodeId`, `DataModelId` | Instance and data-model identifiers |
673
850
  | `QueryOptions`, `QuerySelect`, `WhereInput`, `SortInput` | Query input types |
674
851
  | `QueryResult`, `QueryResultItem`, `QueryResultMetadata` | Query output types |
852
+ | `AggregateOptions`, `AggregateGroupBy`, `AggregateDefinition` | Aggregate input types |
853
+ | `AggregateResult`, `AggregateResultItem`, `GroupByKey` | Aggregate output types |
675
854
  | `buildViewSchema`, `nodeIdSchema` | Zod schemas built from Cognite view metadata |
676
855
  | `SortDirection` | `"ascending"` \| `"descending"` |
677
856
 
@@ -737,6 +916,39 @@ items[0]?.parent?.name;
737
916
  items[0]?.externalId;
738
917
  ```
739
918
 
919
+ ### `model.aggregate<TModel>()(options)`
920
+
921
+ | Option | Type | Description |
922
+ |--------|------|-------------|
923
+ | `viewExternalId` | `string` | The view to aggregate |
924
+ | `groupBy` | `AggregateGroupBy<TModel>` | Optional. Object of groupable properties set to `true` (max 5) |
925
+ | `filters` | `WhereInput<TModel>` | Same filter syntax as `query` |
926
+ | `aggregate` | `AggregateDefinition<TModel>` | Optional. One of `avg`, `min`, `max`, `sum`, or `count` per call |
927
+
928
+ Provide at least one of `groupBy` or `aggregate`. Omit `aggregate` to fetch distinct values for the grouped fields. The client always requests nodes with `limit: 1000`.
929
+
930
+ `aggregate()` uses the same curried form as `query`. Each result item has the shape:
931
+
932
+ ```ts
933
+ type AggregateResultItem = {
934
+ group?: { /* keys from groupBy */ };
935
+ aggregate?: { property?: string; value: number };
936
+ };
937
+ ```
938
+
939
+ When counting all rows, `aggregate` has only `value` (no `property`). When aggregating a field, `property` matches the name you passed in the request.
940
+
941
+ | Aggregate | Input | Use case |
942
+ |-----------|-------|----------|
943
+ | `count` | `{ count: {} }` | Row count (optionally filtered) |
944
+ | `count` | `{ count: "name" }` | Count non-null values for a property |
945
+ | `avg` | `{ avg: "volume" }` | Average of a numeric property |
946
+ | `min` | `{ min: "volume" }` | Minimum numeric value |
947
+ | `max` | `{ max: "volume" }` | Maximum numeric value |
948
+ | `sum` | `{ sum: "volume" }` | Sum of a numeric property |
949
+
950
+ See [Count assets by source ID](#count-assets-by-source-id), [List distinct source IDs](#list-distinct-source-ids), and [Average volume by type](#average-volume-by-type) for full examples.
951
+
740
952
  ### Automatic query behavior
741
953
 
742
954
  - **`hasData` filter** — every root query includes a `hasData` constraint for the target view.
@@ -747,15 +959,18 @@ items[0]?.externalId;
747
959
 
748
960
  | Type | Operators |
749
961
  |------|-----------|
750
- | `string` | `eq`, `in`, `prefix`, `exists` |
962
+ | `string` | `eq`, `in`, `prefix`, `search`, `exists` |
751
963
  | `number` | `eq`, `in`, `gt`, `gte`, `lt`, `lte`, `exists` |
752
964
  | `boolean` | `eq`, `exists` |
753
965
  | timestamp / `Date` | `eq`, `in`, `gt`, `gte`, `lt`, `lte`, `exists` — use ISO strings or `Date` values (coerced to ISO) |
754
966
  | `NodeId` | `eq`, `in`, `exists` |
967
+ | `string[]` | `containsAny`, `containsAll`, `search`, `exists` |
755
968
  | `T[]` | `containsAny`, `containsAll`, `exists` |
756
969
 
757
970
  Logical combinators `AND`, `OR`, and `NOT` are supported at any nesting level, including inside nested relation filters (e.g. `parent: { OR: [...] }`).
758
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
+
759
974
  ### Relation traversal
760
975
 
761
976
  - **Direct relations** — `parent`, `asset`, `unit` (outwards from the current node). List relations such as `path` return arrays when expanded.