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