industrial-model 0.4.0 → 0.6.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
@@ -2,31 +2,36 @@
2
2
 
3
3
  TypeScript SDK for querying [Cognite Flexible Data Models (FDM)](https://docs.cognite.com/cdf/data_modeling/) with a type-safe, graph-aware API.
4
4
 
5
- ## Features
5
+ `industrial-model` is designed for application code that needs to move through industrial data as a model, not as loosely typed query payloads. Start with a Cognite data model, describe the view shape in TypeScript, and query nodes, relations, filters, sorting, pagination, and aggregations with compiler support.
6
6
 
7
- - **Type-safe queries** — define your data model types once, get compile-time validation on filters, selects, and sorts
8
- - **Relation traversal** — query nested relations (edges/nodes) up to 3 levels deep with automatic pagination
9
- - **Dual CJS/ESM** works in Node.js and bundlers out of the box
10
- - **Cursor-based pagination** — built-in support for iterating large result sets
7
+ ## What You Get
8
+
9
+ - **Typed model queries** - validate selected fields, filters, and sort keys at compile time.
10
+ - **Precise result types** - returned items follow the `select` tree, including nested relation selections.
11
+ - **Graph traversal** - expand direct, reverse, and edge relations up to 3 levels deep.
12
+ - **Industrial filters** - combine scalar filters, list filters, full-text search, and nested relation filters.
13
+ - **Pagination support** - use cursors manually or fetch all root pages with `limit: -1`.
14
+ - **Aggregation support** - count, group, list distinct values, and aggregate numeric properties.
15
+ - **Runtime validation option** - parse query results with Zod schemas derived from Cognite view metadata.
16
+ - **CJS and ESM builds** - works in Node.js and common bundler setups.
11
17
 
12
18
  ## Installation
13
19
 
14
20
  ```bash
15
21
  npm install industrial-model
16
- ```
17
-
18
- `@cognite/sdk` is a peer dependency and must be installed separately:
19
-
20
- ```bash
21
22
  npm install @cognite/sdk
22
23
  ```
23
24
 
25
+ `@cognite/sdk` is a peer dependency and must be installed by your application.
26
+
24
27
  ## Requirements
25
28
 
26
29
  - Node.js `>=20`
27
- - `@cognite/sdk` `^10.10.0` (peer dependency)
30
+ - `@cognite/sdk` `^10.10.0`
28
31
 
29
- ## Quick start
32
+ ## First Query
33
+
34
+ Create a Cognite SDK client, point `IndustrialModelClient` at a data model, and query a view.
30
35
 
31
36
  ```ts
32
37
  import { CogniteClient } from "@cognite/sdk";
@@ -47,58 +52,55 @@ const model = new IndustrialModelClient(client, {
47
52
 
48
53
  const { items } = await model.query<{ name: string; description: string }>()({
49
54
  viewExternalId: "CogniteAsset",
50
- select: { name: true, description: true },
51
- filters: { name: { prefix: "Pump" } },
55
+ select: {
56
+ name: true,
57
+ description: true,
58
+ },
59
+ filters: {
60
+ name: { prefix: "Pump" },
61
+ },
62
+ sort: {
63
+ name: "ascending",
64
+ },
52
65
  limit: 10,
53
66
  });
67
+
68
+ items[0]?.name;
54
69
  ```
55
70
 
56
- ## Examples
71
+ This is the basic contract:
57
72
 
58
- | Topic | Section |
59
- |-------|---------|
60
- | Setup & types | [Shared type definitions](#shared-type-definitions) |
61
- | Basic queries | [Query assets](#query-assets), [Single asset](#query-a-single-asset-by-externalid) |
62
- | 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
- | Select & sort | [Select all scalars](#select-all-scalar-fields), [Multi-field sort](#sort-by-multiple-fields) |
65
- | Pagination | [Manual cursor loop](#paginate-through-all-assets), [Fetch all pages](#fetch-all-pages-automatically) |
66
- | 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) |
67
- | Advanced | [Custom data model](#use-a-custom-data-model), [Full query](#full-example-assets-equipment-and-filters) |
73
+ 1. `viewExternalId` selects the Cognite view.
74
+ 2. The generic type describes the fields you want TypeScript to understand.
75
+ 3. `select` controls the returned shape.
76
+ 4. `filters`, `sort`, `limit`, and `cursor` control the query behavior.
68
77
 
69
- 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`.
78
+ ## Define A Model
70
79
 
71
- ### Shared type definitions
80
+ For scalar-only views, a plain object type is enough:
72
81
 
73
82
  ```ts
74
- import type {
75
- IndustrialModel,
76
- ModelProps,
77
- ModelRelations,
78
- NodeId,
79
- QueryResultItem,
80
- } from "industrial-model";
81
-
82
- type CogniteAssetClass = IndustrialModel<{
83
+ type CogniteAssetClass = {
83
84
  name: string;
84
85
  code: string;
85
- }>;
86
+ };
87
+ ```
88
+
89
+ When a view has expandable relations, use `IndustrialModel<TProps, TRelations>`. Put the raw view properties in `TProps`, and put expandable relation result shapes in `TRelations`.
90
+
91
+ ```ts
92
+ import type { IndustrialModel, ModelProps, ModelRelations, NodeId } from "industrial-model";
86
93
 
87
94
  type CogniteAsset = IndustrialModel<
88
95
  {
89
96
  name: string;
90
97
  description: string;
91
98
  tags: string[];
92
- aliases: string[];
93
99
  sourceId: string;
94
- sourceCreatedTime: string;
95
- sourceUpdatedTime: string;
96
100
  parent?: NodeId;
97
101
  root?: NodeId;
98
102
  path: NodeId[];
99
103
  assetClass?: NodeId;
100
- type?: NodeId;
101
- source?: NodeId;
102
104
  },
103
105
  {
104
106
  parent?: CogniteAsset;
@@ -108,77 +110,27 @@ type CogniteAsset = IndustrialModel<
108
110
  }
109
111
  >;
110
112
 
111
- type CogniteActivity = IndustrialModel<{
112
- name: string;
113
- description: string;
114
- startTime: string;
115
- endTime: string;
116
- scheduledStartTime: string;
117
- scheduledEndTime: string;
118
- assets: NodeId[];
119
- equipment: NodeId[];
120
- timeSeries: NodeId[];
121
- }>;
122
-
123
113
  type CogniteEquipment = IndustrialModel<
124
114
  {
125
115
  name: string;
126
- description: string;
127
116
  manufacturer: string;
128
117
  serialNumber: string;
129
118
  tags: string[];
130
119
  asset?: NodeId;
131
- equipmentType?: NodeId;
132
- source?: NodeId;
133
120
  },
134
121
  {
135
122
  asset?: CogniteAsset;
136
- activities?: CogniteActivity[];
137
- }
138
- >;
139
-
140
- type CogniteUnit = IndustrialModel<{
141
- name: string;
142
- symbol: string;
143
- quantity: string;
144
- source: string;
145
- }>;
146
-
147
- type CogniteTimeSeries = IndustrialModel<
148
- {
149
- name: string;
150
- description: string;
151
- isStep: boolean;
152
- sourceUnit: string;
153
- unit?: NodeId;
154
- assets: NodeId[];
155
- equipment: NodeId[];
156
- },
157
- {
158
- unit?: CogniteUnit;
159
- }
160
- >;
161
-
162
- type Cognite360Image = IndustrialModel<{
163
- takenAt: string;
164
- }>;
165
-
166
- type Cognite3DObject = IndustrialModel<
167
- {
168
- name: string;
169
- description: string;
170
- },
171
- {
172
- images360?: Cognite360Image[];
173
123
  }
174
124
  >;
175
125
  ```
176
126
 
177
- ---
127
+ The relation metadata is type-only. It lets the SDK infer nested `select` trees and nested filters while Cognite remains the source of truth for the actual view and relation definitions.
128
+
129
+ 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`.
178
130
 
179
- ### Query assets
131
+ ## Query Basics
180
132
 
181
- Fetch the first 100 assets whose name starts with `"Pump"`, sorted alphabetically.
133
+ Start with a single view, select the fields the application needs, then layer on filters and sorting.
182
134
 
183
135
  ```ts
184
136
  const { items, cursor } = await model.query<CogniteAsset>()({
@@ -192,32 +144,22 @@ const { items, cursor } = await model.query<CogniteAsset>()({
192
144
  filters: {
193
145
  name: { prefix: "Pump" },
194
146
  },
195
- sort: { name: "ascending" },
147
+ sort: {
148
+ name: "ascending",
149
+ },
196
150
  limit: 100,
197
151
  });
198
152
  ```
199
153
 
200
- ---
201
-
202
- ### Query a single asset by externalId
154
+ The returned item type follows the selection:
203
155
 
204
156
  ```ts
205
- const { items } = await model.query<CogniteAsset>()({
206
- viewExternalId: "CogniteAsset",
207
- select: { name: true, description: true, tags: true },
208
- filters: {
209
- externalId: { eq: "WMT:VAL" },
210
- },
211
- });
212
-
213
- const asset = items[0];
157
+ items[0]?.name; // string
158
+ items[0]?.description; // string
159
+ items[0]?.externalId; // instance metadata is always included
214
160
  ```
215
161
 
216
- ---
217
-
218
- ### Query assets with parent and root relations
219
-
220
- Traverse up the asset hierarchy — fetch each asset alongside its direct parent and the root of the tree.
162
+ To find one known instance, filter by `externalId`:
221
163
 
222
164
  ```ts
223
165
  const { items } = await model.query<CogniteAsset>()({
@@ -225,51 +167,31 @@ const { items } = await model.query<CogniteAsset>()({
225
167
  select: {
226
168
  name: true,
227
169
  description: true,
228
- parent: {
229
- name: true,
230
- description: true,
231
- parent: {
232
- name: true,
233
- },
234
- },
235
- root: {
236
- name: true,
237
- },
170
+ tags: true,
238
171
  },
239
172
  filters: {
240
- name: { prefix: "Pump" },
173
+ externalId: { eq: "WMT:VAL" },
241
174
  },
242
- limit: 50,
243
175
  });
244
176
 
245
- const firstParentName = items[0]?.parent?.name;
177
+ const asset = items[0];
246
178
  ```
247
179
 
248
- ---
249
-
250
- ### Query assets with their full path
251
-
252
- The `path` property is a list of `NodeId` references representing the ancestor chain. Use it to reconstruct breadcrumbs.
180
+ Use `_all` when you want every scalar property from the root view:
253
181
 
254
182
  ```ts
255
183
  const { items } = await model.query<CogniteAsset>()({
256
184
  viewExternalId: "CogniteAsset",
257
- select: {
258
- name: true,
259
- path: {
260
- name: true,
261
- description: true,
262
- },
263
- },
264
- filters: {
265
- externalId: { eq: "WMT:VAL" },
266
- },
185
+ select: { _all: true },
186
+ limit: 50,
267
187
  });
268
188
  ```
269
189
 
270
- ---
190
+ `_all` includes scalar fields and relation IDs. Add nested selections when you want relation objects instead of `NodeId` references.
191
+
192
+ ## Filters And Sorting
271
193
 
272
- ### Query equipment linked to an asset
194
+ Filters are typed from your model. String, number, boolean, date, `NodeId`, and list fields each expose the operators that make sense for that field.
273
195
 
274
196
  ```ts
275
197
  const { items } = await model.query<CogniteEquipment>()({
@@ -285,249 +207,157 @@ const { items } = await model.query<CogniteEquipment>()({
285
207
  asset: { eq: { space: "my-space", externalId: "WMT:VAL" } },
286
208
  manufacturer: { exists: true },
287
209
  },
288
- sort: { name: "ascending" },
210
+ sort: {
211
+ name: "ascending",
212
+ },
289
213
  limit: 50,
290
214
  });
291
215
  ```
292
216
 
293
- ---
294
-
295
- ### Query time series with their unit
217
+ Combine conditions with `AND`, `OR`, and `NOT`:
296
218
 
297
219
  ```ts
298
- const { items } = await model.query<CogniteTimeSeries>()({
299
- viewExternalId: "CogniteTimeSeries",
220
+ const { items } = await model.query<CogniteAsset>()({
221
+ viewExternalId: "CogniteAsset",
300
222
  select: {
301
223
  name: true,
302
- description: true,
303
- isStep: true,
304
- sourceUnit: true,
305
- unit: {
306
- name: true,
307
- symbol: true,
308
- quantity: true,
309
- },
224
+ tags: true,
225
+ sourceId: true,
310
226
  },
311
227
  filters: {
312
- isStep: { eq: false },
313
- sourceUnit: { exists: true },
228
+ OR: [
229
+ { tags: { containsAny: ["critical"] } },
230
+ { name: { prefix: "Compressor" } },
231
+ ],
232
+ NOT: {
233
+ sourceId: { eq: "legacy-system" },
234
+ },
314
235
  },
315
- limit: 200,
236
+ limit: 100,
316
237
  });
317
238
  ```
318
239
 
319
- ---
320
-
321
- ### Query activities in a time window
240
+ List fields support `containsAny` and `containsAll`:
322
241
 
323
242
  ```ts
324
- const { items } = await model.query<CogniteActivity>()({
325
- viewExternalId: "CogniteActivity",
243
+ const critical = await model.query<CogniteAsset>()({
244
+ viewExternalId: "CogniteAsset",
326
245
  select: {
327
246
  name: true,
328
- description: true,
329
- startTime: true,
330
- endTime: true,
331
- scheduledStartTime: true,
332
- scheduledEndTime: true,
247
+ tags: true,
333
248
  },
334
249
  filters: {
335
- startTime: { gte: "2024-01-01T00:00:00Z", lte: "2024-12-31T23:59:59Z" },
250
+ tags: { containsAny: ["critical", "safety"] },
336
251
  },
337
- sort: { startTime: "ascending" },
338
- limit: 500,
252
+ limit: 100,
339
253
  });
340
- ```
341
-
342
- ---
343
254
 
344
- ### Combine filters with AND / OR / NOT
345
-
346
- Fetch assets that are either tagged `"critical"` or have a name starting with `"Compressor"`, but exclude those from source `"legacy-system"`.
347
-
348
- ```ts
349
- const { items } = await model.query<CogniteAsset>()({
255
+ const fullyTagged = await model.query<CogniteAsset>()({
350
256
  viewExternalId: "CogniteAsset",
351
- select: { name: true, tags: true, sourceId: true },
257
+ select: {
258
+ name: true,
259
+ tags: true,
260
+ },
352
261
  filters: {
353
- OR: [
354
- { tags: { containsAny: ["critical"] } },
355
- { name: { prefix: "Compressor" } },
356
- ],
357
- NOT: { sourceId: { eq: "legacy-system" } },
262
+ tags: { containsAll: ["production", "verified"] },
358
263
  },
359
264
  limit: 100,
360
265
  });
361
266
  ```
362
267
 
363
- ---
364
-
365
- ### Paginate through all assets
366
-
367
- ```ts
368
- let cursor: string | null = null;
369
- const allAssets: QueryResultItem<CogniteAsset, { name: true; description: true }>[] = [];
370
-
371
- do {
372
- const result = await model.query<CogniteAsset>()({
373
- viewExternalId: "CogniteAsset",
374
- select: { name: true, description: true },
375
- limit: 1000,
376
- cursor,
377
- });
378
-
379
- allAssets.push(...result.items);
380
- cursor = result.cursor;
381
- } while (cursor !== null);
382
-
383
- console.log(`Total assets: ${allAssets.length}`);
384
- ```
385
-
386
- ---
387
-
388
- ### Fetch all pages automatically
389
-
390
- Pass `limit: -1` to follow root-view cursors until every page is loaded. The SDK issues multiple `instances.query` calls (1000 items per page by default). The returned `cursor` is always `null`.
268
+ Sort clauses apply to primitive fields on the root view, including node metadata such as `externalId`.
391
269
 
392
270
  ```ts
393
271
  const { items } = await model.query<CogniteAsset>()({
394
272
  viewExternalId: "CogniteAsset",
395
- select: { name: true, description: true },
396
- filters: { tags: { containsAny: ["production"] } },
397
- limit: -1,
273
+ select: {
274
+ name: true,
275
+ sourceId: true,
276
+ },
277
+ sort: {
278
+ name: "ascending",
279
+ externalId: "descending",
280
+ },
281
+ limit: 100,
398
282
  });
399
-
400
- console.log(`Loaded ${items.length} assets across all pages`);
401
283
  ```
402
284
 
403
- ---
404
-
405
- ### Select all scalar fields
406
-
407
- Use `_all` to include every scalar property on the view without listing them individually. Relation fields are returned as `NodeId` references but are not expanded — add nested `select` blocks when you need related data.
408
-
409
- ```ts
410
- const { items } = await model.query<CogniteAsset>()({
411
- viewExternalId: "CogniteAsset",
412
- select: { _all: true },
413
- limit: 50,
414
- });
415
-
416
- // items[0] includes name, description, tags, parent (as NodeId), etc.
417
- ```
285
+ ## Text Search
418
286
 
419
- Combine `_all` with explicit relation expansion:
287
+ Use `search` on Cognite text properties and string-list text properties when you want full-text matching instead of exact or prefix matching. The optional `operator` is passed to Cognite search and defaults to `"OR"`.
420
288
 
421
289
  ```ts
422
290
  const { items } = await model.query<CogniteAsset>()({
423
291
  viewExternalId: "CogniteAsset",
424
292
  select: {
425
- _all: true,
426
- parent: { name: true },
293
+ name: true,
294
+ description: true,
295
+ tags: true,
427
296
  },
428
- limit: 25,
297
+ filters: {
298
+ name: { search: { query: "root pump", operator: "AND" } },
299
+ tags: { search: { query: "critical" } },
300
+ },
301
+ limit: 100,
429
302
  });
430
303
  ```
431
304
 
432
- ---
433
-
434
- ### Filter by multiple external IDs
305
+ Search filters can be combined with regular operators. The SDK first calls Cognite `instances.search`, maps the matched nodes to instance references, and then applies those references to the query or aggregate request.
435
306
 
436
307
  ```ts
437
- const { items } = await model.query<CogniteAsset>()({
308
+ const pumps = await model.query<CogniteAsset>()({
438
309
  viewExternalId: "CogniteAsset",
439
- select: { name: true, description: true },
310
+ select: {
311
+ name: true,
312
+ sourceId: true,
313
+ },
440
314
  filters: {
441
- externalId: {
442
- in: ["WMT:VAL", "WMT:PUMP-01", "WMT:PUMP-02"],
315
+ name: {
316
+ prefix: "Pump",
317
+ search: { query: "motor" },
443
318
  },
319
+ sourceId: { exists: true },
444
320
  },
445
321
  });
446
322
  ```
447
323
 
448
- ---
449
-
450
- ### Filter assets by tags
451
-
452
- ```ts
453
- // Match assets that have at least one of these tags
454
- const critical = await model.query<CogniteAsset>()({
455
- viewExternalId: "CogniteAsset",
456
- select: { name: true, tags: true },
457
- filters: { tags: { containsAny: ["critical", "safety"] } },
458
- limit: 100,
459
- });
460
-
461
- // Match assets that must have every tag
462
- const fullyTagged = await model.query<CogniteAsset>()({
463
- viewExternalId: "CogniteAsset",
464
- select: { name: true, tags: true },
465
- filters: { tags: { containsAll: ["production", "verified"] } },
466
- limit: 100,
467
- });
468
- ```
469
-
470
- ---
324
+ ## Relations
471
325
 
472
- ### Filter on related nodes
473
-
474
- Filter the root view based on properties of a direct or nested relation. This uses Cognite nested filters under the hood.
326
+ The same `select` object that selects scalar fields can expand relations. Direct relations move outward from the current node.
475
327
 
476
328
  ```ts
477
- // Assets whose parent is named "Site Root"
478
329
  const { items } = await model.query<CogniteAsset>()({
479
- viewExternalId: "CogniteAsset",
480
- select: { name: true, parent: { name: true } },
481
- filters: {
482
- parent: { name: { eq: "Site Root" } },
483
- },
484
- limit: 50,
485
- });
486
-
487
- // Assets whose parent's asset class code is "PUMP"
488
- const pumpsByClass = await model.query<CogniteAsset>()({
489
330
  viewExternalId: "CogniteAsset",
490
331
  select: {
491
332
  name: true,
492
- parent: { assetClass: { name: true, code: true } },
333
+ parent: {
334
+ name: true,
335
+ description: true,
336
+ parent: {
337
+ name: true,
338
+ },
339
+ },
340
+ root: {
341
+ name: true,
342
+ },
493
343
  },
494
344
  filters: {
495
- parent: { assetClass: { code: { eq: "PUMP" } } },
345
+ name: { prefix: "Pump" },
496
346
  },
497
347
  limit: 50,
498
348
  });
499
349
 
500
- // Combine root and nested conditions
501
- const filtered = await model.query<CogniteAsset>()({
502
- viewExternalId: "CogniteAsset",
503
- select: { name: true, parent: { name: true } },
504
- filters: {
505
- AND: [
506
- { name: { prefix: "Pump" } },
507
- { parent: { name: { exists: true } } },
508
- ],
509
- },
510
- limit: 100,
511
- });
350
+ const firstParentName = items[0]?.parent?.name;
512
351
  ```
513
352
 
514
- ---
515
-
516
- ### Query child assets (reverse relation)
517
-
518
- Declare reverse relations in the `IndustrialModel<TProps, TRelations>` metadata. The library resolves the correct traversal direction from your data model.
353
+ List relations work the same way. For example, `path` can be expanded into asset objects for breadcrumb-style views.
519
354
 
520
355
  ```ts
521
- type AssetWithChildren = IndustrialModel<
522
- ModelProps<CogniteAsset>,
523
- ModelRelations<CogniteAsset> & { children?: CogniteAsset[] }
524
- >;
525
-
526
- const { items } = await model.query<AssetWithChildren>()({
356
+ const { items } = await model.query<CogniteAsset>()({
527
357
  viewExternalId: "CogniteAsset",
528
358
  select: {
529
359
  name: true,
530
- children: {
360
+ path: {
531
361
  name: true,
532
362
  description: true,
533
363
  },
@@ -538,140 +368,174 @@ const { items } = await model.query<AssetWithChildren>()({
538
368
  });
539
369
  ```
540
370
 
541
- ---
542
-
543
- ### Traverse edge relations (360 images on 3D objects)
544
-
545
- Some relations are modeled as edges rather than direct node links. Select them the same way — the SDK builds the edge hop automatically.
371
+ Nested relation filters let you filter the root view based on related nodes.
546
372
 
547
373
  ```ts
548
- const { items } = await model.query<Cognite3DObject>()({
549
- viewExternalId: "Cognite3DObject",
374
+ const { items } = await model.query<CogniteAsset>()({
375
+ viewExternalId: "CogniteAsset",
550
376
  select: {
551
377
  name: true,
552
- images360: { takenAt: true },
553
- },
554
- filters: {
555
- name: { prefix: "Tank" },
378
+ parent: {
379
+ name: true,
380
+ },
556
381
  },
557
- limit: 20,
382
+ filters: {
383
+ parent: {
384
+ name: { eq: "Site Root" },
385
+ },
386
+ },
387
+ limit: 50,
558
388
  });
559
389
  ```
560
390
 
561
- ---
562
-
563
- ### Sort by multiple fields
564
-
565
- Sort clauses apply to primitive fields on the root view, including node-level properties like `externalId`.
391
+ You can keep moving through the graph:
566
392
 
567
393
  ```ts
568
- const { items } = await model.query<CogniteAsset>()({
394
+ const pumpsByClass = await model.query<CogniteAsset>()({
569
395
  viewExternalId: "CogniteAsset",
570
- select: { name: true, sourceId: true },
571
- sort: {
572
- name: "ascending",
573
- externalId: "descending",
396
+ select: {
397
+ name: true,
398
+ parent: {
399
+ assetClass: {
400
+ name: true,
401
+ code: true,
402
+ },
403
+ },
574
404
  },
575
- limit: 100,
405
+ filters: {
406
+ parent: {
407
+ assetClass: {
408
+ code: { eq: "PUMP" },
409
+ },
410
+ },
411
+ },
412
+ limit: 50,
576
413
  });
577
414
  ```
578
415
 
579
- ---
416
+ ### Reverse Relations
580
417
 
581
- ### Use a custom data model
582
-
583
- Point the client at any FDM in your project — not only Cognite Core. Scalar fields work with a plain object type; use `IndustrialModel<TProps, TRelations>` when you need relation selects, filters, or inference.
418
+ Declare reverse relations in `IndustrialModel<TProps, TRelations>`. The SDK resolves the traversal direction from the Cognite data model.
584
419
 
585
420
  ```ts
586
- const model = new IndustrialModelClient(client, {
587
- space: "my-custom-space",
588
- externalId: "MyPlantModel",
589
- version: "1",
590
- });
591
-
592
- type PlantArea = IndustrialModel<{
593
- name: string;
594
- code: string;
595
- site?: NodeId;
596
- }>;
421
+ type AssetWithChildren = IndustrialModel<
422
+ ModelProps<CogniteAsset>,
423
+ ModelRelations<CogniteAsset> & {
424
+ children?: CogniteAsset[];
425
+ }
426
+ >;
597
427
 
598
- const { items } = await model.query<PlantArea>()({
599
- viewExternalId: "PlantArea",
600
- select: { name: true, code: true, site: true },
601
- filters: { code: { prefix: "AREA-" } },
602
- limit: 200,
428
+ const { items } = await model.query<AssetWithChildren>()({
429
+ viewExternalId: "CogniteAsset",
430
+ select: {
431
+ name: true,
432
+ children: {
433
+ name: true,
434
+ description: true,
435
+ },
436
+ },
437
+ filters: {
438
+ externalId: { eq: "WMT:VAL" },
439
+ },
603
440
  });
604
441
  ```
605
442
 
606
- ---
443
+ ### Edge Relations
607
444
 
608
- ### Full example: assets, equipment, and filters
609
-
610
- A single query combining nested selects, nested filters, sorting, and pagination.
445
+ Some relations are modeled as edges rather than direct node references. Select them with the same relation syntax.
611
446
 
612
447
  ```ts
613
- type AssetWithRelations = IndustrialModel<
614
- ModelProps<CogniteAsset>,
615
- ModelRelations<CogniteAsset> & { children?: CogniteAsset[] }
448
+ type Cognite360Image = IndustrialModel<{
449
+ takenAt: string;
450
+ }>;
451
+
452
+ type Cognite3DObject = IndustrialModel<
453
+ {
454
+ name: string;
455
+ description: string;
456
+ },
457
+ {
458
+ images360?: Cognite360Image[];
459
+ }
616
460
  >;
617
461
 
618
- const { items, cursor } = await model.query<AssetWithRelations>()({
619
- viewExternalId: "CogniteAsset",
462
+ const { items } = await model.query<Cognite3DObject>()({
463
+ viewExternalId: "Cognite3DObject",
620
464
  select: {
621
465
  name: true,
622
- description: true,
623
- tags: true,
624
- parent: {
625
- name: true,
626
- assetClass: { name: true, code: true },
466
+ images360: {
467
+ takenAt: true,
627
468
  },
628
469
  },
629
470
  filters: {
630
- name: { prefix: "WMT" },
631
- parent: { name: { exists: true } },
632
- OR: [
633
- { tags: { containsAny: ["critical"] } },
634
- { sourceId: { eq: "sap" } },
635
- ],
471
+ name: { prefix: "Tank" },
636
472
  },
637
- sort: { name: "ascending" },
638
- limit: 25,
639
- cursor: null,
473
+ limit: 20,
640
474
  });
475
+ ```
641
476
 
642
- // Follow-up page
643
- if (cursor) {
644
- const next = await model.query<AssetWithRelations>()({
477
+ ## Pagination
478
+
479
+ `query()` returns a root cursor when more root-view items are available.
480
+
481
+ ```ts
482
+ import type { QueryResultItem } from "industrial-model";
483
+
484
+ let cursor: string | null = null;
485
+ const allAssets: QueryResultItem<CogniteAsset, { name: true; description: true }>[] = [];
486
+
487
+ do {
488
+ const result = await model.query<CogniteAsset>()({
645
489
  viewExternalId: "CogniteAsset",
646
490
  select: {
647
491
  name: true,
648
492
  description: true,
649
- tags: true,
650
- parent: { name: true, assetClass: { name: true, code: true } },
651
- },
652
- filters: {
653
- name: { prefix: "WMT" },
654
- parent: { name: { exists: true } },
655
493
  },
656
- sort: { name: "ascending" },
657
- limit: 25,
494
+ limit: 1000,
658
495
  cursor,
659
496
  });
660
- }
497
+
498
+ allAssets.push(...result.items);
499
+ cursor = result.cursor;
500
+ } while (cursor !== null);
501
+ ```
502
+
503
+ Pass `limit: -1` when you want the SDK to follow all root cursors automatically. The SDK issues multiple `instances.query` calls, using 1000 root items per page by default, and returns `cursor: null`.
504
+
505
+ ```ts
506
+ const { items } = await model.query<CogniteAsset>()({
507
+ viewExternalId: "CogniteAsset",
508
+ select: {
509
+ name: true,
510
+ description: true,
511
+ },
512
+ filters: {
513
+ tags: { containsAny: ["production"] },
514
+ },
515
+ limit: -1,
516
+ });
661
517
  ```
662
518
 
663
- ---
519
+ Expanded relations use internal pagination as well. When a nested relation query reaches the internal page size, the client follows dependency cursors for up to 3 additional rounds.
664
520
 
665
- ### Count assets by source ID
521
+ ## Aggregation
666
522
 
667
- Group assets and count how many share each `sourceId`. Uses the same `filters` syntax as `query`.
523
+ Use `aggregate()` when you need grouped counts, distinct values, or numeric summaries without loading every instance.
524
+
525
+ Group and count assets by `sourceId`:
668
526
 
669
527
  ```ts
670
528
  const { items } = await model.aggregate<CogniteAsset>()({
671
529
  viewExternalId: "CogniteAsset",
672
- groupBy: { sourceId: true },
673
- aggregate: { count: {} },
674
- filters: { name: { prefix: "WMT" } },
530
+ groupBy: {
531
+ sourceId: true,
532
+ },
533
+ aggregate: {
534
+ count: {},
535
+ },
536
+ filters: {
537
+ name: { prefix: "WMT" },
538
+ },
675
539
  });
676
540
 
677
541
  for (const row of items) {
@@ -679,37 +543,36 @@ for (const row of items) {
679
543
  }
680
544
  ```
681
545
 
682
- ---
683
-
684
- ### List distinct source IDs
685
-
686
- Omit `aggregate` to return unique combinations of the `groupBy` fields (up to 1000 groups).
546
+ Omit `aggregate` to list distinct values for grouped fields:
687
547
 
688
548
  ```ts
689
549
  const { items } = await model.aggregate<CogniteAsset>()({
690
550
  viewExternalId: "CogniteAsset",
691
- groupBy: { sourceId: true },
551
+ groupBy: {
552
+ sourceId: true,
553
+ },
692
554
  });
693
555
 
694
556
  const sourceIds = items.map((row) => row.group?.sourceId);
695
557
  ```
696
558
 
697
- ---
698
-
699
- ### Average volume by type
700
-
701
- Use `avg`, `min`, `max`, or `sum` on numeric view properties. Only one aggregate operation per call.
559
+ Use `avg`, `min`, `max`, or `sum` on numeric properties:
702
560
 
703
561
  ```ts
704
562
  type PointCloudVolume = IndustrialModel<{
705
563
  volume: number;
706
564
  volumeType: string;
565
+ object3D?: NodeId;
707
566
  }>;
708
567
 
709
568
  const { items } = await model.aggregate<PointCloudVolume>()({
710
569
  viewExternalId: "CognitePointCloudVolume",
711
- groupBy: { volumeType: true },
712
- aggregate: { avg: "volume" },
570
+ groupBy: {
571
+ volumeType: true,
572
+ },
573
+ aggregate: {
574
+ avg: "volume",
575
+ },
713
576
  });
714
577
 
715
578
  items[0]?.group?.volumeType;
@@ -717,216 +580,387 @@ items[0]?.aggregate?.property; // "volume"
717
580
  items[0]?.aggregate?.value;
718
581
  ```
719
582
 
720
- Other numeric aggregates:
583
+ Count all rows matching a filter:
721
584
 
722
585
  ```ts
723
- await model.aggregate<PointCloudVolume>()({
724
- viewExternalId: "CognitePointCloudVolume",
725
- aggregate: { min: "volume" },
586
+ const { items } = await model.aggregate<CogniteAsset>()({
587
+ viewExternalId: "CogniteAsset",
588
+ aggregate: {
589
+ count: {},
590
+ },
591
+ filters: {
592
+ OR: [{ tags: { containsAny: ["critical"] } }, { sourceId: { eq: "sap" } }],
593
+ },
726
594
  });
727
595
 
728
- await model.aggregate<PointCloudVolume>()({
729
- viewExternalId: "CognitePointCloudVolume",
730
- aggregate: { max: "volume" },
731
- });
596
+ items[0]?.aggregate?.value;
597
+ ```
732
598
 
733
- await model.aggregate<PointCloudVolume>()({
599
+ Group by a direct relation when you need relation IDs in the result:
600
+
601
+ ```ts
602
+ const { items } = await model.aggregate<PointCloudVolume>()({
734
603
  viewExternalId: "CognitePointCloudVolume",
735
- aggregate: { sum: "volume" },
604
+ groupBy: {
605
+ object3D: true,
606
+ },
607
+ aggregate: {
608
+ sum: "volume",
609
+ },
736
610
  });
737
- ```
738
611
 
739
- ---
612
+ items[0]?.group?.object3D?.externalId;
613
+ ```
740
614
 
741
- ### Count non-null values for a property
615
+ Text search filters are also supported in aggregations:
742
616
 
743
617
  ```ts
744
618
  const { items } = await model.aggregate<CogniteAsset>()({
745
619
  viewExternalId: "CogniteAsset",
746
- aggregate: { count: "name" },
620
+ aggregate: {
621
+ count: {},
622
+ },
623
+ filters: {
624
+ name: { search: { query: "compressor seal" } },
625
+ },
747
626
  });
748
-
749
- items[0]?.aggregate?.property; // "name"
750
- items[0]?.aggregate?.value;
751
627
  ```
752
628
 
753
- ---
629
+ ## Runtime Validation
754
630
 
755
- ### Count all matching assets
631
+ By default, the SDK validates query inputs against the loaded Cognite view metadata before building the request. Query results are mapped without parsing each returned item.
756
632
 
757
- A global count with no `groupBy`:
633
+ Enable `validateResults` when you also want result parsing through Zod schemas derived from Cognite view metadata:
758
634
 
759
635
  ```ts
760
- const { items } = await model.aggregate<CogniteAsset>()({
761
- viewExternalId: "CogniteAsset",
762
- aggregate: { count: {} },
763
- filters: {
764
- OR: [{ tags: { containsAny: ["critical"] } }, { sourceId: { eq: "sap" } }],
636
+ const model = new IndustrialModelClient(
637
+ client,
638
+ {
639
+ space: "cdf_cdm",
640
+ externalId: "CogniteCore",
641
+ version: "v1",
765
642
  },
766
- });
767
-
768
- items[0]?.aggregate?.value;
643
+ {
644
+ validateResults: true,
645
+ },
646
+ );
769
647
  ```
770
648
 
771
- ---
649
+ When result validation is enabled, Cognite `date` and `timestamp` view properties are converted to JavaScript `Date` objects. Without it, result values are returned as Cognite provides them, usually ISO strings for timestamps.
772
650
 
773
- ### Group by a direct relation
651
+ ## Cognite Core Client
774
652
 
775
- `groupBy` supports direct relations; results are returned as `NodeId` objects.
653
+ For applications working with the Cognite Core Data Model (`cdf_cdm/CogniteCore/v1`), use `CogniteCoreClient` instead of `IndustrialModelClient`. It pre-configures the data model, bundles all view type definitions, and moves the view name to the first positional argument so TypeScript can infer the model type without a generic annotation.
776
654
 
777
655
  ```ts
778
- type PointCloudVolume = IndustrialModel<{
779
- volume: number;
780
- object3D?: NodeId;
781
- }>;
782
-
783
- const { items } = await model.aggregate<PointCloudVolume>()({
784
- viewExternalId: "CognitePointCloudVolume",
785
- groupBy: { object3D: true },
786
- aggregate: { sum: "volume" },
787
- });
656
+ import { CogniteClient } from "@cognite/sdk";
657
+ import { CogniteCoreClient } from "industrial-model";
788
658
 
789
- items[0]?.group?.object3D?.externalId;
659
+ const client = new CogniteClient({ ... });
660
+ const core = new CogniteCoreClient(client);
790
661
  ```
791
662
 
792
- ---
663
+ Query any Cognite Core view by passing its name to `query()`:
793
664
 
794
- ## API
665
+ ```ts
666
+ const { items } = await core.query("CogniteAsset")({
667
+ select: {
668
+ name: true,
669
+ description: true,
670
+ parent: { name: true },
671
+ },
672
+ filters: {
673
+ name: { prefix: "Pump" },
674
+ },
675
+ limit: 50,
676
+ });
795
677
 
796
- ### Exports
678
+ items[0]?.name; // string | undefined
679
+ items[0]?.parent?.name; // string | undefined
680
+ ```
797
681
 
798
- | Symbol | Description |
799
- |--------|-------------|
800
- | `IndustrialModelClient` | Main client |
801
- | `IndustrialModel`, `ModelProps`, `ModelRelations` | Type helpers for models and relations |
802
- | `NodeId`, `DataModelId` | Instance and data-model identifiers |
803
- | `QueryOptions`, `QuerySelect`, `WhereInput`, `SortInput` | Query input types |
804
- | `QueryResult`, `QueryResultItem`, `QueryResultMetadata` | Query output types |
805
- | `AggregateOptions`, `AggregateGroupBy`, `AggregateDefinition` | Aggregate input types |
806
- | `AggregateResult`, `AggregateResultItem`, `GroupByKey` | Aggregate output types |
807
- | `buildViewSchema`, `nodeIdSchema` | Zod schemas built from Cognite view metadata |
808
- | `SortDirection` | `"ascending"` \| `"descending"` |
682
+ The view name drives TypeScript inference — no generic annotation is needed. All filters, `select` fields, and nested relation selections are type-checked against the bundled view definition. Every feature available on `IndustrialModelClient` — text search, pagination, `limit: -1`, nested filters, and relation traversal — works identically.
809
683
 
810
- ### `new IndustrialModelClient(client, dataModelId, options?)`
684
+ Aggregations use the same positional-view-name pattern:
811
685
 
812
- | Parameter | Type | Description |
813
- |-----------|------|-------------|
814
- | `client` | `CogniteClient` | Authenticated Cognite SDK client |
815
- | `dataModelId` | `DataModelId` | Space, externalId, and version of the data model |
816
- | `options.validateResults` | `boolean` | Optional. Validate and parse query results with Zod schemas derived from Cognite view metadata |
686
+ ```ts
687
+ const { items } = await core.aggregate("CogniteEquipment")({
688
+ groupBy: { manufacturer: true },
689
+ aggregate: { count: {} },
690
+ filters: {
691
+ equipmentType: { exists: true },
692
+ },
693
+ });
817
694
 
818
- On the first query, view definitions are loaded from CDF and cached for the lifetime of the client instance.
695
+ items[0]?.group?.manufacturer;
696
+ items[0]?.aggregate?.value;
697
+ ```
819
698
 
820
- 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:
699
+ All Cognite Core view types are exported from `industrial-model` and can be imported directly for use with `IndustrialModelClient` if needed:
821
700
 
822
701
  ```ts
823
- const model = new IndustrialModelClient(client, dataModelId, {
824
- validateResults: true,
825
- });
702
+ import type { CogniteAsset, CogniteEquipment, CogniteTimeSeries } from "industrial-model";
826
703
  ```
827
704
 
828
- 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.
705
+ ### Inward List-Relation Limitation
829
706
 
830
- ### `model.query<TModel>()(options)`
707
+ Cognite rejects server-side inward traversal of list direct relations. As a result, `timeSeries`, `files`, and `activities` cannot be expanded from `CogniteAsset`. Attempting to select them throws a descriptive error before the Cognite API is called, naming the view to query and the field to filter on.
831
708
 
832
- | Option | Type | Description |
833
- |--------|------|-------------|
834
- | `viewExternalId` | `string` | The view to query |
835
- | `select` | `QuerySelect<TModel>` | Optional. Defaults to `{ _all: true }` (all scalar fields). Use `_all: true` explicitly or list fields; use nested objects for relations |
836
- | `filters` | `WhereInput<TModel>` | Filter conditions (supports nested relation filters) |
837
- | `sort` | `SortInput<TModel>` | Sort by primitive fields on the **root** view only |
838
- | `limit` | `number` | Root page size (default `1000`). Use `-1` to fetch all root pages automatically |
839
- | `cursor` | `string \| null` | Pagination cursor from a previous response |
709
+ The alternative is to query the target view directly and filter by the relation field pointing back to the asset:
840
710
 
841
- `query()` uses a curried form so you can supply the model types first and still get return-type inference from `select`.
711
+ ```ts
712
+ // not supported — throws before calling Cognite
713
+ await core.query("CogniteAsset")({
714
+ select: { timeSeries: { name: true } } as never,
715
+ });
716
+
717
+ // correct alternative: query CogniteTimeSeries and filter by assets
718
+ const { items } = await core.query("CogniteTimeSeries")({
719
+ select: { name: true, type: true },
720
+ filters: {
721
+ assets: { containsAny: [{ space: "my-space", externalId: "WMT:VAL" }] },
722
+ },
723
+ });
724
+ ```
842
725
 
843
- Use `IndustrialModel<TProps, TRelations>` when the model has expandable direct, reverse, or edge relations.
726
+ ## Custom Data Models
844
727
 
845
- Returns `Promise<QueryResult<QueryResultItem<TModel, TSelect>>>`:
728
+ The client can query any FDM in your CDF project. Cognite Core is not required.
846
729
 
847
730
  ```ts
848
- type QueryResult<TItem> = {
849
- items: TItem[];
850
- cursor: string | null; // null when no more root pages
851
- };
731
+ const model = new IndustrialModelClient(client, {
732
+ space: "my-custom-space",
733
+ externalId: "MyPlantModel",
734
+ version: "1",
735
+ });
736
+
737
+ type PlantArea = IndustrialModel<{
738
+ name: string;
739
+ code: string;
740
+ site?: NodeId;
741
+ }>;
742
+
743
+ const { items } = await model.query<PlantArea>()({
744
+ viewExternalId: "PlantArea",
745
+ select: {
746
+ name: true,
747
+ code: true,
748
+ site: true,
749
+ },
750
+ filters: {
751
+ code: { prefix: "AREA-" },
752
+ },
753
+ limit: 200,
754
+ });
852
755
  ```
853
756
 
854
- Each item always includes instance metadata (`space`, `externalId`, `createdTime`, `deletedTime`, `lastUpdatedTime`, `instanceType`) plus the fields you selected. With `{ _all: true }`, scalar view properties are included as well.
757
+ ## Complete Example
855
758
 
856
- Example:
759
+ This example combines typed relations, nested selections, nested filters, sorting, and cursor pagination.
857
760
 
858
761
  ```ts
859
- const { items } = await model.query<CogniteAsset>()({
762
+ type AssetWithRelations = IndustrialModel<
763
+ ModelProps<CogniteAsset>,
764
+ ModelRelations<CogniteAsset> & {
765
+ children?: CogniteAsset[];
766
+ }
767
+ >;
768
+
769
+ const { items, cursor } = await model.query<AssetWithRelations>()({
860
770
  viewExternalId: "CogniteAsset",
861
771
  select: {
772
+ name: true,
773
+ description: true,
774
+ tags: true,
862
775
  parent: {
863
776
  name: true,
777
+ assetClass: {
778
+ name: true,
779
+ code: true,
780
+ },
781
+ },
782
+ children: {
783
+ name: true,
864
784
  },
865
785
  },
786
+ filters: {
787
+ name: { prefix: "WMT" },
788
+ parent: {
789
+ name: { exists: true },
790
+ },
791
+ OR: [
792
+ { tags: { containsAny: ["critical"] } },
793
+ { sourceId: { eq: "sap" } },
794
+ ],
795
+ },
796
+ sort: {
797
+ name: "ascending",
798
+ },
799
+ limit: 25,
800
+ cursor: null,
866
801
  });
867
802
 
868
- items[0]?.parent?.name;
869
- items[0]?.externalId;
803
+ if (cursor) {
804
+ const next = await model.query<AssetWithRelations>()({
805
+ viewExternalId: "CogniteAsset",
806
+ select: {
807
+ name: true,
808
+ description: true,
809
+ tags: true,
810
+ parent: {
811
+ name: true,
812
+ assetClass: {
813
+ name: true,
814
+ code: true,
815
+ },
816
+ },
817
+ },
818
+ filters: {
819
+ name: { prefix: "WMT" },
820
+ parent: {
821
+ name: { exists: true },
822
+ },
823
+ },
824
+ sort: {
825
+ name: "ascending",
826
+ },
827
+ limit: 25,
828
+ cursor,
829
+ });
830
+ }
870
831
  ```
871
832
 
872
- ### `model.aggregate<TModel>()(options)`
833
+ ## API Reference
873
834
 
874
- | Option | Type | Description |
875
- |--------|------|-------------|
876
- | `viewExternalId` | `string` | The view to aggregate |
877
- | `groupBy` | `AggregateGroupBy<TModel>` | Optional. Object of groupable properties set to `true` (max 5) |
878
- | `filters` | `WhereInput<TModel>` | Same filter syntax as `query` |
879
- | `aggregate` | `AggregateDefinition<TModel>` | Optional. One of `avg`, `min`, `max`, `sum`, or `count` per call |
835
+ ### `new CogniteCoreClient(client, options?)`
880
836
 
881
- 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`.
837
+ | Parameter | Type | Description |
838
+ | --- | --- | --- |
839
+ | `client` | `CogniteClient` | Authenticated Cognite SDK client. |
840
+ | `options` | `IndustrialModelClientOptions` | Optional. Same options as `IndustrialModelClient`. |
841
+
842
+ Pre-configured for the Cognite Core Data Model (`cdf_cdm/CogniteCore/v1`). The exported constant `COGNITE_CORE_DATA_MODEL` holds the data model identifier if you need to pass it to other utilities.
843
+
844
+ ### `core.query(viewExternalId)(options)`
845
+
846
+ Same as `model.query<TModel>()(options)` on `IndustrialModelClient`, except the view is provided as the first positional argument and the model type is inferred from it. The `viewExternalId` option is not accepted in the second call. `viewExternalId` must be a valid `CogniteCoreViewExternalId`.
847
+
848
+ ### `core.aggregate(viewExternalId)(options)`
849
+
850
+ Same as `model.aggregate<TModel>()(options)` on `IndustrialModelClient`, with the view name as the first positional argument.
851
+
852
+ ### `new IndustrialModelClient(client, dataModelId, options?)`
853
+
854
+ | Parameter | Type | Description |
855
+ | --- | --- | --- |
856
+ | `client` | `CogniteClient` | Authenticated Cognite SDK client. |
857
+ | `dataModelId` | `DataModelId` | Data model `space`, `externalId`, and `version`. |
858
+ | `options.validateResults` | `boolean` | Optional. Parse result items with generated Zod schemas. |
882
859
 
883
- `aggregate()` uses the same curried form as `query`. Each result item has the shape:
860
+ On the first query or aggregation, view definitions are loaded from CDF and cached for the lifetime of the client instance.
861
+
862
+ ### `model.query<TModel>()(options)`
863
+
864
+ `query()` uses a curried form so you can provide the model type first and still get return-type inference from `select`.
865
+
866
+ | Option | Description |
867
+ | --- | --- |
868
+ | `viewExternalId` | View to query. |
869
+ | `select` | Optional. Defaults to `{ _all: true }`. Use nested objects for relations. |
870
+ | `filters` | Field, logical, search, and nested relation filters. |
871
+ | `sort` | Sort by primitive fields on the root view only. |
872
+ | `limit` | Root page size. Defaults to `1000`. Use `-1` to fetch all root pages. |
873
+ | `cursor` | Root pagination cursor from a previous response. |
874
+
875
+ Returns:
884
876
 
885
877
  ```ts
886
- type AggregateResultItem = {
887
- group?: { /* keys from groupBy */ };
888
- aggregate?: { property?: string; value: number };
878
+ type QueryResult<TItem> = {
879
+ items: TItem[];
880
+ cursor: string | null;
889
881
  };
890
882
  ```
891
883
 
892
- When counting all rows, `aggregate` has only `value` (no `property`). When aggregating a field, `property` matches the name you passed in the request.
893
-
894
- | Aggregate | Input | Use case |
895
- |-----------|-------|----------|
896
- | `count` | `{ count: {} }` | Row count (optionally filtered) |
897
- | `count` | `{ count: "name" }` | Count non-null values for a property |
898
- | `avg` | `{ avg: "volume" }` | Average of a numeric property |
899
- | `min` | `{ min: "volume" }` | Minimum numeric value |
900
- | `max` | `{ max: "volume" }` | Maximum numeric value |
901
- | `sum` | `{ sum: "volume" }` | Sum of a numeric property |
902
-
903
- 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.
884
+ Each item includes instance metadata such as `space`, `externalId`, `version`, `createdTime`, `deletedTime`, and `lastUpdatedTime`, plus the selected fields.
904
885
 
905
- ### Automatic query behavior
886
+ ### `model.aggregate<TModel>()(options)`
906
887
 
907
- - **`hasData` filter** every root query includes a `hasData` constraint for the target view.
908
- - **Nested relation limits** — expanded relations use an internal page size of `10000` per sub-query.
909
- - **Dependency pagination** — when a nested sub-query returns `10000` items and a cursor, the client issues follow-up queries (up to 3 rounds) to load additional related data.
888
+ | Option | Description |
889
+ | --- | --- |
890
+ | `viewExternalId` | View to aggregate. |
891
+ | `groupBy` | Groupable properties set to `true`; max 5 fields. |
892
+ | `filters` | Same filter syntax as `query()`. |
893
+ | `aggregate` | One of `avg`, `min`, `max`, `sum`, or `count`. |
910
894
 
911
- ### Filter operators
895
+ Provide at least one of `groupBy` or `aggregate`. Omit `aggregate` to fetch distinct grouped values. The client requests up to 1000 aggregate rows.
912
896
 
913
- | Type | Operators |
914
- |------|-----------|
915
- | `string` | `eq`, `in`, `prefix`, `exists` |
897
+ | Aggregate | Input | Use case |
898
+ | --- | --- | --- |
899
+ | `count` | `{ count: {} }` | Row count, optionally filtered. |
900
+ | `count` | `{ count: "name" }` | Count non-null values for a property. |
901
+ | `avg` | `{ avg: "volume" }` | Average of a numeric property. |
902
+ | `min` | `{ min: "volume" }` | Minimum numeric value. |
903
+ | `max` | `{ max: "volume" }` | Maximum numeric value. |
904
+ | `sum` | `{ sum: "volume" }` | Sum of a numeric property. |
905
+
906
+ ### Filter Operators
907
+
908
+ | Field type | Operators |
909
+ | --- | --- |
910
+ | `string` | `eq`, `in`, `prefix`, `search`, `exists` |
916
911
  | `number` | `eq`, `in`, `gt`, `gte`, `lt`, `lte`, `exists` |
917
912
  | `boolean` | `eq`, `exists` |
918
- | timestamp / `Date` | `eq`, `in`, `gt`, `gte`, `lt`, `lte`, `exists` — use ISO strings or `Date` values (coerced to ISO) |
913
+ | timestamp / `Date` | `eq`, `in`, `gt`, `gte`, `lt`, `lte`, `exists` |
919
914
  | `NodeId` | `eq`, `in`, `exists` |
915
+ | `string[]` | `containsAny`, `containsAll`, `search`, `exists` |
920
916
  | `T[]` | `containsAny`, `containsAll`, `exists` |
921
917
 
922
- Logical combinators `AND`, `OR`, and `NOT` are supported at any nesting level, including inside nested relation filters (e.g. `parent: { OR: [...] }`).
918
+ Logical combinators `AND`, `OR`, and `NOT` are supported at any nesting level, including nested relation filters.
919
+
920
+ ### Relation Traversal
921
+
922
+ | Relation type | Description |
923
+ | --- | --- |
924
+ | Direct relations | Outward node references such as `parent`, `asset`, and `unit`. |
925
+ | Reverse relations | Relations declared only in `TRelations`, such as `children` on `CogniteAsset`. |
926
+ | Edge relations | Edge-backed relations declared in `TRelations`, such as `images360` on `Cognite3DObject`. |
927
+ | Depth | Nested `select` and `filters` are supported up to 3 levels deep. |
928
+
929
+ ### Exports
930
+
931
+ **Core**
923
932
 
924
- ### Relation traversal
933
+ | Symbol | Description |
934
+ | --- | --- |
935
+ | `IndustrialModelClient` | Main client for any FDM data model. |
936
+ | `IndustrialModel`, `ModelProps`, `ModelRelations` | Type helpers for model properties and relation metadata. |
937
+ | `NodeId`, `DataModelId` | Instance and data model identifiers. |
938
+ | `QuerySelect` | Type helper for reusable query selections. |
939
+ | `QueryResult`, `QueryResultItem` | Query output types. |
940
+ | `AggregateResult`, `AggregateResultItem` | Aggregate output types. |
941
+ | `IndustrialModelClientOptions` | Client configuration options. |
925
942
 
926
- - **Direct relations** — `parent`, `asset`, `unit` (outwards from the current node). List relations such as `path` return arrays when expanded.
927
- - **Reverse relations** — declare in `TRelations` (e.g. `children` on `CogniteAsset`)
928
- - **Edge relations** declare in `TRelations` (e.g. `images360` on `Cognite3DObject`)
929
- - **Depth** nested `select` and `filters` up to 3 levels; dependency pages for large nested result sets are fetched automatically (see above)
943
+ **Cognite Core**
944
+
945
+ | Symbol | Description |
946
+ | --- | --- |
947
+ | `CogniteCoreClient` | Convenience client pre-configured for `cdf_cdm/CogniteCore/v1`. |
948
+ | `COGNITE_CORE_DATA_MODEL` | Data model identifier constant for Cognite Core v1. |
949
+ | `CogniteCoreViewExternalId` | Union type of all Cognite Core view names. |
950
+ | `CogniteAsset`, `CogniteAssetClass`, `CogniteAssetType` | Asset hierarchy views. |
951
+ | `CogniteEquipment`, `CogniteEquipmentType` | Equipment views. |
952
+ | `CogniteFile`, `CogniteFileCategory` | File views. |
953
+ | `CogniteActivity` | Activity view. |
954
+ | `CogniteTimeSeries` | Time series view. |
955
+ | `CogniteUnit` | Unit of measurement view. |
956
+ | `CogniteAnnotation`, `CogniteDiagramAnnotation` | Annotation views. |
957
+ | `CogniteSourceSystem` | Source system view. |
958
+ | `CogniteDescribable`, `CogniteSourceable`, `CogniteSchedulable`, `CogniteVisualizable` | Mixin views. |
959
+ | `Cognite3DObject`, `Cognite3DModel`, `Cognite3DRevision`, `Cognite3DTransformation` | 3D object and model views. |
960
+ | `CogniteCADModel`, `CogniteCADRevision`, `CogniteCADNode` | CAD-specific views. |
961
+ | `CognitePointCloudModel`, `CognitePointCloudRevision`, `CognitePointCloudVolume` | Point cloud views. |
962
+ | `Cognite360Image`, `Cognite360ImageModel`, `Cognite360ImageCollection`, `Cognite360ImageStation`, `Cognite360ImageAnnotation` | 360 image views. |
963
+ | `CogniteCubeMap` | Cube map view. |
930
964
 
931
965
  ## Releasing
932
966