industrial-model 0.6.0 → 0.7.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 +195 -1
- package/dist/cognite-core/index.cjs +366 -0
- package/dist/cognite-core/index.cjs.map +1 -1
- package/dist/cognite-core/index.d.cts +5 -2
- package/dist/cognite-core/index.d.ts +5 -2
- package/dist/cognite-core/index.js +366 -0
- package/dist/cognite-core/index.js.map +1 -1
- package/dist/index.cjs +358 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +358 -0
- package/dist/index.js.map +1 -1
- package/dist/{types-DCP5GMi3.d.cts → types-2jjbs2i7.d.cts} +52 -1
- package/dist/{types-DCP5GMi3.d.ts → types-2jjbs2i7.d.ts} +52 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ TypeScript SDK for querying [Cognite Flexible Data Models (FDM)](https://docs.co
|
|
|
12
12
|
- **Industrial filters** - combine scalar filters, list filters, full-text search, and nested relation filters.
|
|
13
13
|
- **Pagination support** - use cursors manually or fetch all root pages with `limit: -1`.
|
|
14
14
|
- **Aggregation support** - count, group, list distinct values, and aggregate numeric properties.
|
|
15
|
+
- **Mutation support** - upsert model-shaped node patches and delete nodes by identity.
|
|
15
16
|
- **Runtime validation option** - parse query results with Zod schemas derived from Cognite view metadata.
|
|
16
17
|
- **CJS and ESM builds** - works in Node.js and common bundler setups.
|
|
17
18
|
|
|
@@ -518,6 +519,114 @@ const { items } = await model.query<CogniteAsset>()({
|
|
|
518
519
|
|
|
519
520
|
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
|
|
|
522
|
+
## Upsert
|
|
523
|
+
|
|
524
|
+
Use `upsert()` to create or patch nodes with the same model shape you use for queries. Each item must include `space` and `externalId`; all other fields are optional and only the fields you pass are updated.
|
|
525
|
+
|
|
526
|
+
```ts
|
|
527
|
+
await model.upsert<CogniteAsset>()({
|
|
528
|
+
viewExternalId: "CogniteAsset",
|
|
529
|
+
items: [
|
|
530
|
+
{
|
|
531
|
+
space: "asset-space",
|
|
532
|
+
externalId: "pump-1",
|
|
533
|
+
name: "Pump 1",
|
|
534
|
+
parent: { space: "asset-space", externalId: "root" },
|
|
535
|
+
},
|
|
536
|
+
],
|
|
537
|
+
});
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Direct relations are written as `NodeId` values. Reverse direct relations are written by patching the target nodes through the relation field defined in Cognite. For example, writing `children` on an asset updates each child asset's `parent` reference.
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
await model.upsert<CogniteAsset>()({
|
|
544
|
+
viewExternalId: "CogniteAsset",
|
|
545
|
+
items: [
|
|
546
|
+
{
|
|
547
|
+
space: "asset-space",
|
|
548
|
+
externalId: "parent-asset",
|
|
549
|
+
children: [{ space: "asset-space", externalId: "child-1" }],
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
});
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Edge-backed relations need an edge ID. Provide `onEdgeCreation` for every edge connection property you write. The callback receives normalized `startNode`, `endNode`, and `edgeType` values after the SDK has applied the relation direction from the view metadata.
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
await model.upsert<Cognite3DObject>()({
|
|
559
|
+
viewExternalId: "Cognite3DObject",
|
|
560
|
+
items: [
|
|
561
|
+
{
|
|
562
|
+
space: "object-space",
|
|
563
|
+
externalId: "object-1",
|
|
564
|
+
images360: [{ space: "image-space", externalId: "image-1" }],
|
|
565
|
+
},
|
|
566
|
+
],
|
|
567
|
+
onEdgeCreation: {
|
|
568
|
+
images360: ({ startNode, endNode, edgeType }) => ({
|
|
569
|
+
space: startNode.space,
|
|
570
|
+
externalId: `${startNode.externalId}:${edgeType.externalId}:${endNode.externalId}`,
|
|
571
|
+
}),
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
`edgeMode` controls how edge connection properties are applied:
|
|
577
|
+
|
|
578
|
+
| Mode | Behavior |
|
|
579
|
+
| --- | --- |
|
|
580
|
+
| `"append"` | Default. Creates the generated edges and leaves existing edges untouched. |
|
|
581
|
+
| `"replace"` | Queries existing edges for the provided edge connection fields, deletes edges that were not generated by the current upsert, then applies the new edges. |
|
|
582
|
+
|
|
583
|
+
To clear an edge connection for a node, include the property with an empty array and use `edgeMode: "replace"`:
|
|
584
|
+
|
|
585
|
+
```ts
|
|
586
|
+
await model.upsert<Cognite3DObject>()({
|
|
587
|
+
viewExternalId: "Cognite3DObject",
|
|
588
|
+
edgeMode: "replace",
|
|
589
|
+
items: [
|
|
590
|
+
{
|
|
591
|
+
space: "object-space",
|
|
592
|
+
externalId: "object-1",
|
|
593
|
+
images360: [],
|
|
594
|
+
},
|
|
595
|
+
],
|
|
596
|
+
});
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
This deletes existing `images360` edges for `object-1`. Other edge connection fields on the same node are not touched, and omitting `images360` entirely leaves its existing edges unchanged.
|
|
600
|
+
|
|
601
|
+
Use `replace: true` when you want Cognite apply replace semantics for container-backed node properties.
|
|
602
|
+
|
|
603
|
+
Important constraints:
|
|
604
|
+
|
|
605
|
+
- Relation fields accept only `NodeId` or `NodeId[]` references. Nested node mutation is intentionally not supported.
|
|
606
|
+
- Unknown fields are rejected before Cognite is called.
|
|
607
|
+
- Edge connection fields require `onEdgeCreation.<property>` only when the submitted array contains edges to create.
|
|
608
|
+
- Cognite apply requests are limited to 1000 writes/deletes per call. You can still pass more than 1000 upsert items or edge references; the SDK follows paginated edge-replacement queries and splits large apply payloads into multiple Cognite calls.
|
|
609
|
+
- `edgeMode: "replace"` only replaces edge connection fields included in the submitted items.
|
|
610
|
+
|
|
611
|
+
## Delete
|
|
612
|
+
|
|
613
|
+
Use `delete()` when you only need to delete nodes by identity. The method accepts an array of values with `space` and `externalId`; any extra fields are ignored.
|
|
614
|
+
|
|
615
|
+
```ts
|
|
616
|
+
await model.delete([
|
|
617
|
+
{ space: "asset-space", externalId: "pump-1" },
|
|
618
|
+
{ space: "asset-space", externalId: "pump-2", name: "Pump 2" },
|
|
619
|
+
]);
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
The delete operation is view-independent, so it does not require `viewExternalId` and is also available directly on `CogniteCoreClient`.
|
|
623
|
+
|
|
624
|
+
```ts
|
|
625
|
+
await core.delete([{ space: "asset-space", externalId: "pump-1" }]);
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Deletes are sent through Cognite apply. When more than 1000 nodes are provided, the SDK splits them into multiple Cognite calls.
|
|
629
|
+
|
|
521
630
|
## Aggregation
|
|
522
631
|
|
|
523
632
|
Use `aggregate()` when you need grouped counts, distinct values, or numeric summaries without loading every instance.
|
|
@@ -654,7 +763,7 @@ For applications working with the Cognite Core Data Model (`cdf_cdm/CogniteCore/
|
|
|
654
763
|
|
|
655
764
|
```ts
|
|
656
765
|
import { CogniteClient } from "@cognite/sdk";
|
|
657
|
-
import { CogniteCoreClient } from "industrial-model";
|
|
766
|
+
import { CogniteCoreClient } from "industrial-model/cognite-core";
|
|
658
767
|
|
|
659
768
|
const client = new CogniteClient({ ... });
|
|
660
769
|
const core = new CogniteCoreClient(client);
|
|
@@ -696,6 +805,27 @@ items[0]?.group?.manufacturer;
|
|
|
696
805
|
items[0]?.aggregate?.value;
|
|
697
806
|
```
|
|
698
807
|
|
|
808
|
+
Upserts use the same pattern and infer the item shape from the view name:
|
|
809
|
+
|
|
810
|
+
```ts
|
|
811
|
+
await core.upsert("CogniteAsset")({
|
|
812
|
+
items: [
|
|
813
|
+
{
|
|
814
|
+
space: "asset-space",
|
|
815
|
+
externalId: "pump-1",
|
|
816
|
+
name: "Pump 1",
|
|
817
|
+
parent: { space: "asset-space", externalId: "root" },
|
|
818
|
+
},
|
|
819
|
+
],
|
|
820
|
+
});
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
Deletes are view-independent:
|
|
824
|
+
|
|
825
|
+
```ts
|
|
826
|
+
await core.delete([{ space: "asset-space", externalId: "pump-1" }]);
|
|
827
|
+
```
|
|
828
|
+
|
|
699
829
|
All Cognite Core view types are exported from `industrial-model` and can be imported directly for use with `IndustrialModelClient` if needed:
|
|
700
830
|
|
|
701
831
|
```ts
|
|
@@ -849,6 +979,14 @@ Same as `model.query<TModel>()(options)` on `IndustrialModelClient`, except the
|
|
|
849
979
|
|
|
850
980
|
Same as `model.aggregate<TModel>()(options)` on `IndustrialModelClient`, with the view name as the first positional argument.
|
|
851
981
|
|
|
982
|
+
### `core.upsert(viewExternalId)(options)`
|
|
983
|
+
|
|
984
|
+
Same as `model.upsert<TModel>()(options)` on `IndustrialModelClient`, with the view name as the first positional argument. The model type is inferred from the Cognite Core view name.
|
|
985
|
+
|
|
986
|
+
### `core.delete(items)`
|
|
987
|
+
|
|
988
|
+
Same as `model.delete(items)` on `IndustrialModelClient`. Deletes nodes by `space` and `externalId`; no view name is required.
|
|
989
|
+
|
|
852
990
|
### `new IndustrialModelClient(client, dataModelId, options?)`
|
|
853
991
|
|
|
854
992
|
| Parameter | Type | Description |
|
|
@@ -883,6 +1021,58 @@ type QueryResult<TItem> = {
|
|
|
883
1021
|
|
|
884
1022
|
Each item includes instance metadata such as `space`, `externalId`, `version`, `createdTime`, `deletedTime`, and `lastUpdatedTime`, plus the selected fields.
|
|
885
1023
|
|
|
1024
|
+
### `model.upsert<TModel>()(options)`
|
|
1025
|
+
|
|
1026
|
+
`upsert()` uses the same model type as `query()` and accepts partial node patches. It returns the Cognite apply result items.
|
|
1027
|
+
|
|
1028
|
+
| Option | Description |
|
|
1029
|
+
| --- | --- |
|
|
1030
|
+
| `viewExternalId` | View to create or patch. |
|
|
1031
|
+
| `items` | Node patches. Each item must include `space` and `externalId`. Inputs larger than Cognite's 1000-item apply limit are split into multiple calls. |
|
|
1032
|
+
| `replace` | Optional. Enables Cognite apply replace semantics for submitted container-backed properties. |
|
|
1033
|
+
| `edgeMode` | Optional. `"append"` by default; use `"replace"` to remove existing edge connection edges for submitted edge fields before applying the new references. |
|
|
1034
|
+
| `onEdgeCreation` | Optional map of edge connection property names to callbacks that generate edge IDs. Required for every edge connection property that creates one or more edges. |
|
|
1035
|
+
|
|
1036
|
+
Returns:
|
|
1037
|
+
|
|
1038
|
+
```ts
|
|
1039
|
+
type UpsertResult = {
|
|
1040
|
+
items: Array<{
|
|
1041
|
+
instanceType: "node" | "edge";
|
|
1042
|
+
space: string;
|
|
1043
|
+
externalId: string;
|
|
1044
|
+
version?: number;
|
|
1045
|
+
wasModified?: boolean;
|
|
1046
|
+
createdTime?: number;
|
|
1047
|
+
lastUpdatedTime?: number;
|
|
1048
|
+
}>;
|
|
1049
|
+
};
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
### `model.delete<TItem extends NodeId>(items)`
|
|
1053
|
+
|
|
1054
|
+
Deletes nodes by identity. Each item must include `space` and `externalId`; extra fields are ignored. Inputs larger than Cognite's 1000-item apply limit are split into multiple calls.
|
|
1055
|
+
|
|
1056
|
+
```ts
|
|
1057
|
+
await model.delete([{ space: "asset-space", externalId: "pump-1" }]);
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
Returns:
|
|
1061
|
+
|
|
1062
|
+
```ts
|
|
1063
|
+
type DeleteResult = {
|
|
1064
|
+
items: Array<{
|
|
1065
|
+
instanceType: "node";
|
|
1066
|
+
space: string;
|
|
1067
|
+
externalId: string;
|
|
1068
|
+
version?: number;
|
|
1069
|
+
wasModified?: boolean;
|
|
1070
|
+
createdTime?: number;
|
|
1071
|
+
lastUpdatedTime?: number;
|
|
1072
|
+
}>;
|
|
1073
|
+
};
|
|
1074
|
+
```
|
|
1075
|
+
|
|
886
1076
|
### `model.aggregate<TModel>()(options)`
|
|
887
1077
|
|
|
888
1078
|
| Option | Description |
|
|
@@ -938,6 +1128,10 @@ Logical combinators `AND`, `OR`, and `NOT` are supported at any nesting level, i
|
|
|
938
1128
|
| `QuerySelect` | Type helper for reusable query selections. |
|
|
939
1129
|
| `QueryResult`, `QueryResultItem` | Query output types. |
|
|
940
1130
|
| `AggregateResult`, `AggregateResultItem` | Aggregate output types. |
|
|
1131
|
+
| `UpsertOptions`, `UpsertNode`, `UpsertProperties` | Upsert input helper types. |
|
|
1132
|
+
| `UpsertResult`, `UpsertResultItem` | Upsert output types. |
|
|
1133
|
+
| `DeleteExecutor`, `DeleteResult`, `DeleteResultItem` | Delete helper and output types. |
|
|
1134
|
+
| `EdgeCreationContext`, `EdgeCreationCallback`, `EdgeCreationCallbacks`, `EdgeMode` | Edge upsert helper types. |
|
|
941
1135
|
| `IndustrialModelClientOptions` | Client configuration options. |
|
|
942
1136
|
|
|
943
1137
|
**Cognite Core**
|
|
@@ -43,6 +43,13 @@ var CogniteSdkAdapter = class {
|
|
|
43
43
|
items: response.items
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
+
async applyInstances(request) {
|
|
47
|
+
const apply = this.client.instances.apply;
|
|
48
|
+
const response = await apply(request);
|
|
49
|
+
return {
|
|
50
|
+
items: response.items
|
|
51
|
+
};
|
|
52
|
+
}
|
|
46
53
|
};
|
|
47
54
|
|
|
48
55
|
// src/constants.ts
|
|
@@ -796,6 +803,85 @@ ${result.error.issues.map((issue) => `- ${issue.path.map(String).join(".")}: ${i
|
|
|
796
803
|
return (isList ? zod.z.array(nestedSchema) : nestedSchema).optional();
|
|
797
804
|
}
|
|
798
805
|
};
|
|
806
|
+
var strictNodeIdSchema = zod.z.object({
|
|
807
|
+
space: zod.z.string().min(1),
|
|
808
|
+
externalId: zod.z.string().min(1)
|
|
809
|
+
}).strict();
|
|
810
|
+
var nodeIdLikeSchema = zod.z.object({
|
|
811
|
+
space: zod.z.string().min(1),
|
|
812
|
+
externalId: zod.z.string().min(1)
|
|
813
|
+
}).loose();
|
|
814
|
+
var optionsSchema = zod.z.object({
|
|
815
|
+
viewExternalId: zod.z.string().min(1),
|
|
816
|
+
items: zod.z.array(zod.z.record(zod.z.string(), zod.z.unknown())),
|
|
817
|
+
onEdgeCreation: zod.z.record(zod.z.string(), zod.z.function()).optional(),
|
|
818
|
+
replace: zod.z.boolean().optional(),
|
|
819
|
+
edgeMode: zod.z.enum(["append", "replace"]).optional()
|
|
820
|
+
}).strict();
|
|
821
|
+
function issuePath3(path) {
|
|
822
|
+
return path.length === 0 ? "upsert" : path.map(String).join(".");
|
|
823
|
+
}
|
|
824
|
+
function formatZodIssues3(error, path) {
|
|
825
|
+
return error.issues.map((issue) => `${issuePath3([...path, ...issue.path])}: ${issue.message}`);
|
|
826
|
+
}
|
|
827
|
+
function relationValueSchema(property) {
|
|
828
|
+
if (isReverseDirectRelation(property) && property.targetsList === true) {
|
|
829
|
+
return zod.z.never();
|
|
830
|
+
}
|
|
831
|
+
return zod.z.union([nodeIdLikeSchema, zod.z.array(nodeIdLikeSchema)]);
|
|
832
|
+
}
|
|
833
|
+
var UpsertValidator = class {
|
|
834
|
+
validate(options, rootView) {
|
|
835
|
+
const errors = [];
|
|
836
|
+
const optionsResult = optionsSchema.safeParse(options);
|
|
837
|
+
if (!optionsResult.success) {
|
|
838
|
+
errors.push(...formatZodIssues3(optionsResult.error, []));
|
|
839
|
+
}
|
|
840
|
+
if (options.viewExternalId !== rootView.externalId) {
|
|
841
|
+
errors.push(
|
|
842
|
+
`viewExternalId: expected "${rootView.externalId}", received "${options.viewExternalId}"`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
for (const [index, item] of options.items.entries()) {
|
|
846
|
+
errors.push(
|
|
847
|
+
...this.validateItem(item, rootView, ["items", index])
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
if (errors.length > 0) {
|
|
851
|
+
throw new Error(`Invalid upsert options:
|
|
852
|
+
${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
validateItem(item, view, path) {
|
|
856
|
+
const errors = [];
|
|
857
|
+
const identityResult = strictNodeIdSchema.safeParse({
|
|
858
|
+
space: item.space,
|
|
859
|
+
externalId: item.externalId
|
|
860
|
+
});
|
|
861
|
+
if (!identityResult.success) {
|
|
862
|
+
errors.push(...formatZodIssues3(identityResult.error, path));
|
|
863
|
+
}
|
|
864
|
+
for (const [name, value] of Object.entries(item)) {
|
|
865
|
+
if (name === "space" || name === "externalId") continue;
|
|
866
|
+
const property = view.properties[name];
|
|
867
|
+
if (!property) {
|
|
868
|
+
errors.push(`${issuePath3([...path, name])}: unknown view property`);
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (isViewPropertyDefinition(property)) {
|
|
872
|
+
const schema = property.type.type === "direct" ? property.type.list === true ? zod.z.array(nodeIdLikeSchema) : nodeIdLikeSchema : propertyValueSchema(property);
|
|
873
|
+
const result = schema.safeParse(value);
|
|
874
|
+
if (!result.success) errors.push(...formatZodIssues3(result.error, [...path, name]));
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
|
|
878
|
+
const result = relationValueSchema(property).safeParse(value);
|
|
879
|
+
if (!result.success) errors.push(...formatZodIssues3(result.error, [...path, name]));
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return errors;
|
|
883
|
+
}
|
|
884
|
+
};
|
|
799
885
|
|
|
800
886
|
// src/mappers/filter-mapper.ts
|
|
801
887
|
var LEAF_OPS = /* @__PURE__ */ new Set([
|
|
@@ -1363,6 +1449,216 @@ var QueryResultMapper = class {
|
|
|
1363
1449
|
}
|
|
1364
1450
|
};
|
|
1365
1451
|
|
|
1452
|
+
// src/mappers/upsert-mapper.ts
|
|
1453
|
+
var IDENTITY_KEYS = /* @__PURE__ */ new Set(["space", "externalId"]);
|
|
1454
|
+
var EDGE_QUERY_LIMIT = 1e3;
|
|
1455
|
+
var UpsertMapper = class {
|
|
1456
|
+
constructor(viewMapper, cognite) {
|
|
1457
|
+
this.viewMapper = viewMapper;
|
|
1458
|
+
this.cognite = cognite;
|
|
1459
|
+
this.validator = new UpsertValidator();
|
|
1460
|
+
}
|
|
1461
|
+
async map(options) {
|
|
1462
|
+
const rootView = await this.viewMapper.getView(options.viewExternalId);
|
|
1463
|
+
this.validator.validate(options, rootView);
|
|
1464
|
+
const edgeMode = options.edgeMode ?? "append";
|
|
1465
|
+
const mappedItems = options.items.map(
|
|
1466
|
+
(item) => this.mapItem(item, rootView, options.onEdgeCreation, edgeMode)
|
|
1467
|
+
);
|
|
1468
|
+
const items = mappedItems.flatMap((item) => item.writes);
|
|
1469
|
+
const edgeReplacements = mappedItems.flatMap((item) => item.edgeReplacements);
|
|
1470
|
+
const deleteItems = edgeMode === "replace" ? await this.mapEdgeReplacementDeletes(edgeReplacements) : [];
|
|
1471
|
+
return {
|
|
1472
|
+
items,
|
|
1473
|
+
...deleteItems.length > 0 ? { delete: deleteItems } : {},
|
|
1474
|
+
...options.replace === true ? { replace: true } : {}
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
mapItem(item, rootView, onEdgeCreation, edgeMode) {
|
|
1478
|
+
const node = { space: item.space, externalId: item.externalId };
|
|
1479
|
+
const nodeProperties = {};
|
|
1480
|
+
const inferredItems = [];
|
|
1481
|
+
const edgeReplacements = [];
|
|
1482
|
+
for (const [name, value] of Object.entries(item)) {
|
|
1483
|
+
if (IDENTITY_KEYS.has(name)) continue;
|
|
1484
|
+
const property = rootView.properties[name];
|
|
1485
|
+
if (!property) continue;
|
|
1486
|
+
if (isViewPropertyDefinition(property)) {
|
|
1487
|
+
nodeProperties[name] = normalizeViewPropertyValue(value, property);
|
|
1488
|
+
} else if (isReverseDirectRelation(property)) {
|
|
1489
|
+
inferredItems.push(...this.mapReverseDirectRelation(node, value, property));
|
|
1490
|
+
} else if (isEdgeConnection(property)) {
|
|
1491
|
+
const desiredEdges = this.mapEdgeConnection(node, name, value, property, onEdgeCreation);
|
|
1492
|
+
inferredItems.push(...desiredEdges);
|
|
1493
|
+
if (edgeMode === "replace") {
|
|
1494
|
+
edgeReplacements.push({ rootNode: node, propertyName: name, property, desiredEdges });
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
const applyNode = {
|
|
1499
|
+
instanceType: "node",
|
|
1500
|
+
...node
|
|
1501
|
+
};
|
|
1502
|
+
if (Object.keys(nodeProperties).length > 0) {
|
|
1503
|
+
applyNode.sources = [{ source: toViewReference(rootView), properties: nodeProperties }];
|
|
1504
|
+
}
|
|
1505
|
+
return { writes: [applyNode, ...inferredItems], edgeReplacements };
|
|
1506
|
+
}
|
|
1507
|
+
async mapEdgeReplacementDeletes(replacements) {
|
|
1508
|
+
const deletes = await Promise.all(
|
|
1509
|
+
replacements.map(async (replacement) => {
|
|
1510
|
+
const existingEdges = await this.queryExistingEdges(replacement);
|
|
1511
|
+
const desiredEdgeKeys = new Set(replacement.desiredEdges.map((edge) => instanceKey(edge)));
|
|
1512
|
+
return existingEdges.filter((edge) => !desiredEdgeKeys.has(instanceKey(edge))).map((edge) => ({
|
|
1513
|
+
instanceType: "edge",
|
|
1514
|
+
space: edge.space,
|
|
1515
|
+
externalId: edge.externalId
|
|
1516
|
+
}));
|
|
1517
|
+
})
|
|
1518
|
+
);
|
|
1519
|
+
return uniqueDeletes(deletes.flat());
|
|
1520
|
+
}
|
|
1521
|
+
async queryExistingEdges(replacement) {
|
|
1522
|
+
const rootKey = `${replacement.propertyName}Root`;
|
|
1523
|
+
const edgeKey = `${replacement.propertyName}Edges`;
|
|
1524
|
+
const direction = replacement.property.direction ?? "outwards";
|
|
1525
|
+
const query = {
|
|
1526
|
+
with: {
|
|
1527
|
+
[rootKey]: {
|
|
1528
|
+
nodes: {
|
|
1529
|
+
filter: {
|
|
1530
|
+
instanceReferences: [replacement.rootNode]
|
|
1531
|
+
}
|
|
1532
|
+
},
|
|
1533
|
+
limit: 1
|
|
1534
|
+
},
|
|
1535
|
+
[edgeKey]: {
|
|
1536
|
+
edges: {
|
|
1537
|
+
from: rootKey,
|
|
1538
|
+
maxDistance: 1,
|
|
1539
|
+
direction,
|
|
1540
|
+
filter: {
|
|
1541
|
+
equals: { property: ["edge", "type"], value: replacement.property.type }
|
|
1542
|
+
}
|
|
1543
|
+
},
|
|
1544
|
+
limit: EDGE_QUERY_LIMIT
|
|
1545
|
+
}
|
|
1546
|
+
},
|
|
1547
|
+
select: {
|
|
1548
|
+
[rootKey]: {},
|
|
1549
|
+
[edgeKey]: {}
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
const edges = [];
|
|
1553
|
+
let cursor;
|
|
1554
|
+
do {
|
|
1555
|
+
const response = await this.cognite.queryInstances({
|
|
1556
|
+
...query,
|
|
1557
|
+
...cursor ? { cursors: { [edgeKey]: cursor } } : {}
|
|
1558
|
+
});
|
|
1559
|
+
edges.push(
|
|
1560
|
+
...(response.items[edgeKey] ?? []).filter(
|
|
1561
|
+
(item) => item.instanceType === "edge"
|
|
1562
|
+
)
|
|
1563
|
+
);
|
|
1564
|
+
cursor = response.nextCursor[edgeKey];
|
|
1565
|
+
} while (cursor);
|
|
1566
|
+
return edges;
|
|
1567
|
+
}
|
|
1568
|
+
mapReverseDirectRelation(node, value, property) {
|
|
1569
|
+
const targets = asNodeIdArray(value);
|
|
1570
|
+
return targets.map((target) => ({
|
|
1571
|
+
instanceType: "node",
|
|
1572
|
+
...target,
|
|
1573
|
+
sources: [
|
|
1574
|
+
{
|
|
1575
|
+
source: property.through.source,
|
|
1576
|
+
properties: {
|
|
1577
|
+
[property.through.identifier]: normalizeReverseDirectRelationValue(node, property)
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
]
|
|
1581
|
+
}));
|
|
1582
|
+
}
|
|
1583
|
+
mapEdgeConnection(node, propertyName, value, property, onEdgeCreation) {
|
|
1584
|
+
const direction = property.direction ?? "outwards";
|
|
1585
|
+
return asNodeIdArray(value).map((target) => {
|
|
1586
|
+
const startNode = direction === "inwards" ? target : node;
|
|
1587
|
+
const endNode = direction === "inwards" ? node : target;
|
|
1588
|
+
const edgeType = toNodeId(property.type, `edge type for "${propertyName}"`);
|
|
1589
|
+
const createEdgeId = onEdgeCreation?.[propertyName];
|
|
1590
|
+
if (!createEdgeId) {
|
|
1591
|
+
throw new Error(
|
|
1592
|
+
`Invalid upsert options:
|
|
1593
|
+
- onEdgeCreation.${propertyName}: required when ingesting edge connection "${propertyName}"`
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
const edgeId = createEdgeId({
|
|
1597
|
+
startNode,
|
|
1598
|
+
endNode,
|
|
1599
|
+
edgeType
|
|
1600
|
+
});
|
|
1601
|
+
assertNodeId(edgeId, `onEdgeCreation(${propertyName})`);
|
|
1602
|
+
return {
|
|
1603
|
+
instanceType: "edge",
|
|
1604
|
+
...edgeId,
|
|
1605
|
+
type: property.type,
|
|
1606
|
+
startNode,
|
|
1607
|
+
endNode
|
|
1608
|
+
};
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
function normalizeReverseDirectRelationValue(node, property) {
|
|
1613
|
+
return property.targetsList === true ? [node] : node;
|
|
1614
|
+
}
|
|
1615
|
+
function normalizeViewPropertyValue(value, property) {
|
|
1616
|
+
if (property.type.type !== "direct") return normalizePropertyValue(value);
|
|
1617
|
+
if (Array.isArray(value)) return value.map((item) => toNodeId(item));
|
|
1618
|
+
return toNodeId(value);
|
|
1619
|
+
}
|
|
1620
|
+
function normalizePropertyValue(value) {
|
|
1621
|
+
if (value instanceof Date) return value.toISOString();
|
|
1622
|
+
if (Array.isArray(value)) return value.map(normalizePropertyValue);
|
|
1623
|
+
if (isPlainObject(value)) {
|
|
1624
|
+
return Object.fromEntries(
|
|
1625
|
+
Object.entries(value).map(([key, nestedValue]) => [key, normalizePropertyValue(nestedValue)])
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
return value;
|
|
1629
|
+
}
|
|
1630
|
+
function asNodeIdArray(value) {
|
|
1631
|
+
return Array.isArray(value) ? value.map((item) => toNodeId(item)) : [toNodeId(value)];
|
|
1632
|
+
}
|
|
1633
|
+
function toNodeId(value, label = "relation reference") {
|
|
1634
|
+
if (!isPlainObject(value) || typeof value.space !== "string" || typeof value.externalId !== "string") {
|
|
1635
|
+
throw new Error(`Invalid upsert options:
|
|
1636
|
+
- ${label}: expected a NodeId`);
|
|
1637
|
+
}
|
|
1638
|
+
return { space: value.space, externalId: value.externalId };
|
|
1639
|
+
}
|
|
1640
|
+
function instanceKey(instance) {
|
|
1641
|
+
return `${instance.space}\0${instance.externalId}`;
|
|
1642
|
+
}
|
|
1643
|
+
function uniqueDeletes(deletes) {
|
|
1644
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1645
|
+
return deletes.filter((item) => {
|
|
1646
|
+
const key = instanceKey(item);
|
|
1647
|
+
if (seen.has(key)) return false;
|
|
1648
|
+
seen.add(key);
|
|
1649
|
+
return true;
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
function assertNodeId(value, label) {
|
|
1653
|
+
if (!isPlainObject(value) || typeof value.space !== "string" || typeof value.externalId !== "string") {
|
|
1654
|
+
throw new Error(`Invalid upsert options:
|
|
1655
|
+
- ${label}: expected a NodeId`);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
function isPlainObject(value) {
|
|
1659
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1366
1662
|
// src/mappers/view-mapper.ts
|
|
1367
1663
|
var ViewMapper = class {
|
|
1368
1664
|
constructor(cognite, dataModelId) {
|
|
@@ -1410,6 +1706,7 @@ var ViewMapper = class {
|
|
|
1410
1706
|
};
|
|
1411
1707
|
|
|
1412
1708
|
// src/client.ts
|
|
1709
|
+
var APPLY_ITEM_LIMIT = 1e3;
|
|
1413
1710
|
var IndustrialModelClient = class {
|
|
1414
1711
|
constructor(client, dataModelId, options = {}) {
|
|
1415
1712
|
const cognite = createCogniteAdapter(client);
|
|
@@ -1417,6 +1714,7 @@ var IndustrialModelClient = class {
|
|
|
1417
1714
|
const viewMapper = new ViewMapper(cognite, dataModelId);
|
|
1418
1715
|
this.queryMapper = new QueryMapper(viewMapper, cognite);
|
|
1419
1716
|
this.aggregateMapper = new AggregateMapper(viewMapper, cognite);
|
|
1717
|
+
this.upsertMapper = new UpsertMapper(viewMapper, cognite);
|
|
1420
1718
|
this.aggregateResultMapper = new AggregateResultMapper();
|
|
1421
1719
|
this.resultMapper = new QueryResultMapper(viewMapper);
|
|
1422
1720
|
this.resultValidator = new QueryResultValidator(viewMapper);
|
|
@@ -1430,6 +1728,54 @@ var IndustrialModelClient = class {
|
|
|
1430
1728
|
const execute = (options) => this.aggregateInternal(options);
|
|
1431
1729
|
return execute;
|
|
1432
1730
|
}
|
|
1731
|
+
upsert() {
|
|
1732
|
+
const execute = (options) => this.upsertInternal(options);
|
|
1733
|
+
return execute;
|
|
1734
|
+
}
|
|
1735
|
+
async delete(items) {
|
|
1736
|
+
const deleteItems = items.map((item) => {
|
|
1737
|
+
assertNodeId2(item);
|
|
1738
|
+
return {
|
|
1739
|
+
instanceType: "node",
|
|
1740
|
+
space: item.space,
|
|
1741
|
+
externalId: item.externalId
|
|
1742
|
+
};
|
|
1743
|
+
});
|
|
1744
|
+
const response = await this.applyInstancesInChunks({
|
|
1745
|
+
items: [],
|
|
1746
|
+
delete: deleteItems
|
|
1747
|
+
});
|
|
1748
|
+
return { items: response.items };
|
|
1749
|
+
}
|
|
1750
|
+
async upsertInternal(options) {
|
|
1751
|
+
const cogniteRequest = await this.upsertMapper.map(options);
|
|
1752
|
+
const response = await this.applyInstancesInChunks(cogniteRequest);
|
|
1753
|
+
return { items: response.items };
|
|
1754
|
+
}
|
|
1755
|
+
async applyInstancesInChunks(request) {
|
|
1756
|
+
const deleteItems = request.delete ?? [];
|
|
1757
|
+
const totalItems = request.items.length + deleteItems.length;
|
|
1758
|
+
if (totalItems === 0) return { items: [] };
|
|
1759
|
+
if (totalItems <= APPLY_ITEM_LIMIT) {
|
|
1760
|
+
return this.cognite.applyInstances(request);
|
|
1761
|
+
}
|
|
1762
|
+
const responses = [];
|
|
1763
|
+
for (const deleteChunk of chunks(deleteItems, APPLY_ITEM_LIMIT)) {
|
|
1764
|
+
const response = await this.cognite.applyInstances({
|
|
1765
|
+
items: [],
|
|
1766
|
+
delete: deleteChunk
|
|
1767
|
+
});
|
|
1768
|
+
responses.push(...response.items);
|
|
1769
|
+
}
|
|
1770
|
+
for (const itemChunk of chunks(request.items, APPLY_ITEM_LIMIT)) {
|
|
1771
|
+
const response = await this.cognite.applyInstances({
|
|
1772
|
+
items: itemChunk,
|
|
1773
|
+
...request.replace === true ? { replace: true } : {}
|
|
1774
|
+
});
|
|
1775
|
+
responses.push(...response.items);
|
|
1776
|
+
}
|
|
1777
|
+
return { items: responses };
|
|
1778
|
+
}
|
|
1433
1779
|
async aggregateInternal(options) {
|
|
1434
1780
|
const cogniteRequest = await this.aggregateMapper.map(options);
|
|
1435
1781
|
const response = await this.cognite.aggregateInstances(cogniteRequest);
|
|
@@ -1486,6 +1832,18 @@ var IndustrialModelClient = class {
|
|
|
1486
1832
|
return appendNodesAndEdges(result, nestedResults);
|
|
1487
1833
|
}
|
|
1488
1834
|
};
|
|
1835
|
+
function chunks(items, size) {
|
|
1836
|
+
const result = [];
|
|
1837
|
+
for (let index = 0; index < items.length; index += size) {
|
|
1838
|
+
result.push(items.slice(index, index + size));
|
|
1839
|
+
}
|
|
1840
|
+
return result;
|
|
1841
|
+
}
|
|
1842
|
+
function assertNodeId2(value) {
|
|
1843
|
+
if (value == null || typeof value !== "object" || Array.isArray(value) || typeof value.space !== "string" || value.space?.length === 0 || typeof value.externalId !== "string" || value.externalId?.length === 0) {
|
|
1844
|
+
throw new Error("Invalid delete options:\n- items: expected NodeId values");
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1489
1847
|
|
|
1490
1848
|
// src/cognite-core/client.ts
|
|
1491
1849
|
var COGNITE_CORE_DATA_MODEL = {
|
|
@@ -1508,6 +1866,14 @@ var CogniteCoreClient = class {
|
|
|
1508
1866
|
const execute = (options = {}) => aggregate({ ...options, viewExternalId });
|
|
1509
1867
|
return execute;
|
|
1510
1868
|
}
|
|
1869
|
+
upsert(viewExternalId) {
|
|
1870
|
+
const upsert = this.model.upsert();
|
|
1871
|
+
const execute = (options) => upsert({ ...options, viewExternalId });
|
|
1872
|
+
return execute;
|
|
1873
|
+
}
|
|
1874
|
+
delete(items) {
|
|
1875
|
+
return this.model.delete(items);
|
|
1876
|
+
}
|
|
1511
1877
|
};
|
|
1512
1878
|
|
|
1513
1879
|
exports.COGNITE_CORE_DATA_MODEL = COGNITE_CORE_DATA_MODEL;
|