cognite-create 0.2.14 → 0.2.16
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/bin/index.js +13 -5
- package/package.json +2 -2
- package/templates/.cursor/mcp.json +4 -4
- package/templates/.cursor/rules/core-cdf-apis.mdc +1525 -0
- package/templates/.cursor/rules/datamodelling.mdc +2023 -0
- package/templates/.cursor/rules/development.mdc +90 -0
- package/templates/.cursor/rules/general.mdc +2 -0
- package/templates/.cursor/rules/requirements-gathering.mdc +24 -0
- package/templates/.env.example +11 -0
|
@@ -0,0 +1,1525 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Cognite Core APIs SDK Guide
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
This guide covers the core Cognite Data Fusion (CDF) APIs that handle industrial data management: **3D Models**, **Files**, **Time Series**, **Data Points**, **Raw** (NoSQL storage), and **Units**. These APIs enable you to work with various types of industrial data including 3D models for digital twins, files for documents and images, time series for sensor data, raw tables for flexible storage, and standardized units for measurements.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
To use these APIs, install the Cognite SDK:
|
|
14
|
+
|
|
15
|
+
Using npm:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @cognite/sdk --save
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Using yarn:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
yarn add @cognite/sdk
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Initialization
|
|
28
|
+
|
|
29
|
+
Initialize the CogniteClient with your project details and authentication:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
import { CogniteClient } from "@cognite/sdk";
|
|
33
|
+
|
|
34
|
+
const client = new CogniteClient({
|
|
35
|
+
appId: "YOUR_APP_NAME",
|
|
36
|
+
project: "YOUR_PROJECT_NAME",
|
|
37
|
+
oidcTokenProvider: async () => {
|
|
38
|
+
return "YOUR_OIDC_ACCESS_TOKEN";
|
|
39
|
+
},
|
|
40
|
+
baseUrl: "https://api.cognitedata.com", // Optional
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 3D APIs
|
|
45
|
+
|
|
46
|
+
CDF's 3D APIs enable you to work with 3D models for digital twins, including CAD models, point clouds, and 360-degree images.
|
|
47
|
+
|
|
48
|
+
### Models 3D API
|
|
49
|
+
|
|
50
|
+
#### client.models3D.create(models)
|
|
51
|
+
|
|
52
|
+
- **Description**: Creates new 3D models
|
|
53
|
+
- **Parameters**:
|
|
54
|
+
- `models` (CreateModel3D[], required): Array of 3D model definitions
|
|
55
|
+
- **Return Type**: Promise<Model3D[]>
|
|
56
|
+
- **Example**:
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
const models = await client.models3D.create([
|
|
60
|
+
{
|
|
61
|
+
name: "Process Plant 3D Model",
|
|
62
|
+
description: "Complete 3D model of the processing facility",
|
|
63
|
+
dataSetId: 123456789,
|
|
64
|
+
metadata: {
|
|
65
|
+
source: "AutoCAD",
|
|
66
|
+
version: "2024",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### client.models3D.list(scope?)
|
|
73
|
+
|
|
74
|
+
- **Description**: Lists 3D models with optional filtering
|
|
75
|
+
- **Parameters**:
|
|
76
|
+
- `scope` (Model3DListRequest, optional): Filter options
|
|
77
|
+
- **Return Type**: CursorAndAsyncIterator<Model3D>
|
|
78
|
+
- **Example**:
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// List published 3D models
|
|
82
|
+
const publishedModels = await client.models3D
|
|
83
|
+
.list({ published: true })
|
|
84
|
+
.autoPagingToArray();
|
|
85
|
+
|
|
86
|
+
// List all models
|
|
87
|
+
const allModels = await client.models3D.list().autoPagingToArray();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### client.models3D.retrieve(id)
|
|
91
|
+
|
|
92
|
+
- **Description**: Retrieves a specific 3D model
|
|
93
|
+
- **Parameters**:
|
|
94
|
+
- `id` (number, required): Model ID
|
|
95
|
+
- **Return Type**: Promise<Model3D>
|
|
96
|
+
- **Example**:
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
const model = await client.models3D.retrieve(3744350296805509);
|
|
100
|
+
console.log(`Model: ${model.name}`);
|
|
101
|
+
console.log(`Created: ${new Date(model.createdTime)}`);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### client.models3D.update(changes)
|
|
105
|
+
|
|
106
|
+
- **Description**: Updates 3D models
|
|
107
|
+
- **Parameters**:
|
|
108
|
+
- `changes` (UpdateModel3D[], required): Update operations
|
|
109
|
+
- **Return Type**: Promise<Model3D[]>
|
|
110
|
+
- **Example**:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
const updated = await client.models3D.update([
|
|
114
|
+
{
|
|
115
|
+
id: 3744350296805509,
|
|
116
|
+
update: {
|
|
117
|
+
name: { set: "Updated Plant Model" },
|
|
118
|
+
metadata: { set: { status: "reviewed" } },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### client.models3D.delete(ids)
|
|
125
|
+
|
|
126
|
+
- **Description**: Deletes 3D models
|
|
127
|
+
- **Parameters**:
|
|
128
|
+
- `ids` (InternalId[], required): Model IDs to delete
|
|
129
|
+
- **Return Type**: Promise<{}>
|
|
130
|
+
- **Example**:
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
await client.models3D.delete([{ id: 3744350296805509 }]);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Revisions 3D API
|
|
137
|
+
|
|
138
|
+
3D revisions represent versions of a 3D model, linked to uploaded 3D files.
|
|
139
|
+
|
|
140
|
+
#### client.revisions3D.create(modelId, items)
|
|
141
|
+
|
|
142
|
+
- **Description**: Creates revisions for a 3D model
|
|
143
|
+
- **Parameters**:
|
|
144
|
+
- `modelId` (number, required): Parent model ID
|
|
145
|
+
- `items` (CreateRevision3D[], required): Revision definitions
|
|
146
|
+
- **Return Type**: Promise<Revision3D[]>
|
|
147
|
+
- **Example**:
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
const revisions = await client.revisions3D.create(3744350296805509, [
|
|
151
|
+
{
|
|
152
|
+
fileId: 8252999965991682,
|
|
153
|
+
published: false,
|
|
154
|
+
rotation: [0, 0, 0],
|
|
155
|
+
scale: [1, 1, 1],
|
|
156
|
+
translation: [0, 0, 0],
|
|
157
|
+
metadata: {
|
|
158
|
+
revision: "A",
|
|
159
|
+
status: "draft",
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
]);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### client.revisions3D.list(modelId, filter?)
|
|
166
|
+
|
|
167
|
+
- **Description**: Lists revisions for a 3D model
|
|
168
|
+
- **Parameters**:
|
|
169
|
+
- `modelId` (number, required): Model ID
|
|
170
|
+
- `filter` (Revision3DListRequest, optional): Filter options
|
|
171
|
+
- **Return Type**: CursorAndAsyncIterator<Revision3D>
|
|
172
|
+
- **Example**:
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
const revisions = await client.revisions3D
|
|
176
|
+
.list(3744350296805509, {
|
|
177
|
+
published: true,
|
|
178
|
+
})
|
|
179
|
+
.autoPagingToArray();
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### client.revisions3D.update(modelId, changes)
|
|
183
|
+
|
|
184
|
+
- **Description**: Updates 3D revisions
|
|
185
|
+
- **Parameters**:
|
|
186
|
+
- `modelId` (number, required): Model ID
|
|
187
|
+
- `changes` (UpdateRevision3D[], required): Update operations
|
|
188
|
+
- **Return Type**: Promise<Revision3D[]>
|
|
189
|
+
- **Example**:
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
const updated = await client.revisions3D.update(3744350296805509, [
|
|
193
|
+
{
|
|
194
|
+
id: 4190022127342195,
|
|
195
|
+
update: {
|
|
196
|
+
published: { set: true },
|
|
197
|
+
rotation: { set: [0, 0, 90] },
|
|
198
|
+
metadata: { set: { status: "approved" } },
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
]);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Asset Mappings 3D API
|
|
205
|
+
|
|
206
|
+
Links 3D model nodes to assets in CDF.
|
|
207
|
+
|
|
208
|
+
#### client.assetMappings3D.create(modelId, revisionId, items)
|
|
209
|
+
|
|
210
|
+
- **Description**: Creates mappings between 3D nodes and assets
|
|
211
|
+
- **Parameters**:
|
|
212
|
+
- `modelId` (number, required): Model ID
|
|
213
|
+
- `revisionId` (number, required): Revision ID
|
|
214
|
+
- `items` (CreateAssetMapping3D[], required): Mapping definitions
|
|
215
|
+
- **Return Type**: Promise<AssetMapping3D[]>
|
|
216
|
+
- **Example**:
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
const mappings = await client.assetMappings3D.create(
|
|
220
|
+
3744350296805509,
|
|
221
|
+
4190022127342195,
|
|
222
|
+
[
|
|
223
|
+
{
|
|
224
|
+
nodeId: 1234,
|
|
225
|
+
assetId: 5678,
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
nodeId: 2345,
|
|
229
|
+
assetId: 6789,
|
|
230
|
+
},
|
|
231
|
+
]
|
|
232
|
+
);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### client.assetMappings3D.list(modelId, revisionId, scope?)
|
|
236
|
+
|
|
237
|
+
- **Description**: Lists asset mappings for a revision
|
|
238
|
+
- **Parameters**:
|
|
239
|
+
- `modelId` (number, required): Model ID
|
|
240
|
+
- `revisionId` (number, required): Revision ID
|
|
241
|
+
- `scope` (AssetMappings3DListFilter, optional): Filter options
|
|
242
|
+
- **Return Type**: CursorAndAsyncIterator<AssetMapping3D>
|
|
243
|
+
- **Example**:
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
const mappings = await client.assetMappings3D
|
|
247
|
+
.list(3744350296805509, 4190022127342195, {
|
|
248
|
+
assetId: 5678,
|
|
249
|
+
})
|
|
250
|
+
.autoPagingToArray();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Files 3D API
|
|
254
|
+
|
|
255
|
+
Manages 3D file uploads for models.
|
|
256
|
+
|
|
257
|
+
#### client.files3D.retrieve(fileId)
|
|
258
|
+
|
|
259
|
+
- **Description**: Retrieves 3D file information
|
|
260
|
+
- **Parameters**:
|
|
261
|
+
- `fileId` (number, required): File ID
|
|
262
|
+
- **Return Type**: Promise<File3D>
|
|
263
|
+
- **Example**:
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
const file3D = await client.files3D.retrieve(8252999965991682);
|
|
267
|
+
console.log(`File: ${file3D.fileName}`);
|
|
268
|
+
console.log(`Size: ${file3D.size} bytes`);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Files API
|
|
272
|
+
|
|
273
|
+
The Files API manages documents, images, and other file types in CDF.
|
|
274
|
+
|
|
275
|
+
### client.files.upload(fileInfo, fileContent?, overwrite?, waitUntilAcknowledged?)
|
|
276
|
+
|
|
277
|
+
- **Description**: Uploads a file to CDF
|
|
278
|
+
- **Parameters**:
|
|
279
|
+
- `fileInfo` (ExternalFileInfo, required): File metadata
|
|
280
|
+
- `fileContent` (FileContent, optional): File data (Buffer, Blob, string, etc.)
|
|
281
|
+
- `overwrite` (boolean, optional): Whether to overwrite existing file
|
|
282
|
+
- `waitUntilAcknowledged` (boolean, optional): Wait for upload confirmation
|
|
283
|
+
- **Return Type**: Promise<FileUploadResponse | FileInfo>
|
|
284
|
+
- **Example**:
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
// Upload with content
|
|
288
|
+
const file = await client.files.upload(
|
|
289
|
+
{
|
|
290
|
+
name: "equipment-manual.pdf",
|
|
291
|
+
externalId: "manual-pump-001",
|
|
292
|
+
mimeType: "application/pdf",
|
|
293
|
+
source: "manufacturer-website",
|
|
294
|
+
assetIds: [123456789],
|
|
295
|
+
dataSetId: 987654321,
|
|
296
|
+
metadata: {
|
|
297
|
+
documentType: "manual",
|
|
298
|
+
equipmentType: "pump",
|
|
299
|
+
language: "en",
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
fileBuffer // Buffer, Blob, or string
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Upload metadata only (get upload URL)
|
|
306
|
+
const uploadInfo = await client.files.upload({
|
|
307
|
+
name: "large-video.mp4",
|
|
308
|
+
mimeType: "video/mp4",
|
|
309
|
+
});
|
|
310
|
+
// Then upload to uploadInfo.uploadUrl separately
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### client.files.list(scope?)
|
|
314
|
+
|
|
315
|
+
- **Description**: Lists files with advanced filtering
|
|
316
|
+
- **Parameters**:
|
|
317
|
+
- `scope` (FileRequestFilter, optional): Filter and pagination options
|
|
318
|
+
- **Return Type**: CursorAndAsyncIterator<FileInfo>
|
|
319
|
+
- **Example**:
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
// List PDF files
|
|
323
|
+
const pdfFiles = await client.files
|
|
324
|
+
.list({
|
|
325
|
+
filter: {
|
|
326
|
+
mimeType: "application/pdf",
|
|
327
|
+
assetIds: [123456789],
|
|
328
|
+
uploaded: true,
|
|
329
|
+
},
|
|
330
|
+
})
|
|
331
|
+
.autoPagingToArray();
|
|
332
|
+
|
|
333
|
+
// List files by source
|
|
334
|
+
const manualFiles = await client.files
|
|
335
|
+
.list({
|
|
336
|
+
filter: {
|
|
337
|
+
source: "manufacturer-website",
|
|
338
|
+
createdTime: {
|
|
339
|
+
min: new Date("2024-01-01"),
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
limit: 100,
|
|
343
|
+
})
|
|
344
|
+
.autoPagingToArray();
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### client.files.retrieve(ids, params?)
|
|
348
|
+
|
|
349
|
+
- **Description**: Retrieves specific files by ID
|
|
350
|
+
- **Parameters**:
|
|
351
|
+
- `ids` (IdEitherWithInstance[], required): File identifiers
|
|
352
|
+
- `params` (FileRetrieveParams, optional): Additional parameters
|
|
353
|
+
- **Return Type**: Promise<FileInfo[]>
|
|
354
|
+
- **Example**:
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
const files = await client.files.retrieve([
|
|
358
|
+
{ id: 8252999965991682 },
|
|
359
|
+
{ externalId: "manual-pump-001" },
|
|
360
|
+
]);
|
|
361
|
+
|
|
362
|
+
files.forEach((file) => {
|
|
363
|
+
console.log(`File: ${file.name}`);
|
|
364
|
+
console.log(`Size: ${file.size} bytes`);
|
|
365
|
+
console.log(`Uploaded: ${file.uploaded}`);
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### client.files.download(items)
|
|
370
|
+
|
|
371
|
+
- **Description**: Downloads file content
|
|
372
|
+
- **Parameters**:
|
|
373
|
+
- `items` (FileDownloadRequest[], required): Files to download
|
|
374
|
+
- **Return Type**: Promise<(FileContent | undefined)[]>
|
|
375
|
+
- **Example**:
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
const contents = await client.files.download([
|
|
379
|
+
{ id: 8252999965991682 },
|
|
380
|
+
{ externalId: "manual-pump-001" },
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
// Save first file
|
|
384
|
+
const fileContent = contents[0];
|
|
385
|
+
if (fileContent) {
|
|
386
|
+
// fileContent is ArrayBuffer
|
|
387
|
+
const buffer = Buffer.from(fileContent);
|
|
388
|
+
fs.writeFileSync("downloaded-manual.pdf", buffer);
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### client.files.search(query)
|
|
393
|
+
|
|
394
|
+
- **Description**: Searches files using text queries
|
|
395
|
+
- **Parameters**:
|
|
396
|
+
- `query` (FilesSearchFilter, required): Search parameters
|
|
397
|
+
- **Return Type**: Promise<FileInfo[]>
|
|
398
|
+
- **Example**:
|
|
399
|
+
|
|
400
|
+
```javascript
|
|
401
|
+
const searchResults = await client.files.search({
|
|
402
|
+
filter: {
|
|
403
|
+
mimeType: "application/pdf",
|
|
404
|
+
},
|
|
405
|
+
search: {
|
|
406
|
+
name: "pump manual",
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### client.files.update(changes)
|
|
412
|
+
|
|
413
|
+
- **Description**: Updates file metadata
|
|
414
|
+
- **Parameters**:
|
|
415
|
+
- `changes` (FileChangeUpdate[], required): Update operations
|
|
416
|
+
- **Return Type**: Promise<FileInfo[]>
|
|
417
|
+
- **Example**:
|
|
418
|
+
|
|
419
|
+
```javascript
|
|
420
|
+
const updated = await client.files.update([
|
|
421
|
+
{
|
|
422
|
+
id: 8252999965991682,
|
|
423
|
+
update: {
|
|
424
|
+
metadata: { set: { reviewed: "true", reviewer: "John Doe" } },
|
|
425
|
+
assetIds: { add: [234567890] },
|
|
426
|
+
labels: { add: [{ externalId: "approved" }] },
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
]);
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### client.files.delete(ids)
|
|
433
|
+
|
|
434
|
+
- **Description**: Deletes files
|
|
435
|
+
- **Parameters**:
|
|
436
|
+
- `ids` (IdEither[], required): File identifiers
|
|
437
|
+
- **Return Type**: Promise<{}>
|
|
438
|
+
- **Example**:
|
|
439
|
+
|
|
440
|
+
```javascript
|
|
441
|
+
await client.files.delete([
|
|
442
|
+
{ id: 8252999965991682 },
|
|
443
|
+
{ externalId: "obsolete-manual" },
|
|
444
|
+
]);
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Multipart Upload for Large Files
|
|
448
|
+
|
|
449
|
+
For files larger than 5GB, use multipart upload:
|
|
450
|
+
|
|
451
|
+
```javascript
|
|
452
|
+
// 1. Initialize multipart upload
|
|
453
|
+
const uploadSession = await client.files.multipartUploadSession.create({
|
|
454
|
+
name: "large-dataset.csv",
|
|
455
|
+
mimeType: "text/csv",
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// 2. Upload parts (5MB - 5GB each)
|
|
459
|
+
const uploadURLs = await client.files.multipartUploadSession.getUploadUrls({
|
|
460
|
+
id: uploadSession.id,
|
|
461
|
+
parts: 10, // Number of parts
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// 3. Upload each part to its URL
|
|
465
|
+
for (let i = 0; i < uploadURLs.urls.length; i++) {
|
|
466
|
+
const partData = getFilePart(i); // Your logic to get file chunks
|
|
467
|
+
await uploadToUrl(uploadURLs.urls[i], partData);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// 4. Complete upload
|
|
471
|
+
const file = await client.files.multipartUploadSession.complete({
|
|
472
|
+
id: uploadSession.id,
|
|
473
|
+
partETags: etags, // ETags from each part upload
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## Time Series API
|
|
478
|
+
|
|
479
|
+
Manages time series definitions for sensor data, measurements, and other time-based data.
|
|
480
|
+
|
|
481
|
+
### client.timeseries.create(items)
|
|
482
|
+
|
|
483
|
+
- **Description**: Creates new time series
|
|
484
|
+
- **Parameters**:
|
|
485
|
+
- `items` (ExternalTimeseries[], required): Time series definitions
|
|
486
|
+
- **Return Type**: Promise<Timeseries[]>
|
|
487
|
+
- **Example**:
|
|
488
|
+
|
|
489
|
+
```javascript
|
|
490
|
+
const timeseries = await client.timeseries.create([
|
|
491
|
+
{
|
|
492
|
+
externalId: "temperature-sensor-001",
|
|
493
|
+
name: "Temperature Sensor 001",
|
|
494
|
+
description: "Inlet temperature for pump P-001",
|
|
495
|
+
isString: false,
|
|
496
|
+
unit: "°C",
|
|
497
|
+
assetId: 123456789,
|
|
498
|
+
dataSetId: 987654321,
|
|
499
|
+
metadata: {
|
|
500
|
+
location: "inlet",
|
|
501
|
+
sensorType: "PT100",
|
|
502
|
+
calibrationDate: "2024-01-15",
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
externalId: "pump-status-001",
|
|
507
|
+
name: "Pump Status 001",
|
|
508
|
+
isString: true, // String time series
|
|
509
|
+
assetId: 123456789,
|
|
510
|
+
},
|
|
511
|
+
]);
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### client.timeseries.list(scope?)
|
|
515
|
+
|
|
516
|
+
- **Description**: Lists time series with filtering
|
|
517
|
+
- **Parameters**:
|
|
518
|
+
- `scope` (TimeseriesFilterQuery, optional): Filter options
|
|
519
|
+
- **Return Type**: CursorAndAsyncIterator<Timeseries>
|
|
520
|
+
- **Example**:
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
// List numeric time series for an asset
|
|
524
|
+
const numericSeries = await client.timeseries
|
|
525
|
+
.list({
|
|
526
|
+
filter: {
|
|
527
|
+
assetIds: [123456789],
|
|
528
|
+
isString: false,
|
|
529
|
+
},
|
|
530
|
+
})
|
|
531
|
+
.autoPagingToArray();
|
|
532
|
+
|
|
533
|
+
// List by unit
|
|
534
|
+
const temperatureSeries = await client.timeseries
|
|
535
|
+
.list({
|
|
536
|
+
filter: {
|
|
537
|
+
unit: "°C",
|
|
538
|
+
},
|
|
539
|
+
limit: 100,
|
|
540
|
+
})
|
|
541
|
+
.autoPagingToArray();
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### client.timeseries.retrieve(ids, params?)
|
|
545
|
+
|
|
546
|
+
- **Description**: Retrieves specific time series
|
|
547
|
+
- **Parameters**:
|
|
548
|
+
- `ids` (IdEitherWithInstance[], required): Time series identifiers
|
|
549
|
+
- `params` (TimeseriesRetrieveParams, optional): Additional parameters
|
|
550
|
+
- **Return Type**: Promise<Timeseries[]>
|
|
551
|
+
- **Example**:
|
|
552
|
+
|
|
553
|
+
```javascript
|
|
554
|
+
const timeseries = await client.timeseries.retrieve([
|
|
555
|
+
{ id: 987654321 },
|
|
556
|
+
{ externalId: "temperature-sensor-001" },
|
|
557
|
+
]);
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### client.timeseries.search(query)
|
|
561
|
+
|
|
562
|
+
- **Description**: Searches time series
|
|
563
|
+
- **Parameters**:
|
|
564
|
+
- `query` (TimeseriesSearchFilter, required): Search parameters
|
|
565
|
+
- **Return Type**: Promise<Timeseries[]>
|
|
566
|
+
- **Example**:
|
|
567
|
+
|
|
568
|
+
```javascript
|
|
569
|
+
const results = await client.timeseries.search({
|
|
570
|
+
filter: {
|
|
571
|
+
isString: false,
|
|
572
|
+
assetIds: [123456789],
|
|
573
|
+
},
|
|
574
|
+
search: {
|
|
575
|
+
query: "temperature",
|
|
576
|
+
},
|
|
577
|
+
limit: 50,
|
|
578
|
+
});
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### client.timeseries.update(changes)
|
|
582
|
+
|
|
583
|
+
- **Description**: Updates time series metadata
|
|
584
|
+
- **Parameters**:
|
|
585
|
+
- `changes` (TimeSeriesUpdate[], required): Update operations
|
|
586
|
+
- **Return Type**: Promise<Timeseries[]>
|
|
587
|
+
- **Example**:
|
|
588
|
+
|
|
589
|
+
```javascript
|
|
590
|
+
const updated = await client.timeseries.update([
|
|
591
|
+
{
|
|
592
|
+
id: 987654321,
|
|
593
|
+
update: {
|
|
594
|
+
name: { set: "Updated Temperature Sensor" },
|
|
595
|
+
unit: { set: "K" }, // Change to Kelvin
|
|
596
|
+
metadata: { set: { calibrated: "2024-02-01" } },
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
]);
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### client.timeseries.delete(ids)
|
|
603
|
+
|
|
604
|
+
- **Description**: Deletes time series (and all data points)
|
|
605
|
+
- **Parameters**:
|
|
606
|
+
- `ids` (IdEither[], required): Time series identifiers
|
|
607
|
+
- **Return Type**: Promise<{}>
|
|
608
|
+
- **Example**:
|
|
609
|
+
|
|
610
|
+
```javascript
|
|
611
|
+
await client.timeseries.delete([
|
|
612
|
+
{ id: 987654321 },
|
|
613
|
+
{ externalId: "obsolete-sensor" },
|
|
614
|
+
]);
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
## Data Points API
|
|
618
|
+
|
|
619
|
+
Manages the actual time series data values.
|
|
620
|
+
|
|
621
|
+
### client.datapoints.insert(items)
|
|
622
|
+
|
|
623
|
+
- **Description**: Inserts data points into time series
|
|
624
|
+
- **Parameters**:
|
|
625
|
+
- `items` (ExternalDatapointsQuery[], required): Data point batches
|
|
626
|
+
- **Return Type**: Promise<{}>
|
|
627
|
+
- **Example**:
|
|
628
|
+
|
|
629
|
+
```javascript
|
|
630
|
+
await client.datapoints.insert([
|
|
631
|
+
{
|
|
632
|
+
externalId: "temperature-sensor-001",
|
|
633
|
+
datapoints: [
|
|
634
|
+
{ timestamp: new Date("2024-01-01T12:00:00Z"), value: 25.5 },
|
|
635
|
+
{ timestamp: new Date("2024-01-01T12:01:00Z"), value: 25.7 },
|
|
636
|
+
{ timestamp: Date.now(), value: 26.1 },
|
|
637
|
+
],
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
id: 987654321, // Can use ID instead of externalId
|
|
641
|
+
datapoints: [
|
|
642
|
+
{ timestamp: 1704110400000, value: 101.3 }, // Unix timestamp in ms
|
|
643
|
+
],
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
externalId: "pump-status-001",
|
|
647
|
+
datapoints: [
|
|
648
|
+
{ timestamp: new Date(), value: "RUNNING" }, // String value
|
|
649
|
+
],
|
|
650
|
+
},
|
|
651
|
+
]);
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### client.datapoints.retrieve(query)
|
|
655
|
+
|
|
656
|
+
- **Description**: Retrieves data points with optional aggregation
|
|
657
|
+
- **Parameters**:
|
|
658
|
+
- `query` (DatapointsMultiQuery, required): Query parameters
|
|
659
|
+
- **Return Type**: Promise<DatapointAggregates[] | Datapoints[]>
|
|
660
|
+
- **Example**:
|
|
661
|
+
|
|
662
|
+
```javascript
|
|
663
|
+
// Get raw data points
|
|
664
|
+
const rawData = await client.datapoints.retrieve({
|
|
665
|
+
items: [
|
|
666
|
+
{
|
|
667
|
+
externalId: "temperature-sensor-001",
|
|
668
|
+
},
|
|
669
|
+
],
|
|
670
|
+
start: "2024-01-01",
|
|
671
|
+
end: new Date(),
|
|
672
|
+
limit: 1000,
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
// Get aggregated data
|
|
676
|
+
const aggregatedData = await client.datapoints.retrieve({
|
|
677
|
+
items: [
|
|
678
|
+
{
|
|
679
|
+
externalId: "temperature-sensor-001",
|
|
680
|
+
aggregates: ["average", "max", "min", "count"],
|
|
681
|
+
granularity: "1h",
|
|
682
|
+
},
|
|
683
|
+
],
|
|
684
|
+
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago
|
|
685
|
+
end: "now",
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// Multiple time series with different aggregations
|
|
689
|
+
const multiData = await client.datapoints.retrieve({
|
|
690
|
+
items: [
|
|
691
|
+
{
|
|
692
|
+
externalId: "temperature-sensor-001",
|
|
693
|
+
aggregates: ["average"],
|
|
694
|
+
granularity: "1h",
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
externalId: "pressure-sensor-001",
|
|
698
|
+
aggregates: ["max", "min"],
|
|
699
|
+
granularity: "1h",
|
|
700
|
+
},
|
|
701
|
+
],
|
|
702
|
+
start: "2024-01-01",
|
|
703
|
+
end: "2024-01-31",
|
|
704
|
+
timezone: "UTC",
|
|
705
|
+
});
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### client.datapoints.retrieveLatest(items, params?)
|
|
709
|
+
|
|
710
|
+
- **Description**: Gets the latest data point before a given time
|
|
711
|
+
- **Parameters**:
|
|
712
|
+
- `items` (LatestDataBeforeRequest[], required): Query items
|
|
713
|
+
- `params` (LatestDataParams, optional): Additional parameters
|
|
714
|
+
- **Return Type**: Promise<Datapoints[]>
|
|
715
|
+
- **Example**:
|
|
716
|
+
|
|
717
|
+
```javascript
|
|
718
|
+
const latestData = await client.datapoints.retrieveLatest([
|
|
719
|
+
{
|
|
720
|
+
externalId: "temperature-sensor-001",
|
|
721
|
+
before: "now",
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
id: 987654321,
|
|
725
|
+
before: new Date("2024-01-01"),
|
|
726
|
+
},
|
|
727
|
+
]);
|
|
728
|
+
|
|
729
|
+
latestData.forEach((series) => {
|
|
730
|
+
const latest = series.datapoints[0];
|
|
731
|
+
console.log(
|
|
732
|
+
`Series ${series.externalId}: ${latest.value} at ${new Date(
|
|
733
|
+
latest.timestamp
|
|
734
|
+
)}`
|
|
735
|
+
);
|
|
736
|
+
});
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### client.datapoints.delete(items)
|
|
740
|
+
|
|
741
|
+
- **Description**: Deletes data points from time series
|
|
742
|
+
- **Parameters**:
|
|
743
|
+
- `items` (DatapointsDeleteRequest[], required): Deletion ranges
|
|
744
|
+
- **Return Type**: Promise<{}>
|
|
745
|
+
- **Example**:
|
|
746
|
+
|
|
747
|
+
```javascript
|
|
748
|
+
// Delete specific time range
|
|
749
|
+
await client.datapoints.delete([
|
|
750
|
+
{
|
|
751
|
+
externalId: "temperature-sensor-001",
|
|
752
|
+
inclusiveBegin: new Date("2024-01-01"),
|
|
753
|
+
exclusiveEnd: new Date("2024-01-02"),
|
|
754
|
+
},
|
|
755
|
+
]);
|
|
756
|
+
|
|
757
|
+
// Delete all data points
|
|
758
|
+
await client.datapoints.delete([
|
|
759
|
+
{
|
|
760
|
+
id: 987654321,
|
|
761
|
+
// Omit time range to delete all
|
|
762
|
+
},
|
|
763
|
+
]);
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
### Synthetic Time Series
|
|
767
|
+
|
|
768
|
+
Query calculated time series using expressions:
|
|
769
|
+
|
|
770
|
+
```javascript
|
|
771
|
+
const synthetic = await client.timeseries.syntheticQuery([
|
|
772
|
+
{
|
|
773
|
+
expression: "ts{externalId='temperature-sensor-001'} * 1.8 + 32", // Convert C to F
|
|
774
|
+
start: "2024-01-01",
|
|
775
|
+
end: "2024-01-02",
|
|
776
|
+
limit: 100,
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
expression: "ts{externalId='flow-rate'} * ts{externalId='density'}", // Calculate mass flow
|
|
780
|
+
start: "2024-01-01",
|
|
781
|
+
end: "2024-01-02",
|
|
782
|
+
granularity: "1h",
|
|
783
|
+
aggregates: ["average"],
|
|
784
|
+
},
|
|
785
|
+
]);
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
## Raw API
|
|
789
|
+
|
|
790
|
+
The Raw API provides flexible NoSQL storage for data that doesn't fit the standard resource types.
|
|
791
|
+
|
|
792
|
+
### Database Operations
|
|
793
|
+
|
|
794
|
+
#### client.raw.createDatabases(items)
|
|
795
|
+
|
|
796
|
+
- **Description**: Creates raw databases
|
|
797
|
+
- **Parameters**:
|
|
798
|
+
- `items` (RawDBName[], required): Database names
|
|
799
|
+
- **Return Type**: Promise<RawDB[]>
|
|
800
|
+
- **Example**:
|
|
801
|
+
|
|
802
|
+
```javascript
|
|
803
|
+
const databases = await client.raw.createDatabases([
|
|
804
|
+
{ name: "sensor_configurations" },
|
|
805
|
+
{ name: "maintenance_logs" },
|
|
806
|
+
]);
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
#### client.raw.listDatabases(scope?)
|
|
810
|
+
|
|
811
|
+
- **Description**: Lists raw databases
|
|
812
|
+
- **Parameters**:
|
|
813
|
+
- `scope` (ListRawDatabases, optional): List options
|
|
814
|
+
- **Return Type**: CursorAndAsyncIterator<RawDB>
|
|
815
|
+
- **Example**:
|
|
816
|
+
|
|
817
|
+
```javascript
|
|
818
|
+
const databases = await client.raw.listDatabases().autoPagingToArray();
|
|
819
|
+
databases.forEach((db) => {
|
|
820
|
+
console.log(`Database: ${db.name}`);
|
|
821
|
+
});
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
#### client.raw.deleteDatabases(items, params?)
|
|
825
|
+
|
|
826
|
+
- **Description**: Deletes databases and all their contents
|
|
827
|
+
- **Parameters**:
|
|
828
|
+
- `items` (RawDBName[], required): Database names
|
|
829
|
+
- `params` (RawDatabaseDeleteParams, optional): Delete options
|
|
830
|
+
- **Return Type**: Promise<{}>
|
|
831
|
+
- **Example**:
|
|
832
|
+
|
|
833
|
+
```javascript
|
|
834
|
+
await client.raw.deleteDatabases([{ name: "obsolete_data" }], {
|
|
835
|
+
recursive: true, // Delete even if contains tables
|
|
836
|
+
});
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
### Table Operations
|
|
840
|
+
|
|
841
|
+
#### client.raw.createTables(databaseName, items, ensureParent?)
|
|
842
|
+
|
|
843
|
+
- **Description**: Creates tables in a database
|
|
844
|
+
- **Parameters**:
|
|
845
|
+
- `databaseName` (string, required): Database name
|
|
846
|
+
- `items` (RawDBTableName[], required): Table names
|
|
847
|
+
- `ensureParent` (boolean, optional): Create database if missing
|
|
848
|
+
- **Return Type**: Promise<RawDBTable[]>
|
|
849
|
+
- **Example**:
|
|
850
|
+
|
|
851
|
+
```javascript
|
|
852
|
+
const tables = await client.raw.createTables(
|
|
853
|
+
"sensor_configurations",
|
|
854
|
+
[{ name: "temperature_sensors" }, { name: "pressure_sensors" }],
|
|
855
|
+
true // Create database if it doesn't exist
|
|
856
|
+
);
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
#### client.raw.listTables(databaseName, scope?)
|
|
860
|
+
|
|
861
|
+
- **Description**: Lists tables in a database
|
|
862
|
+
- **Parameters**:
|
|
863
|
+
- `databaseName` (string, required): Database name
|
|
864
|
+
- `scope` (ListRawTables, optional): List options
|
|
865
|
+
- **Return Type**: CursorAndAsyncIterator<RawDBTable>
|
|
866
|
+
- **Example**:
|
|
867
|
+
|
|
868
|
+
```javascript
|
|
869
|
+
const tables = await client.raw
|
|
870
|
+
.listTables("sensor_configurations")
|
|
871
|
+
.autoPagingToArray();
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
#### client.raw.deleteTables(databaseName, items)
|
|
875
|
+
|
|
876
|
+
- **Description**: Deletes tables and all their rows
|
|
877
|
+
- **Parameters**:
|
|
878
|
+
- `databaseName` (string, required): Database name
|
|
879
|
+
- `items` (RawDBTableName[], required): Table names
|
|
880
|
+
- **Return Type**: Promise<{}>
|
|
881
|
+
- **Example**:
|
|
882
|
+
|
|
883
|
+
```javascript
|
|
884
|
+
await client.raw.deleteTables("sensor_configurations", [
|
|
885
|
+
{ name: "obsolete_table" },
|
|
886
|
+
]);
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
### Row Operations
|
|
890
|
+
|
|
891
|
+
#### client.raw.insertRows(databaseName, tableName, items, ensureParent?)
|
|
892
|
+
|
|
893
|
+
- **Description**: Inserts or updates rows in a table
|
|
894
|
+
- **Parameters**:
|
|
895
|
+
- `databaseName` (string, required): Database name
|
|
896
|
+
- `tableName` (string, required): Table name
|
|
897
|
+
- `items` (RawDBRowInsert[], required): Rows to insert
|
|
898
|
+
- `ensureParent` (boolean, optional): Create table/database if missing
|
|
899
|
+
- **Return Type**: Promise<{}>
|
|
900
|
+
- **Example**:
|
|
901
|
+
|
|
902
|
+
```javascript
|
|
903
|
+
await client.raw.insertRows(
|
|
904
|
+
"sensor_configurations",
|
|
905
|
+
"temperature_sensors",
|
|
906
|
+
[
|
|
907
|
+
{
|
|
908
|
+
key: "sensor-001",
|
|
909
|
+
columns: {
|
|
910
|
+
location: "Building A, Floor 2",
|
|
911
|
+
type: "PT100",
|
|
912
|
+
range_min: -50,
|
|
913
|
+
range_max: 200,
|
|
914
|
+
unit: "celsius",
|
|
915
|
+
calibration_date: "2024-01-15",
|
|
916
|
+
config: {
|
|
917
|
+
// Nested JSON
|
|
918
|
+
sampling_rate: 1000,
|
|
919
|
+
filter: "moving_average",
|
|
920
|
+
window_size: 10,
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
key: "sensor-002",
|
|
926
|
+
columns: {
|
|
927
|
+
location: "Building B, Floor 1",
|
|
928
|
+
type: "Thermocouple",
|
|
929
|
+
range_min: 0,
|
|
930
|
+
range_max: 1000,
|
|
931
|
+
unit: "celsius",
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
],
|
|
935
|
+
true // Create table/database if missing
|
|
936
|
+
);
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
#### client.raw.listRows(databaseName, tableName, options?)
|
|
940
|
+
|
|
941
|
+
- **Description**: Lists rows from a table with filtering
|
|
942
|
+
- **Parameters**:
|
|
943
|
+
- `databaseName` (string, required): Database name
|
|
944
|
+
- `tableName` (string, required): Table name
|
|
945
|
+
- `options` (ListRawRows, optional): Query options
|
|
946
|
+
- **Return Type**: CursorAndAsyncIterator<RawDBRow>
|
|
947
|
+
- **Example**:
|
|
948
|
+
|
|
949
|
+
```javascript
|
|
950
|
+
// List all rows
|
|
951
|
+
const allRows = await client.raw
|
|
952
|
+
.listRows("sensor_configurations", "temperature_sensors")
|
|
953
|
+
.autoPagingToArray();
|
|
954
|
+
|
|
955
|
+
// Filter by columns
|
|
956
|
+
const filteredRows = await client.raw
|
|
957
|
+
.listRows("sensor_configurations", "temperature_sensors", {
|
|
958
|
+
columns: ["location", "type", "unit"],
|
|
959
|
+
limit: 100,
|
|
960
|
+
})
|
|
961
|
+
.autoPagingToArray();
|
|
962
|
+
|
|
963
|
+
// Filter by key range
|
|
964
|
+
const rangeRows = await client.raw
|
|
965
|
+
.listRows("maintenance_logs", "pump_maintenance", {
|
|
966
|
+
minLastUpdatedTime: new Date("2024-01-01"),
|
|
967
|
+
maxLastUpdatedTime: new Date("2024-01-31"),
|
|
968
|
+
})
|
|
969
|
+
.autoPagingToArray();
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
#### client.raw.retrieveRows(databaseName, tableName, items)
|
|
973
|
+
|
|
974
|
+
- **Description**: Retrieves specific rows by key
|
|
975
|
+
- **Parameters**:
|
|
976
|
+
- `databaseName` (string, required): Database name
|
|
977
|
+
- `tableName` (string, required): Table name
|
|
978
|
+
- `items` (RawDBRowKey[], required): Row keys
|
|
979
|
+
- **Return Type**: Promise<RawDBRow[]>
|
|
980
|
+
- **Example**:
|
|
981
|
+
|
|
982
|
+
```javascript
|
|
983
|
+
const rows = await client.raw.retrieveRows(
|
|
984
|
+
"sensor_configurations",
|
|
985
|
+
"temperature_sensors",
|
|
986
|
+
[{ key: "sensor-001" }, { key: "sensor-002" }]
|
|
987
|
+
);
|
|
988
|
+
|
|
989
|
+
rows.forEach((row) => {
|
|
990
|
+
console.log(`Sensor ${row.key}: ${JSON.stringify(row.columns)}`);
|
|
991
|
+
});
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
#### client.raw.deleteRows(databaseName, tableName, items)
|
|
995
|
+
|
|
996
|
+
- **Description**: Deletes rows from a table
|
|
997
|
+
- **Parameters**:
|
|
998
|
+
- `databaseName` (string, required): Database name
|
|
999
|
+
- `tableName` (string, required): Table name
|
|
1000
|
+
- `items` (RawDBRowKey[], required): Row keys to delete
|
|
1001
|
+
- **Return Type**: Promise<{}>
|
|
1002
|
+
- **Example**:
|
|
1003
|
+
|
|
1004
|
+
```javascript
|
|
1005
|
+
await client.raw.deleteRows("sensor_configurations", "temperature_sensors", [
|
|
1006
|
+
{ key: "obsolete-sensor-001" },
|
|
1007
|
+
{ key: "obsolete-sensor-002" },
|
|
1008
|
+
]);
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
## Units API
|
|
1012
|
+
|
|
1013
|
+
The Units API provides access to standardized units of measurement and unit systems.
|
|
1014
|
+
|
|
1015
|
+
### client.units.list()
|
|
1016
|
+
|
|
1017
|
+
- **Description**: Lists all supported units
|
|
1018
|
+
- **Parameters**: None
|
|
1019
|
+
- **Return Type**: CursorAndAsyncIterator<Unit>
|
|
1020
|
+
- **Example**:
|
|
1021
|
+
|
|
1022
|
+
```javascript
|
|
1023
|
+
const units = await client.units.list().autoPagingToArray();
|
|
1024
|
+
|
|
1025
|
+
// Group units by quantity
|
|
1026
|
+
const unitsByQuantity = {};
|
|
1027
|
+
units.forEach((unit) => {
|
|
1028
|
+
if (!unitsByQuantity[unit.quantity]) {
|
|
1029
|
+
unitsByQuantity[unit.quantity] = [];
|
|
1030
|
+
}
|
|
1031
|
+
unitsByQuantity[unit.quantity].push(unit);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
console.log("Temperature units:", unitsByQuantity["Temperature"]);
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
### client.units.retrieve(ids)
|
|
1038
|
+
|
|
1039
|
+
- **Description**: Retrieves specific units
|
|
1040
|
+
- **Parameters**:
|
|
1041
|
+
- `ids` (ExternalId[], required): Unit external IDs
|
|
1042
|
+
- **Return Type**: Promise<Unit[]>
|
|
1043
|
+
- **Example**:
|
|
1044
|
+
|
|
1045
|
+
```javascript
|
|
1046
|
+
const units = await client.units.retrieve([
|
|
1047
|
+
{ externalId: "temperature:deg_c" },
|
|
1048
|
+
{ externalId: "pressure:bar" },
|
|
1049
|
+
{ externalId: "flow:m3/h" },
|
|
1050
|
+
]);
|
|
1051
|
+
|
|
1052
|
+
units.forEach((unit) => {
|
|
1053
|
+
console.log(`${unit.name} (${unit.symbol})`);
|
|
1054
|
+
console.log(` Quantity: ${unit.quantity}`);
|
|
1055
|
+
console.log(` Aliases: ${unit.aliases?.join(", ")}`);
|
|
1056
|
+
});
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
### client.units.listUnitSystems()
|
|
1060
|
+
|
|
1061
|
+
- **Description**: Lists all supported unit systems
|
|
1062
|
+
- **Parameters**: None
|
|
1063
|
+
- **Return Type**: Promise<UnitSystem[]>
|
|
1064
|
+
- **Example**:
|
|
1065
|
+
|
|
1066
|
+
```javascript
|
|
1067
|
+
const unitSystems = await client.units.listUnitSystems();
|
|
1068
|
+
|
|
1069
|
+
unitSystems.forEach((system) => {
|
|
1070
|
+
console.log(`System: ${system.name}`);
|
|
1071
|
+
console.log(` Quantities: ${Object.keys(system.quantities).join(", ")}`);
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
// Get SI units for each quantity
|
|
1075
|
+
const siSystem = unitSystems.find((s) => s.name === "SI");
|
|
1076
|
+
if (siSystem) {
|
|
1077
|
+
console.log("SI base units:");
|
|
1078
|
+
Object.entries(siSystem.quantities).forEach(([quantity, unit]) => {
|
|
1079
|
+
console.log(` ${quantity}: ${unit}`);
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
## Data Models
|
|
1085
|
+
|
|
1086
|
+
### 3D Model Structure
|
|
1087
|
+
|
|
1088
|
+
```typescript
|
|
1089
|
+
interface Model3D {
|
|
1090
|
+
id: number;
|
|
1091
|
+
name: string;
|
|
1092
|
+
description?: string;
|
|
1093
|
+
createdTime: number;
|
|
1094
|
+
dataSetId?: number;
|
|
1095
|
+
metadata?: Record<string, string>;
|
|
1096
|
+
published: boolean;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
interface Revision3D {
|
|
1100
|
+
id: number;
|
|
1101
|
+
fileId: number;
|
|
1102
|
+
published: boolean;
|
|
1103
|
+
status: "Queued" | "Processing" | "Done" | "Failed";
|
|
1104
|
+
createdTime: number;
|
|
1105
|
+
assetMappingCount: number;
|
|
1106
|
+
metadata?: Record<string, string>;
|
|
1107
|
+
rotation?: [number, number, number];
|
|
1108
|
+
scale?: [number, number, number];
|
|
1109
|
+
translation?: [number, number, number];
|
|
1110
|
+
camera?: {
|
|
1111
|
+
target: [number, number, number];
|
|
1112
|
+
position: [number, number, number];
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
interface AssetMapping3D {
|
|
1117
|
+
nodeId: number;
|
|
1118
|
+
assetId: number;
|
|
1119
|
+
treeIndex?: number;
|
|
1120
|
+
subtreeSize?: number;
|
|
1121
|
+
}
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
### File Structure
|
|
1125
|
+
|
|
1126
|
+
```typescript
|
|
1127
|
+
interface FileInfo {
|
|
1128
|
+
id: number;
|
|
1129
|
+
externalId?: string;
|
|
1130
|
+
name: string;
|
|
1131
|
+
source?: string;
|
|
1132
|
+
mimeType?: string;
|
|
1133
|
+
metadata?: Record<string, string>;
|
|
1134
|
+
assetIds?: number[];
|
|
1135
|
+
dataSetId?: number;
|
|
1136
|
+
sourceCreatedTime?: number;
|
|
1137
|
+
sourceModifiedTime?: number;
|
|
1138
|
+
uploaded: boolean;
|
|
1139
|
+
uploadedTime?: number;
|
|
1140
|
+
createdTime: number;
|
|
1141
|
+
lastUpdatedTime: number;
|
|
1142
|
+
uploadUrl?: string;
|
|
1143
|
+
downloadUrl?: string;
|
|
1144
|
+
size?: number;
|
|
1145
|
+
}
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
### Time Series Structure
|
|
1149
|
+
|
|
1150
|
+
```typescript
|
|
1151
|
+
interface Timeseries {
|
|
1152
|
+
id: number;
|
|
1153
|
+
externalId?: string;
|
|
1154
|
+
name?: string;
|
|
1155
|
+
isString: boolean;
|
|
1156
|
+
metadata?: Record<string, string>;
|
|
1157
|
+
unit?: string;
|
|
1158
|
+
assetId?: number;
|
|
1159
|
+
isStep: boolean;
|
|
1160
|
+
description?: string;
|
|
1161
|
+
securityCategories?: number[];
|
|
1162
|
+
dataSetId?: number;
|
|
1163
|
+
createdTime: number;
|
|
1164
|
+
lastUpdatedTime: number;
|
|
1165
|
+
}
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
### Data Point Structure
|
|
1169
|
+
|
|
1170
|
+
```typescript
|
|
1171
|
+
interface Datapoint {
|
|
1172
|
+
timestamp: number | Date;
|
|
1173
|
+
value: number | string;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
interface DatapointAggregates {
|
|
1177
|
+
timestamp: number;
|
|
1178
|
+
average?: number;
|
|
1179
|
+
max?: number;
|
|
1180
|
+
min?: number;
|
|
1181
|
+
count?: number;
|
|
1182
|
+
sum?: number;
|
|
1183
|
+
interpolation?: number;
|
|
1184
|
+
stepInterpolation?: number;
|
|
1185
|
+
totalVariation?: number;
|
|
1186
|
+
continuousVariance?: number;
|
|
1187
|
+
discreteVariance?: number;
|
|
1188
|
+
}
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
### Raw Storage Structure
|
|
1192
|
+
|
|
1193
|
+
```typescript
|
|
1194
|
+
interface RawDB {
|
|
1195
|
+
name: string;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
interface RawDBTable {
|
|
1199
|
+
name: string;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
interface RawDBRow {
|
|
1203
|
+
key: string;
|
|
1204
|
+
columns: Record<string, any>;
|
|
1205
|
+
lastUpdatedTime: number;
|
|
1206
|
+
}
|
|
1207
|
+
```
|
|
1208
|
+
|
|
1209
|
+
### Unit Structure
|
|
1210
|
+
|
|
1211
|
+
```typescript
|
|
1212
|
+
interface Unit {
|
|
1213
|
+
externalId: string;
|
|
1214
|
+
name: string;
|
|
1215
|
+
longName: string;
|
|
1216
|
+
symbol: string;
|
|
1217
|
+
aliases?: string[];
|
|
1218
|
+
quantity: string;
|
|
1219
|
+
conversion?: {
|
|
1220
|
+
multiplier: number;
|
|
1221
|
+
offset: number;
|
|
1222
|
+
};
|
|
1223
|
+
source?: string;
|
|
1224
|
+
sourceReference?: string;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
interface UnitSystem {
|
|
1228
|
+
name: string;
|
|
1229
|
+
quantities: Record<string, string>; // quantity -> unit externalId
|
|
1230
|
+
}
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
## Error Handling
|
|
1234
|
+
|
|
1235
|
+
All APIs use the same error structure as other CDF APIs:
|
|
1236
|
+
|
|
1237
|
+
```javascript
|
|
1238
|
+
try {
|
|
1239
|
+
await client.files.upload({ name: "test.txt" }, fileContent);
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
if (error instanceof CogniteError) {
|
|
1242
|
+
switch (error.status) {
|
|
1243
|
+
case 400:
|
|
1244
|
+
console.error("Invalid request:", error.errorMessage);
|
|
1245
|
+
break;
|
|
1246
|
+
case 401:
|
|
1247
|
+
console.error("Authentication failed");
|
|
1248
|
+
break;
|
|
1249
|
+
case 409:
|
|
1250
|
+
console.error("Conflict:", error.errorMessage);
|
|
1251
|
+
break;
|
|
1252
|
+
case 413:
|
|
1253
|
+
console.error("File too large");
|
|
1254
|
+
break;
|
|
1255
|
+
case 429:
|
|
1256
|
+
console.error("Rate limited, retry later");
|
|
1257
|
+
break;
|
|
1258
|
+
default:
|
|
1259
|
+
console.error(`Error ${error.status}: ${error.errorMessage}`);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
## Code Example
|
|
1266
|
+
|
|
1267
|
+
Here's a comprehensive example using multiple APIs together:
|
|
1268
|
+
|
|
1269
|
+
```javascript
|
|
1270
|
+
import { CogniteClient } from "@cognite/sdk";
|
|
1271
|
+
import fs from "fs";
|
|
1272
|
+
|
|
1273
|
+
async function industrialDataExample() {
|
|
1274
|
+
// 1. Initialize client
|
|
1275
|
+
const client = new CogniteClient({
|
|
1276
|
+
appId: "IndustrialDataApp",
|
|
1277
|
+
project: "my-project",
|
|
1278
|
+
oidcTokenProvider: async () => process.env.CDF_ACCESS_TOKEN,
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
try {
|
|
1282
|
+
// 2. Create time series for sensor data
|
|
1283
|
+
console.log("Creating time series...");
|
|
1284
|
+
const timeseries = await client.timeseries.create([
|
|
1285
|
+
{
|
|
1286
|
+
externalId: "temp-sensor-tank-001",
|
|
1287
|
+
name: "Tank 001 Temperature",
|
|
1288
|
+
unit: "°C",
|
|
1289
|
+
description: "Temperature sensor in storage tank 001",
|
|
1290
|
+
metadata: {
|
|
1291
|
+
location: "Storage Area A",
|
|
1292
|
+
sensorType: "PT100",
|
|
1293
|
+
},
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
externalId: "level-sensor-tank-001",
|
|
1297
|
+
name: "Tank 001 Level",
|
|
1298
|
+
unit: "m",
|
|
1299
|
+
description: "Level sensor in storage tank 001",
|
|
1300
|
+
},
|
|
1301
|
+
]);
|
|
1302
|
+
console.log(`Created ${timeseries.length} time series`);
|
|
1303
|
+
|
|
1304
|
+
// 3. Insert historical data points
|
|
1305
|
+
console.log("Inserting data points...");
|
|
1306
|
+
const now = Date.now();
|
|
1307
|
+
const dataPoints = [];
|
|
1308
|
+
for (let i = 0; i < 24; i++) {
|
|
1309
|
+
dataPoints.push({
|
|
1310
|
+
timestamp: now - i * 3600000, // Hourly data
|
|
1311
|
+
value: 20 + Math.random() * 10, // 20-30°C
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
await client.datapoints.insert([
|
|
1316
|
+
{
|
|
1317
|
+
externalId: "temp-sensor-tank-001",
|
|
1318
|
+
datapoints: dataPoints,
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
externalId: "level-sensor-tank-001",
|
|
1322
|
+
datapoints: dataPoints.map((dp) => ({
|
|
1323
|
+
timestamp: dp.timestamp,
|
|
1324
|
+
value: 3 + Math.random() * 2, // 3-5m
|
|
1325
|
+
})),
|
|
1326
|
+
},
|
|
1327
|
+
]);
|
|
1328
|
+
console.log("Data points inserted");
|
|
1329
|
+
|
|
1330
|
+
// 4. Upload equipment documentation
|
|
1331
|
+
console.log("Uploading documentation...");
|
|
1332
|
+
const pdfContent = fs.readFileSync("tank-manual.pdf");
|
|
1333
|
+
const file = await client.files.upload(
|
|
1334
|
+
{
|
|
1335
|
+
name: "Tank 001 Operations Manual.pdf",
|
|
1336
|
+
externalId: "manual-tank-001",
|
|
1337
|
+
mimeType: "application/pdf",
|
|
1338
|
+
metadata: {
|
|
1339
|
+
documentType: "operations-manual",
|
|
1340
|
+
equipment: "tank-001",
|
|
1341
|
+
version: "2.0",
|
|
1342
|
+
},
|
|
1343
|
+
},
|
|
1344
|
+
pdfContent
|
|
1345
|
+
);
|
|
1346
|
+
console.log(`Uploaded file: ${file.name}`);
|
|
1347
|
+
|
|
1348
|
+
// 5. Create 3D model for the facility
|
|
1349
|
+
console.log("Creating 3D model...");
|
|
1350
|
+
const model3D = await client.models3D.create([
|
|
1351
|
+
{
|
|
1352
|
+
name: "Storage Facility 3D Model",
|
|
1353
|
+
description: "Complete 3D model of storage facility including tanks",
|
|
1354
|
+
metadata: {
|
|
1355
|
+
source: "Revit",
|
|
1356
|
+
lastUpdated: "2024-01-15",
|
|
1357
|
+
},
|
|
1358
|
+
},
|
|
1359
|
+
]);
|
|
1360
|
+
console.log(`Created 3D model: ${model3D[0].name}`);
|
|
1361
|
+
|
|
1362
|
+
// 6. Store sensor configurations in Raw
|
|
1363
|
+
console.log("Storing sensor configurations...");
|
|
1364
|
+
await client.raw.createDatabases([{ name: "sensor_configs" }]);
|
|
1365
|
+
await client.raw.createTables("sensor_configs", [
|
|
1366
|
+
{ name: "temperature_sensors" },
|
|
1367
|
+
]);
|
|
1368
|
+
|
|
1369
|
+
await client.raw.insertRows("sensor_configs", "temperature_sensors", [
|
|
1370
|
+
{
|
|
1371
|
+
key: "temp-sensor-tank-001",
|
|
1372
|
+
columns: {
|
|
1373
|
+
type: "PT100",
|
|
1374
|
+
range: { min: -50, max: 200 },
|
|
1375
|
+
accuracy: 0.1,
|
|
1376
|
+
calibration: {
|
|
1377
|
+
lastDate: "2024-01-15",
|
|
1378
|
+
nextDate: "2025-01-15",
|
|
1379
|
+
certificate: "CERT-2024-0123",
|
|
1380
|
+
},
|
|
1381
|
+
alarms: {
|
|
1382
|
+
high: 35,
|
|
1383
|
+
highHigh: 40,
|
|
1384
|
+
low: 15,
|
|
1385
|
+
lowLow: 10,
|
|
1386
|
+
},
|
|
1387
|
+
},
|
|
1388
|
+
},
|
|
1389
|
+
]);
|
|
1390
|
+
console.log("Sensor configuration stored");
|
|
1391
|
+
|
|
1392
|
+
// 7. Query aggregated data
|
|
1393
|
+
console.log("Retrieving aggregated temperature data...");
|
|
1394
|
+
const aggregatedData = await client.datapoints.retrieve({
|
|
1395
|
+
items: [
|
|
1396
|
+
{
|
|
1397
|
+
externalId: "temp-sensor-tank-001",
|
|
1398
|
+
aggregates: ["average", "max", "min"],
|
|
1399
|
+
granularity: "6h",
|
|
1400
|
+
},
|
|
1401
|
+
],
|
|
1402
|
+
start: now - 24 * 3600000,
|
|
1403
|
+
end: now,
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
console.log("Temperature statistics (6-hour aggregates):");
|
|
1407
|
+
aggregatedData[0].datapoints.forEach((dp) => {
|
|
1408
|
+
console.log(
|
|
1409
|
+
` ${new Date(dp.timestamp).toISOString()}: ` +
|
|
1410
|
+
`Avg=${dp.average?.toFixed(1)}°C, ` +
|
|
1411
|
+
`Max=${dp.max?.toFixed(1)}°C, ` +
|
|
1412
|
+
`Min=${dp.min?.toFixed(1)}°C`
|
|
1413
|
+
);
|
|
1414
|
+
});
|
|
1415
|
+
|
|
1416
|
+
// 8. Search for related files
|
|
1417
|
+
console.log("\nSearching for tank documentation...");
|
|
1418
|
+
const documents = await client.files.search({
|
|
1419
|
+
search: {
|
|
1420
|
+
name: "tank",
|
|
1421
|
+
},
|
|
1422
|
+
filter: {
|
|
1423
|
+
mimeType: "application/pdf",
|
|
1424
|
+
},
|
|
1425
|
+
limit: 10,
|
|
1426
|
+
});
|
|
1427
|
+
console.log(`Found ${documents.length} tank-related documents`);
|
|
1428
|
+
|
|
1429
|
+
// 9. Get sensor configuration from Raw
|
|
1430
|
+
console.log("\nRetrieving sensor configuration...");
|
|
1431
|
+
const sensorConfig = await client.raw.retrieveRows(
|
|
1432
|
+
"sensor_configs",
|
|
1433
|
+
"temperature_sensors",
|
|
1434
|
+
[{ key: "temp-sensor-tank-001" }]
|
|
1435
|
+
);
|
|
1436
|
+
|
|
1437
|
+
if (sensorConfig.length > 0) {
|
|
1438
|
+
const config = sensorConfig[0].columns;
|
|
1439
|
+
console.log("Sensor Configuration:");
|
|
1440
|
+
console.log(` Type: ${config.type}`);
|
|
1441
|
+
console.log(` Range: ${config.range.min}°C to ${config.range.max}°C`);
|
|
1442
|
+
console.log(` High Alarm: ${config.alarms.high}°C`);
|
|
1443
|
+
console.log(` Next Calibration: ${config.calibration.nextDate}`);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
// 10. List available units
|
|
1447
|
+
console.log("\nAvailable temperature units:");
|
|
1448
|
+
const units = await client.units.list().autoPagingToArray();
|
|
1449
|
+
const tempUnits = units.filter((u) => u.quantity === "Temperature");
|
|
1450
|
+
tempUnits.forEach((unit) => {
|
|
1451
|
+
console.log(` ${unit.name} (${unit.symbol})`);
|
|
1452
|
+
});
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
if (error instanceof CogniteError) {
|
|
1455
|
+
console.error(`CDF Error ${error.status}: ${error.errorMessage}`);
|
|
1456
|
+
console.error(`Request ID: ${error.requestId}`);
|
|
1457
|
+
} else {
|
|
1458
|
+
console.error("Unexpected error:", error);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
// Run the example
|
|
1464
|
+
industrialDataExample();
|
|
1465
|
+
```
|
|
1466
|
+
|
|
1467
|
+
This example demonstrates:
|
|
1468
|
+
|
|
1469
|
+
- Creating time series for sensor data
|
|
1470
|
+
- Inserting and retrieving data points with aggregation
|
|
1471
|
+
- Uploading and managing files
|
|
1472
|
+
- Working with 3D models
|
|
1473
|
+
- Using Raw storage for flexible data
|
|
1474
|
+
- Querying units of measurement
|
|
1475
|
+
- Error handling
|
|
1476
|
+
|
|
1477
|
+
## Best Practices
|
|
1478
|
+
|
|
1479
|
+
1. **Time Series Design**
|
|
1480
|
+
|
|
1481
|
+
- Use meaningful external IDs following a naming convention
|
|
1482
|
+
- Set appropriate units for automatic unit conversion
|
|
1483
|
+
- Use string time series for status/state data
|
|
1484
|
+
- Consider data density when setting up aggregations
|
|
1485
|
+
|
|
1486
|
+
2. **Data Points Management**
|
|
1487
|
+
|
|
1488
|
+
- Batch insert data points for efficiency (up to 100,000 per request)
|
|
1489
|
+
- Use appropriate granularity for aggregations
|
|
1490
|
+
- Consider using synthetic time series for calculations
|
|
1491
|
+
- Store raw data and use aggregations for visualization
|
|
1492
|
+
|
|
1493
|
+
3. **File Management**
|
|
1494
|
+
|
|
1495
|
+
- Use external IDs for files that need stable references
|
|
1496
|
+
- Add comprehensive metadata for searchability
|
|
1497
|
+
- Use multipart upload for files over 5GB
|
|
1498
|
+
- Link files to relevant assets
|
|
1499
|
+
|
|
1500
|
+
4. **Raw Storage Usage**
|
|
1501
|
+
|
|
1502
|
+
- Use Raw for configuration data and flexible schemas
|
|
1503
|
+
- Design meaningful key structures for efficient queries
|
|
1504
|
+
- Consider data size limits (4MB per row)
|
|
1505
|
+
- Use columns parameter to retrieve only needed data
|
|
1506
|
+
|
|
1507
|
+
5. **3D Models**
|
|
1508
|
+
|
|
1509
|
+
- Publish revisions only when ready for consumption
|
|
1510
|
+
- Use asset mappings to link 3D nodes to assets
|
|
1511
|
+
- Set appropriate transformation matrices for alignment
|
|
1512
|
+
- Add metadata for version tracking
|
|
1513
|
+
|
|
1514
|
+
6. **Performance Optimization**
|
|
1515
|
+
|
|
1516
|
+
- Use cursor pagination for large result sets
|
|
1517
|
+
- Leverage filtering to reduce data transfer
|
|
1518
|
+
- Batch operations when possible
|
|
1519
|
+
- Cache frequently accessed metadata
|
|
1520
|
+
|
|
1521
|
+
7. **Error Handling**
|
|
1522
|
+
- Implement exponential backoff for rate limits
|
|
1523
|
+
- Handle 409 conflicts for concurrent updates
|
|
1524
|
+
- Log request IDs for troubleshooting
|
|
1525
|
+
- Validate data before inserting
|