industrial-model 0.5.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/dist/index.js CHANGED
@@ -41,6 +41,13 @@ var CogniteSdkAdapter = class {
41
41
  items: response.items
42
42
  };
43
43
  }
44
+ async applyInstances(request) {
45
+ const apply = this.client.instances.apply;
46
+ const response = await apply(request);
47
+ return {
48
+ items: response.items
49
+ };
50
+ }
44
51
  };
45
52
 
46
53
  // src/constants.ts
@@ -52,7 +59,108 @@ var MAX_DEPENDENCY_DEPTH = 3;
52
59
  var AGGREGATE_LIMIT = 1e3;
53
60
  var MAX_GROUP_BY = 5;
54
61
 
55
- // src/mappers/utils.ts
62
+ // src/utils/query.ts
63
+ function mapNodesAndEdges(queryResult, _query) {
64
+ return queryResult.items;
65
+ }
66
+ function appendNodesAndEdges(initial, additional) {
67
+ if (!additional) return initial;
68
+ for (const [key, items] of Object.entries(additional)) {
69
+ const existing = initial[key];
70
+ if (existing) {
71
+ existing.push(...items);
72
+ } else {
73
+ initial[key] = [...items];
74
+ }
75
+ }
76
+ return initial;
77
+ }
78
+ function getQueryForDependenciesPagination(query, queryResult, viewExternalId) {
79
+ const cursorKeys = new Set(Object.keys(queryResult.nextCursor));
80
+ const { nodesParent, nodesChildren } = getParentAndChildrenNodes(cursorKeys);
81
+ const leafCursors = getLeafCursors(queryResult, viewExternalId, nodesParent, nodesChildren);
82
+ if (Object.keys(leafCursors).length === 0) {
83
+ return null;
84
+ }
85
+ return buildDependenciesQuery(query, nodesParent, nodesChildren, leafCursors);
86
+ }
87
+ function getParentAndChildrenNodes(keys) {
88
+ const nodesParent = /* @__PURE__ */ new Map();
89
+ const nodesChildren = /* @__PURE__ */ new Map();
90
+ for (const key of keys) {
91
+ const keyParts = key.split(NESTED_SEP);
92
+ const validParents = /* @__PURE__ */ new Set();
93
+ for (let i = keyParts.length - 1; i > 0; i--) {
94
+ const parentPath = keyParts.slice(0, i).join(NESTED_SEP);
95
+ const parentWithEdgeMarker = `${parentPath}${NESTED_SEP}${EDGE_MARKER}`;
96
+ if (keys.has(parentWithEdgeMarker)) {
97
+ validParents.add(parentWithEdgeMarker);
98
+ const children = nodesChildren.get(parentWithEdgeMarker) ?? /* @__PURE__ */ new Set();
99
+ children.add(key);
100
+ nodesChildren.set(parentWithEdgeMarker, children);
101
+ }
102
+ if (keys.has(parentPath)) {
103
+ validParents.add(parentPath);
104
+ const children = nodesChildren.get(parentPath) ?? /* @__PURE__ */ new Set();
105
+ children.add(key);
106
+ nodesChildren.set(parentPath, children);
107
+ }
108
+ }
109
+ nodesParent.set(key, validParents);
110
+ }
111
+ return { nodesParent, nodesChildren };
112
+ }
113
+ function getLeafCursors(queryResult, viewExternalId, nodesParent, nodesChildren) {
114
+ const targetCursors = {};
115
+ const targetCursorKeys = /* @__PURE__ */ new Set();
116
+ for (const [cursorKey, cursorValue] of Object.entries(queryResult.nextCursor)) {
117
+ if (cursorKey === viewExternalId || !cursorValue || (queryResult.items[cursorKey]?.length ?? 0) !== MAX_LIMIT) {
118
+ continue;
119
+ }
120
+ const children = nodesChildren.get(cursorKey) ?? /* @__PURE__ */ new Set();
121
+ let skipDueToChild = false;
122
+ for (const c of children) {
123
+ if (targetCursorKeys.has(c)) {
124
+ skipDueToChild = true;
125
+ break;
126
+ }
127
+ }
128
+ if (skipDueToChild) continue;
129
+ const parent = nodesParent.get(cursorKey) ?? /* @__PURE__ */ new Set();
130
+ for (const key of parent) {
131
+ if (targetCursorKeys.has(key)) {
132
+ delete targetCursors[key];
133
+ targetCursorKeys.delete(key);
134
+ }
135
+ }
136
+ targetCursors[cursorKey] = cursorValue;
137
+ targetCursorKeys.add(cursorKey);
138
+ }
139
+ return targetCursors;
140
+ }
141
+ function buildDependenciesQuery(previousQuery, nodesParent, nodesChildren, leafCursors) {
142
+ const withExprs = {};
143
+ const selectExprs = {};
144
+ const finalCursors = {};
145
+ for (const [cursorKey, cursorValue] of Object.entries(leafCursors)) {
146
+ const children = nodesChildren.get(cursorKey) ?? /* @__PURE__ */ new Set();
147
+ const parent = nodesParent.get(cursorKey) ?? /* @__PURE__ */ new Set();
148
+ const validKeys = /* @__PURE__ */ new Set([...parent, ...children, cursorKey]);
149
+ for (const [k, v] of Object.entries(previousQuery.with)) {
150
+ if (validKeys.has(k)) withExprs[k] = v;
151
+ }
152
+ for (const [k, v] of Object.entries(previousQuery.select)) {
153
+ if (validKeys.has(k)) selectExprs[k] = v;
154
+ }
155
+ for (const [k, v] of Object.entries(previousQuery.cursors ?? {})) {
156
+ if (parent.has(k) && v) finalCursors[k] = v;
157
+ }
158
+ finalCursors[cursorKey] = cursorValue;
159
+ }
160
+ return { with: withExprs, select: selectExprs, cursors: finalCursors };
161
+ }
162
+
163
+ // src/utils/view.ts
56
164
  var NODE_PROPERTIES = /* @__PURE__ */ new Set([
57
165
  "externalId",
58
166
  "space",
@@ -116,8 +224,6 @@ function isNumericProperty(property) {
116
224
  function getSelectedGroupByKeys(groupBy) {
117
225
  return Object.entries(groupBy).filter((entry) => entry[1] === true).map(([key]) => key);
118
226
  }
119
-
120
- // src/validation.ts
121
227
  var nodeIdSchema = z.object({
122
228
  space: z.string().min(1),
123
229
  externalId: z.string().min(1)
@@ -166,17 +272,8 @@ function propertyValueSchema(property, options = {}) {
166
272
  }
167
273
  return type.list === true ? z.array(schema) : schema;
168
274
  }
169
- function buildViewSchema(view, options = {}) {
170
- const shape = {};
171
- for (const [name, property] of Object.entries(view.properties)) {
172
- if (isViewPropertyDefinition(property)) {
173
- shape[name] = propertyValueSchema(property, options).optional();
174
- }
175
- }
176
- return z.object(shape).strict();
177
- }
178
275
 
179
- // src/mappers/query-validator.ts
276
+ // src/validators/query-validator.ts
180
277
  var NODE_STRING_PROPERTIES = ["externalId", "space"];
181
278
  var NODE_NUMBER_PROPERTIES = ["createdTime", "deletedTime", "lastUpdatedTime"];
182
279
  var NODE_PROPERTIES2 = /* @__PURE__ */ new Set([...NODE_STRING_PROPERTIES, ...NODE_NUMBER_PROPERTIES]);
@@ -387,6 +484,12 @@ ${errors.map((error) => `- ${error}`).join("\n")}`);
387
484
  );
388
485
  continue;
389
486
  }
487
+ if (isReverseDirectRelation(property) && property.targetsList) {
488
+ errors.push(
489
+ `${issuePath([...path, name])}: cannot select "${name}" \u2014 Cognite does not support inward traversal of list direct relations. Query "${property.source.externalId}" directly and filter by the "${property.through.identifier}" field instead.`
490
+ );
491
+ continue;
492
+ }
390
493
  const targetView = await this.viewMapper.getView(target);
391
494
  errors.push(...await this.validateSelect(value, targetView, [...path, name]));
392
495
  }
@@ -476,7 +579,7 @@ ${errors.map((error) => `- ${error}`).join("\n")}`);
476
579
  }
477
580
  };
478
581
 
479
- // src/mappers/aggregate-validator.ts
582
+ // src/validators/aggregate-validator.ts
480
583
  var NODE_COUNT_PROPERTIES = /* @__PURE__ */ new Set(["externalId", "space"]);
481
584
  function issuePath2(path) {
482
585
  return path.length === 0 ? "aggregate" : path.map(String).join(".");
@@ -612,6 +715,171 @@ ${errors.map((error) => `- ${error}`).join("\n")}`
612
715
  return [];
613
716
  }
614
717
  };
718
+ var nodeMetadataSchema = {
719
+ instanceType: z.literal("node").optional(),
720
+ space: z.string(),
721
+ externalId: z.string(),
722
+ version: z.number().optional(),
723
+ createdTime: z.number().optional(),
724
+ deletedTime: z.number().optional(),
725
+ lastUpdatedTime: z.number().optional(),
726
+ _edges: z.record(z.string(), z.unknown()).optional()
727
+ };
728
+ function isListRelation(property) {
729
+ if (isViewPropertyDefinition(property)) {
730
+ return isListDirectRelation(property);
731
+ }
732
+ if (isReverseDirectRelation(property)) {
733
+ return property.connectionType === "multi_reverse_direct_relation" || property.targetsList === true;
734
+ }
735
+ return isEdgeConnection(property);
736
+ }
737
+ function isRecord2(value) {
738
+ return value != null && typeof value === "object" && !Array.isArray(value);
739
+ }
740
+ var QueryResultValidator = class {
741
+ constructor(viewMapper) {
742
+ this.viewMapper = viewMapper;
743
+ }
744
+ async parseItems(rootViewExternalId, items, select) {
745
+ const rootView = await this.viewMapper.getView(rootViewExternalId);
746
+ const schema = await this.buildResultSchema(rootView, MAX_DEPENDENCY_DEPTH, select);
747
+ const result = z.array(schema).safeParse(items);
748
+ if (!result.success) {
749
+ throw new Error(
750
+ `Invalid query result:
751
+ ${result.error.issues.map((issue) => `- ${issue.path.map(String).join(".")}: ${issue.message}`).join("\n")}`
752
+ );
753
+ }
754
+ return result.data;
755
+ }
756
+ async buildResultSchema(view, remainingDepth, select) {
757
+ const shape = { ...nodeMetadataSchema };
758
+ const includeAllProperties = select == null || select._all === true;
759
+ for (const [name, property] of Object.entries(view.properties)) {
760
+ const isSelected = includeAllProperties || name in select;
761
+ if (!isSelected) continue;
762
+ const nestedSelect = isRecord2(select?.[name]) ? select[name] : void 0;
763
+ if (isViewPropertyDefinition(property)) {
764
+ const relationSource = getDirectRelationSource(property);
765
+ if (relationSource) {
766
+ shape[name] = await this.buildRelationSchema(
767
+ property,
768
+ relationSource.externalId,
769
+ remainingDepth,
770
+ nestedSelect
771
+ );
772
+ } else {
773
+ shape[name] = propertyValueSchema(property, { dateMode: "coerce" }).optional();
774
+ }
775
+ continue;
776
+ }
777
+ if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
778
+ shape[name] = await this.buildRelationSchema(
779
+ property,
780
+ property.source.externalId,
781
+ remainingDepth,
782
+ nestedSelect
783
+ );
784
+ }
785
+ }
786
+ const schema = z.object(shape);
787
+ return includeAllProperties ? schema.strict() : schema;
788
+ }
789
+ async buildRelationSchema(property, targetViewExternalId, remainingDepth, select) {
790
+ const isList = isListRelation(property);
791
+ const fallbackSchema = isViewPropertyDefinition(property) ? propertyValueSchema(property, { dateMode: "coerce" }) : z.unknown();
792
+ if (remainingDepth <= 0 || select == null) {
793
+ return fallbackSchema.optional();
794
+ }
795
+ const targetView = await this.viewMapper.getView(targetViewExternalId);
796
+ const nestedSchema = await this.buildResultSchema(targetView, remainingDepth - 1, select);
797
+ if (isViewPropertyDefinition(property)) {
798
+ const nestedRelationSchema = isList ? z.array(nestedSchema) : nestedSchema;
799
+ return z.union([nestedRelationSchema, fallbackSchema]).optional();
800
+ }
801
+ return (isList ? z.array(nestedSchema) : nestedSchema).optional();
802
+ }
803
+ };
804
+ var strictNodeIdSchema = z.object({
805
+ space: z.string().min(1),
806
+ externalId: z.string().min(1)
807
+ }).strict();
808
+ var nodeIdLikeSchema = z.object({
809
+ space: z.string().min(1),
810
+ externalId: z.string().min(1)
811
+ }).loose();
812
+ var optionsSchema = z.object({
813
+ viewExternalId: z.string().min(1),
814
+ items: z.array(z.record(z.string(), z.unknown())),
815
+ onEdgeCreation: z.record(z.string(), z.function()).optional(),
816
+ replace: z.boolean().optional(),
817
+ edgeMode: z.enum(["append", "replace"]).optional()
818
+ }).strict();
819
+ function issuePath3(path) {
820
+ return path.length === 0 ? "upsert" : path.map(String).join(".");
821
+ }
822
+ function formatZodIssues3(error, path) {
823
+ return error.issues.map((issue) => `${issuePath3([...path, ...issue.path])}: ${issue.message}`);
824
+ }
825
+ function relationValueSchema(property) {
826
+ if (isReverseDirectRelation(property) && property.targetsList === true) {
827
+ return z.never();
828
+ }
829
+ return z.union([nodeIdLikeSchema, z.array(nodeIdLikeSchema)]);
830
+ }
831
+ var UpsertValidator = class {
832
+ validate(options, rootView) {
833
+ const errors = [];
834
+ const optionsResult = optionsSchema.safeParse(options);
835
+ if (!optionsResult.success) {
836
+ errors.push(...formatZodIssues3(optionsResult.error, []));
837
+ }
838
+ if (options.viewExternalId !== rootView.externalId) {
839
+ errors.push(
840
+ `viewExternalId: expected "${rootView.externalId}", received "${options.viewExternalId}"`
841
+ );
842
+ }
843
+ for (const [index, item] of options.items.entries()) {
844
+ errors.push(
845
+ ...this.validateItem(item, rootView, ["items", index])
846
+ );
847
+ }
848
+ if (errors.length > 0) {
849
+ throw new Error(`Invalid upsert options:
850
+ ${errors.map((error) => `- ${error}`).join("\n")}`);
851
+ }
852
+ }
853
+ validateItem(item, view, path) {
854
+ const errors = [];
855
+ const identityResult = strictNodeIdSchema.safeParse({
856
+ space: item.space,
857
+ externalId: item.externalId
858
+ });
859
+ if (!identityResult.success) {
860
+ errors.push(...formatZodIssues3(identityResult.error, path));
861
+ }
862
+ for (const [name, value] of Object.entries(item)) {
863
+ if (name === "space" || name === "externalId") continue;
864
+ const property = view.properties[name];
865
+ if (!property) {
866
+ errors.push(`${issuePath3([...path, name])}: unknown view property`);
867
+ continue;
868
+ }
869
+ if (isViewPropertyDefinition(property)) {
870
+ const schema = property.type.type === "direct" ? property.type.list === true ? z.array(nodeIdLikeSchema) : nodeIdLikeSchema : propertyValueSchema(property);
871
+ const result = schema.safeParse(value);
872
+ if (!result.success) errors.push(...formatZodIssues3(result.error, [...path, name]));
873
+ continue;
874
+ }
875
+ if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
876
+ const result = relationValueSchema(property).safeParse(value);
877
+ if (!result.success) errors.push(...formatZodIssues3(result.error, [...path, name]));
878
+ }
879
+ }
880
+ return errors;
881
+ }
882
+ };
615
883
 
616
884
  // src/mappers/filter-mapper.ts
617
885
  var LEAF_OPS = /* @__PURE__ */ new Set([
@@ -1178,92 +1446,216 @@ var QueryResultMapper = class {
1178
1446
  return entry;
1179
1447
  }
1180
1448
  };
1181
- var nodeMetadataSchema = {
1182
- instanceType: z.literal("node").optional(),
1183
- space: z.string(),
1184
- externalId: z.string(),
1185
- version: z.number().optional(),
1186
- createdTime: z.number().optional(),
1187
- deletedTime: z.number().optional(),
1188
- lastUpdatedTime: z.number().optional(),
1189
- _edges: z.record(z.string(), z.unknown()).optional()
1190
- };
1191
- function isListRelation(property) {
1192
- if (isViewPropertyDefinition(property)) {
1193
- return isListDirectRelation(property);
1194
- }
1195
- if (isReverseDirectRelation(property)) {
1196
- return property.connectionType === "multi_reverse_direct_relation" || property.targetsList === true;
1197
- }
1198
- return isEdgeConnection(property);
1199
- }
1200
- function isRecord2(value) {
1201
- return value != null && typeof value === "object" && !Array.isArray(value);
1202
- }
1203
- var QueryResultValidator = class {
1204
- constructor(viewMapper) {
1449
+
1450
+ // src/mappers/upsert-mapper.ts
1451
+ var IDENTITY_KEYS = /* @__PURE__ */ new Set(["space", "externalId"]);
1452
+ var EDGE_QUERY_LIMIT = 1e3;
1453
+ var UpsertMapper = class {
1454
+ constructor(viewMapper, cognite) {
1205
1455
  this.viewMapper = viewMapper;
1456
+ this.cognite = cognite;
1457
+ this.validator = new UpsertValidator();
1206
1458
  }
1207
- async parseItems(rootViewExternalId, items, select) {
1208
- const rootView = await this.viewMapper.getView(rootViewExternalId);
1209
- const schema = await this.buildResultSchema(rootView, MAX_DEPENDENCY_DEPTH, select);
1210
- const result = z.array(schema).safeParse(items);
1211
- if (!result.success) {
1212
- throw new Error(
1213
- `Invalid query result:
1214
- ${result.error.issues.map((issue) => `- ${issue.path.map(String).join(".")}: ${issue.message}`).join("\n")}`
1215
- );
1216
- }
1217
- return result.data;
1459
+ async map(options) {
1460
+ const rootView = await this.viewMapper.getView(options.viewExternalId);
1461
+ this.validator.validate(options, rootView);
1462
+ const edgeMode = options.edgeMode ?? "append";
1463
+ const mappedItems = options.items.map(
1464
+ (item) => this.mapItem(item, rootView, options.onEdgeCreation, edgeMode)
1465
+ );
1466
+ const items = mappedItems.flatMap((item) => item.writes);
1467
+ const edgeReplacements = mappedItems.flatMap((item) => item.edgeReplacements);
1468
+ const deleteItems = edgeMode === "replace" ? await this.mapEdgeReplacementDeletes(edgeReplacements) : [];
1469
+ return {
1470
+ items,
1471
+ ...deleteItems.length > 0 ? { delete: deleteItems } : {},
1472
+ ...options.replace === true ? { replace: true } : {}
1473
+ };
1218
1474
  }
1219
- async buildResultSchema(view, remainingDepth, select) {
1220
- const shape = { ...nodeMetadataSchema };
1221
- const includeAllProperties = select == null || select._all === true;
1222
- for (const [name, property] of Object.entries(view.properties)) {
1223
- const isSelected = includeAllProperties || name in select;
1224
- if (!isSelected) continue;
1225
- const nestedSelect = isRecord2(select?.[name]) ? select[name] : void 0;
1475
+ mapItem(item, rootView, onEdgeCreation, edgeMode) {
1476
+ const node = { space: item.space, externalId: item.externalId };
1477
+ const nodeProperties = {};
1478
+ const inferredItems = [];
1479
+ const edgeReplacements = [];
1480
+ for (const [name, value] of Object.entries(item)) {
1481
+ if (IDENTITY_KEYS.has(name)) continue;
1482
+ const property = rootView.properties[name];
1483
+ if (!property) continue;
1226
1484
  if (isViewPropertyDefinition(property)) {
1227
- const relationSource = getDirectRelationSource(property);
1228
- if (relationSource) {
1229
- shape[name] = await this.buildRelationSchema(
1230
- property,
1231
- relationSource.externalId,
1232
- remainingDepth,
1233
- nestedSelect
1234
- );
1235
- } else {
1236
- shape[name] = propertyValueSchema(property, { dateMode: "coerce" }).optional();
1485
+ nodeProperties[name] = normalizeViewPropertyValue(value, property);
1486
+ } else if (isReverseDirectRelation(property)) {
1487
+ inferredItems.push(...this.mapReverseDirectRelation(node, value, property));
1488
+ } else if (isEdgeConnection(property)) {
1489
+ const desiredEdges = this.mapEdgeConnection(node, name, value, property, onEdgeCreation);
1490
+ inferredItems.push(...desiredEdges);
1491
+ if (edgeMode === "replace") {
1492
+ edgeReplacements.push({ rootNode: node, propertyName: name, property, desiredEdges });
1237
1493
  }
1238
- continue;
1239
- }
1240
- if (isReverseDirectRelation(property) || isEdgeConnection(property)) {
1241
- shape[name] = await this.buildRelationSchema(
1242
- property,
1243
- property.source.externalId,
1244
- remainingDepth,
1245
- nestedSelect
1246
- );
1247
1494
  }
1248
1495
  }
1249
- const schema = z.object(shape);
1250
- return includeAllProperties ? schema.strict() : schema;
1496
+ const applyNode = {
1497
+ instanceType: "node",
1498
+ ...node
1499
+ };
1500
+ if (Object.keys(nodeProperties).length > 0) {
1501
+ applyNode.sources = [{ source: toViewReference(rootView), properties: nodeProperties }];
1502
+ }
1503
+ return { writes: [applyNode, ...inferredItems], edgeReplacements };
1504
+ }
1505
+ async mapEdgeReplacementDeletes(replacements) {
1506
+ const deletes = await Promise.all(
1507
+ replacements.map(async (replacement) => {
1508
+ const existingEdges = await this.queryExistingEdges(replacement);
1509
+ const desiredEdgeKeys = new Set(replacement.desiredEdges.map((edge) => instanceKey(edge)));
1510
+ return existingEdges.filter((edge) => !desiredEdgeKeys.has(instanceKey(edge))).map((edge) => ({
1511
+ instanceType: "edge",
1512
+ space: edge.space,
1513
+ externalId: edge.externalId
1514
+ }));
1515
+ })
1516
+ );
1517
+ return uniqueDeletes(deletes.flat());
1518
+ }
1519
+ async queryExistingEdges(replacement) {
1520
+ const rootKey = `${replacement.propertyName}Root`;
1521
+ const edgeKey = `${replacement.propertyName}Edges`;
1522
+ const direction = replacement.property.direction ?? "outwards";
1523
+ const query = {
1524
+ with: {
1525
+ [rootKey]: {
1526
+ nodes: {
1527
+ filter: {
1528
+ instanceReferences: [replacement.rootNode]
1529
+ }
1530
+ },
1531
+ limit: 1
1532
+ },
1533
+ [edgeKey]: {
1534
+ edges: {
1535
+ from: rootKey,
1536
+ maxDistance: 1,
1537
+ direction,
1538
+ filter: {
1539
+ equals: { property: ["edge", "type"], value: replacement.property.type }
1540
+ }
1541
+ },
1542
+ limit: EDGE_QUERY_LIMIT
1543
+ }
1544
+ },
1545
+ select: {
1546
+ [rootKey]: {},
1547
+ [edgeKey]: {}
1548
+ }
1549
+ };
1550
+ const edges = [];
1551
+ let cursor;
1552
+ do {
1553
+ const response = await this.cognite.queryInstances({
1554
+ ...query,
1555
+ ...cursor ? { cursors: { [edgeKey]: cursor } } : {}
1556
+ });
1557
+ edges.push(
1558
+ ...(response.items[edgeKey] ?? []).filter(
1559
+ (item) => item.instanceType === "edge"
1560
+ )
1561
+ );
1562
+ cursor = response.nextCursor[edgeKey];
1563
+ } while (cursor);
1564
+ return edges;
1251
1565
  }
1252
- async buildRelationSchema(property, targetViewExternalId, remainingDepth, select) {
1253
- const isList = isListRelation(property);
1254
- const fallbackSchema = isViewPropertyDefinition(property) ? propertyValueSchema(property, { dateMode: "coerce" }) : z.unknown();
1255
- if (remainingDepth <= 0 || select == null) {
1256
- return fallbackSchema.optional();
1257
- }
1258
- const targetView = await this.viewMapper.getView(targetViewExternalId);
1259
- const nestedSchema = await this.buildResultSchema(targetView, remainingDepth - 1, select);
1260
- if (isViewPropertyDefinition(property)) {
1261
- const nestedRelationSchema = isList ? z.array(nestedSchema) : nestedSchema;
1262
- return z.union([nestedRelationSchema, fallbackSchema]).optional();
1263
- }
1264
- return (isList ? z.array(nestedSchema) : nestedSchema).optional();
1566
+ mapReverseDirectRelation(node, value, property) {
1567
+ const targets = asNodeIdArray(value);
1568
+ return targets.map((target) => ({
1569
+ instanceType: "node",
1570
+ ...target,
1571
+ sources: [
1572
+ {
1573
+ source: property.through.source,
1574
+ properties: {
1575
+ [property.through.identifier]: normalizeReverseDirectRelationValue(node, property)
1576
+ }
1577
+ }
1578
+ ]
1579
+ }));
1580
+ }
1581
+ mapEdgeConnection(node, propertyName, value, property, onEdgeCreation) {
1582
+ const direction = property.direction ?? "outwards";
1583
+ return asNodeIdArray(value).map((target) => {
1584
+ const startNode = direction === "inwards" ? target : node;
1585
+ const endNode = direction === "inwards" ? node : target;
1586
+ const edgeType = toNodeId(property.type, `edge type for "${propertyName}"`);
1587
+ const createEdgeId = onEdgeCreation?.[propertyName];
1588
+ if (!createEdgeId) {
1589
+ throw new Error(
1590
+ `Invalid upsert options:
1591
+ - onEdgeCreation.${propertyName}: required when ingesting edge connection "${propertyName}"`
1592
+ );
1593
+ }
1594
+ const edgeId = createEdgeId({
1595
+ startNode,
1596
+ endNode,
1597
+ edgeType
1598
+ });
1599
+ assertNodeId(edgeId, `onEdgeCreation(${propertyName})`);
1600
+ return {
1601
+ instanceType: "edge",
1602
+ ...edgeId,
1603
+ type: property.type,
1604
+ startNode,
1605
+ endNode
1606
+ };
1607
+ });
1265
1608
  }
1266
1609
  };
1610
+ function normalizeReverseDirectRelationValue(node, property) {
1611
+ return property.targetsList === true ? [node] : node;
1612
+ }
1613
+ function normalizeViewPropertyValue(value, property) {
1614
+ if (property.type.type !== "direct") return normalizePropertyValue(value);
1615
+ if (Array.isArray(value)) return value.map((item) => toNodeId(item));
1616
+ return toNodeId(value);
1617
+ }
1618
+ function normalizePropertyValue(value) {
1619
+ if (value instanceof Date) return value.toISOString();
1620
+ if (Array.isArray(value)) return value.map(normalizePropertyValue);
1621
+ if (isPlainObject(value)) {
1622
+ return Object.fromEntries(
1623
+ Object.entries(value).map(([key, nestedValue]) => [key, normalizePropertyValue(nestedValue)])
1624
+ );
1625
+ }
1626
+ return value;
1627
+ }
1628
+ function asNodeIdArray(value) {
1629
+ return Array.isArray(value) ? value.map((item) => toNodeId(item)) : [toNodeId(value)];
1630
+ }
1631
+ function toNodeId(value, label = "relation reference") {
1632
+ if (!isPlainObject(value) || typeof value.space !== "string" || typeof value.externalId !== "string") {
1633
+ throw new Error(`Invalid upsert options:
1634
+ - ${label}: expected a NodeId`);
1635
+ }
1636
+ return { space: value.space, externalId: value.externalId };
1637
+ }
1638
+ function instanceKey(instance) {
1639
+ return `${instance.space}\0${instance.externalId}`;
1640
+ }
1641
+ function uniqueDeletes(deletes) {
1642
+ const seen = /* @__PURE__ */ new Set();
1643
+ return deletes.filter((item) => {
1644
+ const key = instanceKey(item);
1645
+ if (seen.has(key)) return false;
1646
+ seen.add(key);
1647
+ return true;
1648
+ });
1649
+ }
1650
+ function assertNodeId(value, label) {
1651
+ if (!isPlainObject(value) || typeof value.space !== "string" || typeof value.externalId !== "string") {
1652
+ throw new Error(`Invalid upsert options:
1653
+ - ${label}: expected a NodeId`);
1654
+ }
1655
+ }
1656
+ function isPlainObject(value) {
1657
+ return value != null && typeof value === "object" && !Array.isArray(value);
1658
+ }
1267
1659
 
1268
1660
  // src/mappers/view-mapper.ts
1269
1661
  var ViewMapper = class {
@@ -1311,108 +1703,8 @@ var ViewMapper = class {
1311
1703
  }
1312
1704
  };
1313
1705
 
1314
- // src/utils/query.ts
1315
- function mapNodesAndEdges(queryResult, _query) {
1316
- return queryResult.items;
1317
- }
1318
- function appendNodesAndEdges(initial, additional) {
1319
- if (!additional) return initial;
1320
- for (const [key, items] of Object.entries(additional)) {
1321
- const existing = initial[key];
1322
- if (existing) {
1323
- existing.push(...items);
1324
- } else {
1325
- initial[key] = [...items];
1326
- }
1327
- }
1328
- return initial;
1329
- }
1330
- function getQueryForDependenciesPagination(query, queryResult, viewExternalId) {
1331
- const cursorKeys = new Set(Object.keys(queryResult.nextCursor));
1332
- const { nodesParent, nodesChildren } = getParentAndChildrenNodes(cursorKeys);
1333
- const leafCursors = getLeafCursors(queryResult, viewExternalId, nodesParent, nodesChildren);
1334
- if (Object.keys(leafCursors).length === 0) {
1335
- return null;
1336
- }
1337
- return buildDependenciesQuery(query, nodesParent, nodesChildren, leafCursors);
1338
- }
1339
- function getParentAndChildrenNodes(keys) {
1340
- const nodesParent = /* @__PURE__ */ new Map();
1341
- const nodesChildren = /* @__PURE__ */ new Map();
1342
- for (const key of keys) {
1343
- const keyParts = key.split(NESTED_SEP);
1344
- const validParents = /* @__PURE__ */ new Set();
1345
- for (let i = keyParts.length - 1; i > 0; i--) {
1346
- const parentPath = keyParts.slice(0, i).join(NESTED_SEP);
1347
- const parentWithEdgeMarker = `${parentPath}${NESTED_SEP}${EDGE_MARKER}`;
1348
- if (keys.has(parentWithEdgeMarker)) {
1349
- validParents.add(parentWithEdgeMarker);
1350
- const children = nodesChildren.get(parentWithEdgeMarker) ?? /* @__PURE__ */ new Set();
1351
- children.add(key);
1352
- nodesChildren.set(parentWithEdgeMarker, children);
1353
- }
1354
- if (keys.has(parentPath)) {
1355
- validParents.add(parentPath);
1356
- const children = nodesChildren.get(parentPath) ?? /* @__PURE__ */ new Set();
1357
- children.add(key);
1358
- nodesChildren.set(parentPath, children);
1359
- }
1360
- }
1361
- nodesParent.set(key, validParents);
1362
- }
1363
- return { nodesParent, nodesChildren };
1364
- }
1365
- function getLeafCursors(queryResult, viewExternalId, nodesParent, nodesChildren) {
1366
- const targetCursors = {};
1367
- const targetCursorKeys = /* @__PURE__ */ new Set();
1368
- for (const [cursorKey, cursorValue] of Object.entries(queryResult.nextCursor)) {
1369
- if (cursorKey === viewExternalId || !cursorValue || (queryResult.items[cursorKey]?.length ?? 0) !== MAX_LIMIT) {
1370
- continue;
1371
- }
1372
- const children = nodesChildren.get(cursorKey) ?? /* @__PURE__ */ new Set();
1373
- let skipDueToChild = false;
1374
- for (const c of children) {
1375
- if (targetCursorKeys.has(c)) {
1376
- skipDueToChild = true;
1377
- break;
1378
- }
1379
- }
1380
- if (skipDueToChild) continue;
1381
- const parent = nodesParent.get(cursorKey) ?? /* @__PURE__ */ new Set();
1382
- for (const key of parent) {
1383
- if (targetCursorKeys.has(key)) {
1384
- delete targetCursors[key];
1385
- targetCursorKeys.delete(key);
1386
- }
1387
- }
1388
- targetCursors[cursorKey] = cursorValue;
1389
- targetCursorKeys.add(cursorKey);
1390
- }
1391
- return targetCursors;
1392
- }
1393
- function buildDependenciesQuery(previousQuery, nodesParent, nodesChildren, leafCursors) {
1394
- const withExprs = {};
1395
- const selectExprs = {};
1396
- const finalCursors = {};
1397
- for (const [cursorKey, cursorValue] of Object.entries(leafCursors)) {
1398
- const children = nodesChildren.get(cursorKey) ?? /* @__PURE__ */ new Set();
1399
- const parent = nodesParent.get(cursorKey) ?? /* @__PURE__ */ new Set();
1400
- const validKeys = /* @__PURE__ */ new Set([...parent, ...children, cursorKey]);
1401
- for (const [k, v] of Object.entries(previousQuery.with)) {
1402
- if (validKeys.has(k)) withExprs[k] = v;
1403
- }
1404
- for (const [k, v] of Object.entries(previousQuery.select)) {
1405
- if (validKeys.has(k)) selectExprs[k] = v;
1406
- }
1407
- for (const [k, v] of Object.entries(previousQuery.cursors ?? {})) {
1408
- if (parent.has(k) && v) finalCursors[k] = v;
1409
- }
1410
- finalCursors[cursorKey] = cursorValue;
1411
- }
1412
- return { with: withExprs, select: selectExprs, cursors: finalCursors };
1413
- }
1414
-
1415
1706
  // src/client.ts
1707
+ var APPLY_ITEM_LIMIT = 1e3;
1416
1708
  var IndustrialModelClient = class {
1417
1709
  constructor(client, dataModelId, options = {}) {
1418
1710
  const cognite = createCogniteAdapter(client);
@@ -1420,6 +1712,7 @@ var IndustrialModelClient = class {
1420
1712
  const viewMapper = new ViewMapper(cognite, dataModelId);
1421
1713
  this.queryMapper = new QueryMapper(viewMapper, cognite);
1422
1714
  this.aggregateMapper = new AggregateMapper(viewMapper, cognite);
1715
+ this.upsertMapper = new UpsertMapper(viewMapper, cognite);
1423
1716
  this.aggregateResultMapper = new AggregateResultMapper();
1424
1717
  this.resultMapper = new QueryResultMapper(viewMapper);
1425
1718
  this.resultValidator = new QueryResultValidator(viewMapper);
@@ -1433,6 +1726,54 @@ var IndustrialModelClient = class {
1433
1726
  const execute = (options) => this.aggregateInternal(options);
1434
1727
  return execute;
1435
1728
  }
1729
+ upsert() {
1730
+ const execute = (options) => this.upsertInternal(options);
1731
+ return execute;
1732
+ }
1733
+ async delete(items) {
1734
+ const deleteItems = items.map((item) => {
1735
+ assertNodeId2(item);
1736
+ return {
1737
+ instanceType: "node",
1738
+ space: item.space,
1739
+ externalId: item.externalId
1740
+ };
1741
+ });
1742
+ const response = await this.applyInstancesInChunks({
1743
+ items: [],
1744
+ delete: deleteItems
1745
+ });
1746
+ return { items: response.items };
1747
+ }
1748
+ async upsertInternal(options) {
1749
+ const cogniteRequest = await this.upsertMapper.map(options);
1750
+ const response = await this.applyInstancesInChunks(cogniteRequest);
1751
+ return { items: response.items };
1752
+ }
1753
+ async applyInstancesInChunks(request) {
1754
+ const deleteItems = request.delete ?? [];
1755
+ const totalItems = request.items.length + deleteItems.length;
1756
+ if (totalItems === 0) return { items: [] };
1757
+ if (totalItems <= APPLY_ITEM_LIMIT) {
1758
+ return this.cognite.applyInstances(request);
1759
+ }
1760
+ const responses = [];
1761
+ for (const deleteChunk of chunks(deleteItems, APPLY_ITEM_LIMIT)) {
1762
+ const response = await this.cognite.applyInstances({
1763
+ items: [],
1764
+ delete: deleteChunk
1765
+ });
1766
+ responses.push(...response.items);
1767
+ }
1768
+ for (const itemChunk of chunks(request.items, APPLY_ITEM_LIMIT)) {
1769
+ const response = await this.cognite.applyInstances({
1770
+ items: itemChunk,
1771
+ ...request.replace === true ? { replace: true } : {}
1772
+ });
1773
+ responses.push(...response.items);
1774
+ }
1775
+ return { items: responses };
1776
+ }
1436
1777
  async aggregateInternal(options) {
1437
1778
  const cogniteRequest = await this.aggregateMapper.map(options);
1438
1779
  const response = await this.cognite.aggregateInstances(cogniteRequest);
@@ -1489,7 +1830,19 @@ var IndustrialModelClient = class {
1489
1830
  return appendNodesAndEdges(result, nestedResults);
1490
1831
  }
1491
1832
  };
1833
+ function chunks(items, size) {
1834
+ const result = [];
1835
+ for (let index = 0; index < items.length; index += size) {
1836
+ result.push(items.slice(index, index + size));
1837
+ }
1838
+ return result;
1839
+ }
1840
+ function assertNodeId2(value) {
1841
+ 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) {
1842
+ throw new Error("Invalid delete options:\n- items: expected NodeId values");
1843
+ }
1844
+ }
1492
1845
 
1493
- export { IndustrialModelClient, buildViewSchema, nodeIdSchema };
1846
+ export { IndustrialModelClient };
1494
1847
  //# sourceMappingURL=index.js.map
1495
1848
  //# sourceMappingURL=index.js.map