@utaba/deep-memory-storage-cosmosdb 0.18.0 → 0.19.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 +19 -12
- package/dist/index.cjs +374 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +350 -33
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
38
|
// src/CosmosDbProvider.ts
|
|
39
|
-
var
|
|
39
|
+
var import_deep_memory6 = require("@utaba/deep-memory");
|
|
40
40
|
|
|
41
41
|
// src/CosmosDbConnection.ts
|
|
42
42
|
var import_gremlin = __toESM(require("gremlin"), 1);
|
|
@@ -319,6 +319,7 @@ function sleep2(ms) {
|
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
// src/mapping.ts
|
|
322
|
+
var import_deep_memory = require("@utaba/deep-memory");
|
|
322
323
|
var STORED_ENTITY_FIELDS = [
|
|
323
324
|
"id",
|
|
324
325
|
"entityType",
|
|
@@ -659,9 +660,92 @@ function repositoryConfigToLadderBindings(config) {
|
|
|
659
660
|
bindings[SENTINEL_BINDING] = ABSENT_STRING_SENTINEL;
|
|
660
661
|
return bindings;
|
|
661
662
|
}
|
|
663
|
+
var RESERVED_ENTITY_PROPERTY_KEYS = /* @__PURE__ */ new Set([
|
|
664
|
+
"id",
|
|
665
|
+
"repositoryId",
|
|
666
|
+
...ENTITY_REQUIRED_SLOTS,
|
|
667
|
+
...ENTITY_OPTIONAL_SLOTS
|
|
668
|
+
]);
|
|
669
|
+
var RESERVED_RELATIONSHIP_PROPERTY_KEYS = /* @__PURE__ */ new Set([
|
|
670
|
+
"id",
|
|
671
|
+
"repositoryId",
|
|
672
|
+
"label",
|
|
673
|
+
...RELATIONSHIP_REQUIRED_SLOTS,
|
|
674
|
+
...RELATIONSHIP_OPTIONAL_SLOTS
|
|
675
|
+
]);
|
|
676
|
+
var USER_PROPERTY_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
677
|
+
function assertSafeUserPropertyKey(key, reserved, scope) {
|
|
678
|
+
if (!USER_PROPERTY_KEY_PATTERN.test(key)) {
|
|
679
|
+
throw new import_deep_memory.ProviderError(
|
|
680
|
+
`${scope} property key "${key}" is not a valid Gremlin identifier \u2014 must match ${USER_PROPERTY_KEY_PATTERN.source}. User-property keys are interpolated into Gremlin .property(...) / values(...) / .drop() slots (the key slot cannot be parameterised), so an unsafe value would widen the injection surface.`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
if (reserved.has(key)) {
|
|
684
|
+
throw new import_deep_memory.ProviderError(
|
|
685
|
+
`${scope} property key "${key}" collides with a schema-managed field. Reserved names: ${Array.from(reserved).join(", ")}.`
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
return key;
|
|
689
|
+
}
|
|
690
|
+
function assertSafeEntityUserPropertyKey(key) {
|
|
691
|
+
return assertSafeUserPropertyKey(key, RESERVED_ENTITY_PROPERTY_KEYS, "Entity");
|
|
692
|
+
}
|
|
693
|
+
function assertSafeRelationshipUserPropertyKey(key) {
|
|
694
|
+
return assertSafeUserPropertyKey(key, RESERVED_RELATIONSHIP_PROPERTY_KEYS, "Relationship");
|
|
695
|
+
}
|
|
696
|
+
function isNativeStorableValue(value) {
|
|
697
|
+
if (typeof value === "string") return true;
|
|
698
|
+
if (typeof value === "boolean") return true;
|
|
699
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
700
|
+
if (Array.isArray(value)) {
|
|
701
|
+
if (value.length === 0) return true;
|
|
702
|
+
const first = value[0];
|
|
703
|
+
const t = typeof first;
|
|
704
|
+
if (t !== "string" && t !== "boolean" && (t !== "number" || !Number.isFinite(first))) {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
for (const v of value) {
|
|
708
|
+
if (typeof v !== t) return false;
|
|
709
|
+
if (t === "number" && !Number.isFinite(v)) return false;
|
|
710
|
+
}
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
function entityUserPropertyParams(properties) {
|
|
716
|
+
const out = [];
|
|
717
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
718
|
+
assertSafeEntityUserPropertyKey(key);
|
|
719
|
+
if (isNativeStorableValue(value)) {
|
|
720
|
+
out.push({ key, value });
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return out;
|
|
724
|
+
}
|
|
725
|
+
function relationshipUserPropertyParams(properties) {
|
|
726
|
+
const out = [];
|
|
727
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
728
|
+
assertSafeRelationshipUserPropertyKey(key);
|
|
729
|
+
if (isNativeStorableValue(value)) {
|
|
730
|
+
out.push({ key, value });
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return out;
|
|
734
|
+
}
|
|
735
|
+
function existingEntityScalarUserKeys(blob) {
|
|
736
|
+
if (blob == null) return [];
|
|
737
|
+
const out = [];
|
|
738
|
+
for (const [key, value] of Object.entries(blob)) {
|
|
739
|
+
if (!USER_PROPERTY_KEY_PATTERN.test(key)) continue;
|
|
740
|
+
if (RESERVED_ENTITY_PROPERTY_KEYS.has(key)) continue;
|
|
741
|
+
if (!isNativeStorableValue(value)) continue;
|
|
742
|
+
out.push(key);
|
|
743
|
+
}
|
|
744
|
+
return out;
|
|
745
|
+
}
|
|
662
746
|
|
|
663
747
|
// src/queries/repository.ts
|
|
664
|
-
var
|
|
748
|
+
var import_deep_memory2 = require("@utaba/deep-memory");
|
|
665
749
|
var REPO_LABEL = "_repository";
|
|
666
750
|
var REPOSITORY_INDEX_VERTEX_ID = "_repository_index";
|
|
667
751
|
var REPOSITORY_INDEX_PARTITION = "_index";
|
|
@@ -727,7 +811,7 @@ async function createRepository(conn, config) {
|
|
|
727
811
|
{ vid: vertexId, rid: config.repositoryId, lbl: REPO_LABEL }
|
|
728
812
|
);
|
|
729
813
|
if (existing.items.length > 0 && Number(existing.items[0]) > 0) {
|
|
730
|
-
throw new
|
|
814
|
+
throw new import_deep_memory2.DuplicateRepositoryError(config.repositoryId);
|
|
731
815
|
}
|
|
732
816
|
const currentIds = await readRepositoryIndex(conn);
|
|
733
817
|
const updatedIds = currentIds.includes(config.repositoryId) ? currentIds : [...currentIds, config.repositoryId];
|
|
@@ -798,7 +882,7 @@ async function listRepositories(conn, filter) {
|
|
|
798
882
|
async function updateRepository(conn, repositoryId, updates) {
|
|
799
883
|
const vertexId = repoVertexId(repositoryId);
|
|
800
884
|
const existing = await getRepository(conn, repositoryId);
|
|
801
|
-
if (!existing) throw new
|
|
885
|
+
if (!existing) throw new import_deep_memory2.RepositoryNotFoundError(repositoryId);
|
|
802
886
|
const bindings = { vid: vertexId, rid: repositoryId };
|
|
803
887
|
const props = {};
|
|
804
888
|
if (updates.label !== void 0) props["repoLabel"] = updates.label;
|
|
@@ -1033,9 +1117,10 @@ async function getVocabularyChangeLog(conn, repositoryId, options) {
|
|
|
1033
1117
|
}
|
|
1034
1118
|
|
|
1035
1119
|
// src/queries/entity.ts
|
|
1036
|
-
var
|
|
1120
|
+
var import_deep_memory3 = require("@utaba/deep-memory");
|
|
1037
1121
|
var DUPLICATE_SENTINEL = "__duplicate";
|
|
1038
|
-
var
|
|
1122
|
+
var ENTITY_CREATE_PREFIX = `g.V().has('repositoryId', rid).hasId(vid).fold().coalesce(unfold().constant('${DUPLICATE_SENTINEL}'),addV(vertexLabel).property('id', vid).property('repositoryId', rid)${buildEntityPropertyLadder()}`;
|
|
1123
|
+
var ENTITY_CREATE_QUERY = `${ENTITY_CREATE_PREFIX})`;
|
|
1039
1124
|
async function createEntity(conn, repositoryId, entity) {
|
|
1040
1125
|
const bindings = {
|
|
1041
1126
|
rid: repositoryId,
|
|
@@ -1043,14 +1128,27 @@ async function createEntity(conn, repositoryId, entity) {
|
|
|
1043
1128
|
vertexLabel: entity.entityType,
|
|
1044
1129
|
...entityToLadderBindings(entity)
|
|
1045
1130
|
};
|
|
1046
|
-
const
|
|
1131
|
+
const userProps = entityUserPropertyParams(entity.properties ?? {});
|
|
1132
|
+
let query;
|
|
1133
|
+
if (userProps.length === 0) {
|
|
1134
|
+
query = ENTITY_CREATE_QUERY;
|
|
1135
|
+
} else {
|
|
1136
|
+
let suffix = "";
|
|
1137
|
+
for (let i = 0; i < userProps.length; i++) {
|
|
1138
|
+
const { key, value } = userProps[i];
|
|
1139
|
+
suffix += `.property('${key}', p_user_${i})`;
|
|
1140
|
+
bindings[`p_user_${i}`] = value;
|
|
1141
|
+
}
|
|
1142
|
+
query = `${ENTITY_CREATE_PREFIX}${suffix})`;
|
|
1143
|
+
}
|
|
1144
|
+
const result = await conn.submit(query, bindings);
|
|
1047
1145
|
if (result.items[0] === DUPLICATE_SENTINEL) {
|
|
1048
|
-
throw new
|
|
1146
|
+
throw new import_deep_memory3.DuplicateEntityError(entity.id);
|
|
1049
1147
|
}
|
|
1050
1148
|
return entity;
|
|
1051
1149
|
}
|
|
1052
1150
|
async function getEntity(conn, repositoryId, entityId, options) {
|
|
1053
|
-
const projection = (0,
|
|
1151
|
+
const projection = (0, import_deep_memory3.buildVertexProjectChain)({ withEmbedding: options?.loadEmbeddings });
|
|
1054
1152
|
const result = await conn.submit(
|
|
1055
1153
|
`g.V().has('repositoryId', rid).hasId(eid).has('entityType').${projection}`,
|
|
1056
1154
|
{ rid: repositoryId, eid: entityId }
|
|
@@ -1059,7 +1157,7 @@ async function getEntity(conn, repositoryId, entityId, options) {
|
|
|
1059
1157
|
return entityFromGremlin(result.items[0]);
|
|
1060
1158
|
}
|
|
1061
1159
|
async function getEntityBySlug(conn, repositoryId, slug, options) {
|
|
1062
|
-
const projection = (0,
|
|
1160
|
+
const projection = (0, import_deep_memory3.buildVertexProjectChain)({ withEmbedding: options?.loadEmbeddings });
|
|
1063
1161
|
const result = await conn.submit(
|
|
1064
1162
|
`g.V().has('repositoryId', rid).has('slug', slugVal).has('entityType').${projection}`,
|
|
1065
1163
|
{ rid: repositoryId, slugVal: slug }
|
|
@@ -1077,7 +1175,7 @@ async function getEntities(conn, repositoryId, entityIds, options) {
|
|
|
1077
1175
|
idParams.push(paramName);
|
|
1078
1176
|
});
|
|
1079
1177
|
const withinClause = `within(${idParams.join(", ")})`;
|
|
1080
|
-
const projection = (0,
|
|
1178
|
+
const projection = (0, import_deep_memory3.buildVertexProjectChain)({ withEmbedding: options?.loadEmbeddings });
|
|
1081
1179
|
const result = await conn.submit(
|
|
1082
1180
|
`g.V().has('repositoryId', rid).hasId(${withinClause}).has('entityType').${projection}`,
|
|
1083
1181
|
bindings
|
|
@@ -1089,7 +1187,36 @@ async function getEntities(conn, repositoryId, entityIds, options) {
|
|
|
1089
1187
|
}
|
|
1090
1188
|
return map;
|
|
1091
1189
|
}
|
|
1190
|
+
async function readExistingEntityPropertiesBlob(conn, repositoryId, entityId) {
|
|
1191
|
+
const result = await conn.submit(
|
|
1192
|
+
"g.V().has('repositoryId', rid).hasId(eid).has('entityType').values('properties').limit(1)",
|
|
1193
|
+
{ rid: repositoryId, eid: entityId }
|
|
1194
|
+
);
|
|
1195
|
+
if (result.items.length === 0) return { found: false };
|
|
1196
|
+
const raw = result.items[0];
|
|
1197
|
+
const json = typeof raw === "string" ? raw : String(raw ?? "");
|
|
1198
|
+
if (!json) return { found: true, blob: {} };
|
|
1199
|
+
try {
|
|
1200
|
+
const parsed = JSON.parse(json);
|
|
1201
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1202
|
+
return { found: true, blob: parsed };
|
|
1203
|
+
}
|
|
1204
|
+
} catch {
|
|
1205
|
+
}
|
|
1206
|
+
return { found: true, blob: {} };
|
|
1207
|
+
}
|
|
1092
1208
|
async function updateEntity(conn, repositoryId, entityId, updates) {
|
|
1209
|
+
const userProps = updates.properties !== void 0 ? entityUserPropertyParams(updates.properties) : null;
|
|
1210
|
+
let droppedUserKeys = [];
|
|
1211
|
+
if (updates.properties !== void 0) {
|
|
1212
|
+
const existing = await readExistingEntityPropertiesBlob(conn, repositoryId, entityId);
|
|
1213
|
+
if (!existing.found) {
|
|
1214
|
+
throw new import_deep_memory3.EntityNotFoundError(entityId);
|
|
1215
|
+
}
|
|
1216
|
+
const existingKeys = existingEntityScalarUserKeys(existing.blob);
|
|
1217
|
+
const newKeySet = new Set(userProps.map((p) => p.key));
|
|
1218
|
+
droppedUserKeys = existingKeys.filter((k) => !newKeySet.has(k));
|
|
1219
|
+
}
|
|
1093
1220
|
const bindings = { rid: repositoryId, eid: entityId };
|
|
1094
1221
|
const propParts = [];
|
|
1095
1222
|
let idx = 0;
|
|
@@ -1106,7 +1233,18 @@ async function updateEntity(conn, repositoryId, entityId, updates) {
|
|
|
1106
1233
|
if (updates.slug !== void 0) addProp("slug", updates.slug);
|
|
1107
1234
|
if (updates.summary === null) dropProp("summary");
|
|
1108
1235
|
else if (updates.summary !== void 0) addProp("summary", updates.summary);
|
|
1109
|
-
if (updates.properties !== void 0
|
|
1236
|
+
if (updates.properties !== void 0 && userProps !== null) {
|
|
1237
|
+
addProp("properties", JSON.stringify(updates.properties));
|
|
1238
|
+
for (const dropKey of droppedUserKeys) {
|
|
1239
|
+
dropProp(dropKey);
|
|
1240
|
+
}
|
|
1241
|
+
for (let i = 0; i < userProps.length; i++) {
|
|
1242
|
+
const { key, value } = userProps[i];
|
|
1243
|
+
const paramName = `p_user_${i}`;
|
|
1244
|
+
bindings[paramName] = value;
|
|
1245
|
+
propParts.push(`.property('${key}', ${paramName})`);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1110
1248
|
if (updates.data === null) dropProp("data");
|
|
1111
1249
|
else if (updates.data !== void 0) addProp("data", updates.data);
|
|
1112
1250
|
if (updates.dataFormat === null) dropProp("dataFormat");
|
|
@@ -1117,11 +1255,11 @@ async function updateEntity(conn, repositoryId, entityId, updates) {
|
|
|
1117
1255
|
addProp("modifiedAt", updates.provenance.modifiedAt);
|
|
1118
1256
|
if (updates.provenance.modifiedInConversation != null) addProp("modifiedInConversation", updates.provenance.modifiedInConversation);
|
|
1119
1257
|
if (updates.provenance.modifiedFromMessage != null) addProp("modifiedFromMessage", updates.provenance.modifiedFromMessage);
|
|
1120
|
-
const projection = (0,
|
|
1258
|
+
const projection = (0, import_deep_memory3.buildVertexProjectChain)();
|
|
1121
1259
|
const query = `g.V().has('repositoryId', rid).hasId(eid).has('entityType')${propParts.join("")}.${projection}`;
|
|
1122
1260
|
const result = await conn.submit(query, bindings);
|
|
1123
1261
|
if (result.items.length === 0) {
|
|
1124
|
-
throw new
|
|
1262
|
+
throw new import_deep_memory3.EntityNotFoundError(entityId);
|
|
1125
1263
|
}
|
|
1126
1264
|
return entityFromGremlin(result.items[0]);
|
|
1127
1265
|
}
|
|
@@ -1161,16 +1299,33 @@ function buildWhereClause(query, repositoryId) {
|
|
|
1161
1299
|
`(CONTAINS(${sqlPath("entityLabel")}, @term, true) OR CONTAINS(${sqlPath("slug")}, @term, true) OR CONTAINS(${sqlPath("summary")}, @term, true))`
|
|
1162
1300
|
);
|
|
1163
1301
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1302
|
+
let propertyFilterMode = "none";
|
|
1303
|
+
if (query.properties != null && Object.keys(query.properties).length > 0) {
|
|
1304
|
+
const entries = Object.entries(query.properties);
|
|
1305
|
+
const allStorable = entries.every(([, value]) => isNativeStorableValue(value));
|
|
1306
|
+
if (allStorable) {
|
|
1307
|
+
for (const [key] of entries) {
|
|
1308
|
+
assertSafeEntityUserPropertyKey(key);
|
|
1309
|
+
}
|
|
1310
|
+
let i = 0;
|
|
1311
|
+
for (const [key, value] of entries) {
|
|
1312
|
+
const name = `@val${i++}`;
|
|
1313
|
+
params.push({ name, value });
|
|
1314
|
+
predicates.push(`${sqlPath(key)} = ${name}`);
|
|
1315
|
+
}
|
|
1316
|
+
propertyFilterMode = "exact";
|
|
1317
|
+
} else {
|
|
1318
|
+
let i = 0;
|
|
1319
|
+
for (const [key, value] of entries) {
|
|
1320
|
+
const fragment = JSON.stringify({ [key]: value }).slice(1, -1);
|
|
1321
|
+
const name = `@kv${i++}`;
|
|
1322
|
+
params.push({ name, value: fragment });
|
|
1323
|
+
predicates.push(`CONTAINS(${sqlPath("properties")}, ${name}, false)`);
|
|
1324
|
+
}
|
|
1325
|
+
propertyFilterMode = "approximate";
|
|
1171
1326
|
}
|
|
1172
1327
|
}
|
|
1173
|
-
return { sqlWhere: `WHERE ${predicates.join(" AND ")}`, params };
|
|
1328
|
+
return { sqlWhere: `WHERE ${predicates.join(" AND ")}`, params, propertyFilterMode };
|
|
1174
1329
|
}
|
|
1175
1330
|
function buildSelectClause(loadEmbeddings) {
|
|
1176
1331
|
const fields = ["c.id", ...STORED_ENTITY_FIELDS.filter((f) => f !== "id").map((f) => `c.${f}`)];
|
|
@@ -1178,7 +1333,7 @@ function buildSelectClause(loadEmbeddings) {
|
|
|
1178
1333
|
return `SELECT ${fields.join(", ")}`;
|
|
1179
1334
|
}
|
|
1180
1335
|
async function findEntities(docClient, repositoryId, query, options) {
|
|
1181
|
-
const { sqlWhere, params } = buildWhereClause(query, repositoryId);
|
|
1336
|
+
const { sqlWhere, params, propertyFilterMode } = buildWhereClause(query, repositoryId);
|
|
1182
1337
|
const dataParams = [
|
|
1183
1338
|
...params,
|
|
1184
1339
|
{ name: "@off", value: query.offset },
|
|
@@ -1187,7 +1342,7 @@ async function findEntities(docClient, repositoryId, query, options) {
|
|
|
1187
1342
|
const selectClause = buildSelectClause(options?.loadEmbeddings === true);
|
|
1188
1343
|
const dataSql = `${selectClause} FROM c ${sqlWhere} ORDER BY c.id OFFSET @off LIMIT @lim`;
|
|
1189
1344
|
const countSql = `SELECT VALUE COUNT(1) FROM c ${sqlWhere}`;
|
|
1190
|
-
const skipCount =
|
|
1345
|
+
const skipCount = propertyFilterMode === "approximate";
|
|
1191
1346
|
const [dataResult, countResult] = await Promise.all([
|
|
1192
1347
|
docClient.query(dataSql, dataParams, {
|
|
1193
1348
|
partitionKey: repositoryId
|
|
@@ -1199,7 +1354,7 @@ async function findEntities(docClient, repositoryId, query, options) {
|
|
|
1199
1354
|
const filters = Object.entries(query.properties).map(
|
|
1200
1355
|
([key, value]) => ({ key, operator: "eq", value })
|
|
1201
1356
|
);
|
|
1202
|
-
items = items.filter((entity) => (0,
|
|
1357
|
+
items = items.filter((entity) => (0, import_deep_memory3.matchesPropertyFilters)(entity.properties, filters));
|
|
1203
1358
|
}
|
|
1204
1359
|
const total = countResult && countResult.documents.length > 0 ? Number(countResult.documents[0]) : void 0;
|
|
1205
1360
|
const hasMore = total != null ? query.offset + items.length < total : items.length === query.limit;
|
|
@@ -1213,9 +1368,10 @@ async function findEntities(docClient, repositoryId, query, options) {
|
|
|
1213
1368
|
}
|
|
1214
1369
|
|
|
1215
1370
|
// src/queries/relationship.ts
|
|
1216
|
-
var
|
|
1371
|
+
var import_deep_memory4 = require("@utaba/deep-memory");
|
|
1217
1372
|
var DUPLICATE_SENTINEL2 = "__duplicate";
|
|
1218
|
-
var
|
|
1373
|
+
var RELATIONSHIP_CREATE_PREFIX = `g.E().has('repositoryId', rid).hasId(relId).fold().coalesce(unfold().constant('${DUPLICATE_SENTINEL2}'),g.V().has('repositoryId', rid).hasId(srcId).has('entityType').addE(edgeLabel).to(g.V().has('repositoryId', rid).hasId(tgtId).has('entityType')).property('id', relId).property('repositoryId', rid)${buildRelationshipPropertyLadder()}`;
|
|
1374
|
+
var RELATIONSHIP_CREATE_QUERY = `${RELATIONSHIP_CREATE_PREFIX})`;
|
|
1219
1375
|
async function createRelationship(conn, repositoryId, relationship) {
|
|
1220
1376
|
const bindings = {
|
|
1221
1377
|
rid: repositoryId,
|
|
@@ -1225,14 +1381,27 @@ async function createRelationship(conn, repositoryId, relationship) {
|
|
|
1225
1381
|
edgeLabel: relationship.relationshipType,
|
|
1226
1382
|
...relationshipToLadderBindings(relationship)
|
|
1227
1383
|
};
|
|
1228
|
-
const
|
|
1384
|
+
const userProps = relationshipUserPropertyParams(relationship.properties ?? {});
|
|
1385
|
+
let query;
|
|
1386
|
+
if (userProps.length === 0) {
|
|
1387
|
+
query = RELATIONSHIP_CREATE_QUERY;
|
|
1388
|
+
} else {
|
|
1389
|
+
let suffix = "";
|
|
1390
|
+
for (let i = 0; i < userProps.length; i++) {
|
|
1391
|
+
const { key, value } = userProps[i];
|
|
1392
|
+
suffix += `.property('${key}', p_user_${i})`;
|
|
1393
|
+
bindings[`p_user_${i}`] = value;
|
|
1394
|
+
}
|
|
1395
|
+
query = `${RELATIONSHIP_CREATE_PREFIX}${suffix})`;
|
|
1396
|
+
}
|
|
1397
|
+
const result = await conn.submit(query, bindings);
|
|
1229
1398
|
if (result.items[0] === DUPLICATE_SENTINEL2) {
|
|
1230
|
-
throw new
|
|
1399
|
+
throw new import_deep_memory4.DuplicateRelationshipError(relationship.id);
|
|
1231
1400
|
}
|
|
1232
1401
|
return relationship;
|
|
1233
1402
|
}
|
|
1234
1403
|
async function getRelationship(conn, repositoryId, relationshipId) {
|
|
1235
|
-
const projection = (0,
|
|
1404
|
+
const projection = (0, import_deep_memory4.buildEdgeProjectChain)();
|
|
1236
1405
|
const result = await conn.submit(
|
|
1237
1406
|
`g.E().hasId(relId).has('repositoryId', rid).${projection}`,
|
|
1238
1407
|
{ relId: relationshipId, rid: repositoryId }
|
|
@@ -1279,7 +1448,7 @@ async function getEntityRelationships(conn, repositoryId, entityId, options) {
|
|
|
1279
1448
|
unionQuery = `g.V().has('repositoryId', rid).hasId(eid).has('entityType').union(inE()${typeFilter}, outE()${typeFilter}.has('bidirectional', true))`;
|
|
1280
1449
|
}
|
|
1281
1450
|
const baseQuery = unionQuery ?? `${edgeTraversal}${typeFilter}`;
|
|
1282
|
-
const projection = (0,
|
|
1451
|
+
const projection = (0, import_deep_memory4.buildEdgeProjectChain)();
|
|
1283
1452
|
const dataBindings = { ...baseBindings, rangeStart: offset, rangeEnd: offset + limit };
|
|
1284
1453
|
const [countResult, dataResult] = await Promise.all([
|
|
1285
1454
|
hasPropertyFilters ? Promise.resolve(null) : conn.submit(`${baseQuery}.dedup().count()`, baseBindings),
|
|
@@ -1291,7 +1460,7 @@ async function getEntityRelationships(conn, repositoryId, entityId, options) {
|
|
|
1291
1460
|
const rawItems = dataResult.items;
|
|
1292
1461
|
let items = rawItems.map(relationshipFromGremlin);
|
|
1293
1462
|
if (hasPropertyFilters) {
|
|
1294
|
-
items = items.filter((rel) => (0,
|
|
1463
|
+
items = items.filter((rel) => (0, import_deep_memory4.matchesPropertyFilters)(rel.properties, options.propertyFilters));
|
|
1295
1464
|
}
|
|
1296
1465
|
const total = countResult ? Number(countResult.items[0] ?? 0) : void 0;
|
|
1297
1466
|
const hasMore = total != null ? offset + rawItems.length < total : rawItems.length === limit;
|
|
@@ -1381,7 +1550,7 @@ function isInTimeRange(timestamp, timeRange) {
|
|
|
1381
1550
|
}
|
|
1382
1551
|
|
|
1383
1552
|
// src/queries/adaptive-import.ts
|
|
1384
|
-
var
|
|
1553
|
+
var import_deep_memory5 = require("@utaba/deep-memory");
|
|
1385
1554
|
var DEFAULT_MIN = 1;
|
|
1386
1555
|
var DEFAULT_START = 5;
|
|
1387
1556
|
var DEFAULT_MAX = 32;
|
|
@@ -1632,7 +1801,7 @@ async function runAdaptive(items, controller, fn) {
|
|
|
1632
1801
|
}
|
|
1633
1802
|
await Promise.all(workers);
|
|
1634
1803
|
if (aborted) {
|
|
1635
|
-
throw new
|
|
1804
|
+
throw new import_deep_memory5.ImportThrottleAbortError(
|
|
1636
1805
|
controller.getConcurrency(),
|
|
1637
1806
|
controller.getConsecutiveThrottlesAtMin(),
|
|
1638
1807
|
controller.getCompleted(),
|
|
@@ -1778,8 +1947,12 @@ var ENTITY_LADDER_CHAIN = buildEntityPropertyLadder();
|
|
|
1778
1947
|
var RELATIONSHIP_LADDER_CHAIN = buildRelationshipPropertyLadder();
|
|
1779
1948
|
var INSERT_ENTITY_QUERY = `g.addV(vertexLabel).property('id', vid).property('repositoryId', rid)${ENTITY_LADDER_CHAIN}`;
|
|
1780
1949
|
var INSERT_RELATIONSHIP_QUERY = `g.V().has('repositoryId', rid).hasId(srcId).has('entityType').addE(edgeLabel).to(g.V().has('repositoryId', rid).hasId(tgtId).has('entityType')).property('id', relId).property('repositoryId', rid)${RELATIONSHIP_LADDER_CHAIN}`;
|
|
1781
|
-
var
|
|
1782
|
-
var
|
|
1950
|
+
var UPSERT_ENTITY_OPEN = `g.V().has('repositoryId', rid).hasId(vid).has('entityType').fold().coalesce(unfold()${ENTITY_LADDER_CHAIN}`;
|
|
1951
|
+
var UPSERT_ENTITY_CREATE_BRANCH = `, addV(vertexLabel).property('id', vid).property('repositoryId', rid)${ENTITY_LADDER_CHAIN}`;
|
|
1952
|
+
var UPSERT_ENTITY_QUERY = `${UPSERT_ENTITY_OPEN}${UPSERT_ENTITY_CREATE_BRANCH})`;
|
|
1953
|
+
var UPSERT_RELATIONSHIP_OPEN = `g.E().has('repositoryId', rid).hasId(relId).fold().coalesce(unfold()${RELATIONSHIP_LADDER_CHAIN}`;
|
|
1954
|
+
var UPSERT_RELATIONSHIP_CREATE_BRANCH = `, g.V().has('repositoryId', rid).hasId(srcId).has('entityType').addE(edgeLabel).to(g.V().has('repositoryId', rid).hasId(tgtId).has('entityType')).property('id', relId).property('repositoryId', rid)${RELATIONSHIP_LADDER_CHAIN}`;
|
|
1955
|
+
var UPSERT_RELATIONSHIP_QUERY = `${UPSERT_RELATIONSHIP_OPEN}${UPSERT_RELATIONSHIP_CREATE_BRANCH})`;
|
|
1783
1956
|
async function insertEntity(conn, repositoryId, entity) {
|
|
1784
1957
|
const bindings = {
|
|
1785
1958
|
rid: repositoryId,
|
|
@@ -1787,7 +1960,20 @@ async function insertEntity(conn, repositoryId, entity) {
|
|
|
1787
1960
|
vertexLabel: entity.entityType,
|
|
1788
1961
|
...entityToLadderBindings(entity)
|
|
1789
1962
|
};
|
|
1790
|
-
|
|
1963
|
+
const userProps = entityUserPropertyParams(entity.properties ?? {});
|
|
1964
|
+
let query;
|
|
1965
|
+
if (userProps.length === 0) {
|
|
1966
|
+
query = INSERT_ENTITY_QUERY;
|
|
1967
|
+
} else {
|
|
1968
|
+
let suffix = "";
|
|
1969
|
+
for (let i = 0; i < userProps.length; i++) {
|
|
1970
|
+
const { key, value } = userProps[i];
|
|
1971
|
+
suffix += `.property('${key}', p_user_${i})`;
|
|
1972
|
+
bindings[`p_user_${i}`] = value;
|
|
1973
|
+
}
|
|
1974
|
+
query = `${INSERT_ENTITY_QUERY}${suffix}`;
|
|
1975
|
+
}
|
|
1976
|
+
await conn.submit(query, bindings);
|
|
1791
1977
|
}
|
|
1792
1978
|
async function insertRelationship(conn, repositoryId, rel) {
|
|
1793
1979
|
const bindings = {
|
|
@@ -1798,7 +1984,20 @@ async function insertRelationship(conn, repositoryId, rel) {
|
|
|
1798
1984
|
edgeLabel: rel.relationshipType,
|
|
1799
1985
|
...relationshipToLadderBindings(rel)
|
|
1800
1986
|
};
|
|
1801
|
-
|
|
1987
|
+
const userProps = relationshipUserPropertyParams(rel.properties ?? {});
|
|
1988
|
+
let query;
|
|
1989
|
+
if (userProps.length === 0) {
|
|
1990
|
+
query = INSERT_RELATIONSHIP_QUERY;
|
|
1991
|
+
} else {
|
|
1992
|
+
let suffix = "";
|
|
1993
|
+
for (let i = 0; i < userProps.length; i++) {
|
|
1994
|
+
const { key, value } = userProps[i];
|
|
1995
|
+
suffix += `.property('${key}', p_user_${i})`;
|
|
1996
|
+
bindings[`p_user_${i}`] = value;
|
|
1997
|
+
}
|
|
1998
|
+
query = `${INSERT_RELATIONSHIP_QUERY}${suffix}`;
|
|
1999
|
+
}
|
|
2000
|
+
await conn.submit(query, bindings);
|
|
1802
2001
|
}
|
|
1803
2002
|
async function upsertEntity(conn, repositoryId, entity) {
|
|
1804
2003
|
const bindings = {
|
|
@@ -1807,7 +2006,20 @@ async function upsertEntity(conn, repositoryId, entity) {
|
|
|
1807
2006
|
vertexLabel: entity.entityType,
|
|
1808
2007
|
...entityToLadderBindings(entity)
|
|
1809
2008
|
};
|
|
1810
|
-
|
|
2009
|
+
const userProps = entityUserPropertyParams(entity.properties ?? {});
|
|
2010
|
+
let query;
|
|
2011
|
+
if (userProps.length === 0) {
|
|
2012
|
+
query = UPSERT_ENTITY_QUERY;
|
|
2013
|
+
} else {
|
|
2014
|
+
let suffix = "";
|
|
2015
|
+
for (let i = 0; i < userProps.length; i++) {
|
|
2016
|
+
const { key, value } = userProps[i];
|
|
2017
|
+
suffix += `.property('${key}', p_user_${i})`;
|
|
2018
|
+
bindings[`p_user_${i}`] = value;
|
|
2019
|
+
}
|
|
2020
|
+
query = `${UPSERT_ENTITY_OPEN}${suffix}${UPSERT_ENTITY_CREATE_BRANCH}${suffix})`;
|
|
2021
|
+
}
|
|
2022
|
+
await conn.submit(query, bindings);
|
|
1811
2023
|
}
|
|
1812
2024
|
async function upsertRelationship(conn, repositoryId, rel) {
|
|
1813
2025
|
const bindings = {
|
|
@@ -1818,7 +2030,20 @@ async function upsertRelationship(conn, repositoryId, rel) {
|
|
|
1818
2030
|
edgeLabel: rel.relationshipType,
|
|
1819
2031
|
...relationshipToLadderBindings(rel)
|
|
1820
2032
|
};
|
|
1821
|
-
|
|
2033
|
+
const userProps = relationshipUserPropertyParams(rel.properties ?? {});
|
|
2034
|
+
let query;
|
|
2035
|
+
if (userProps.length === 0) {
|
|
2036
|
+
query = UPSERT_RELATIONSHIP_QUERY;
|
|
2037
|
+
} else {
|
|
2038
|
+
let suffix = "";
|
|
2039
|
+
for (let i = 0; i < userProps.length; i++) {
|
|
2040
|
+
const { key, value } = userProps[i];
|
|
2041
|
+
suffix += `.property('${key}', p_user_${i})`;
|
|
2042
|
+
bindings[`p_user_${i}`] = value;
|
|
2043
|
+
}
|
|
2044
|
+
query = `${UPSERT_RELATIONSHIP_OPEN}${suffix}${UPSERT_RELATIONSHIP_CREATE_BRANCH}${suffix})`;
|
|
2045
|
+
}
|
|
2046
|
+
await conn.submit(query, bindings);
|
|
1822
2047
|
}
|
|
1823
2048
|
|
|
1824
2049
|
// src/CosmosDbProvider.ts
|
|
@@ -1838,7 +2063,7 @@ var CosmosDbProvider = class {
|
|
|
1838
2063
|
conn;
|
|
1839
2064
|
docClient;
|
|
1840
2065
|
config;
|
|
1841
|
-
compiler = new
|
|
2066
|
+
compiler = new import_deep_memory6.GremlinCompiler();
|
|
1842
2067
|
reportUsage;
|
|
1843
2068
|
/**
|
|
1844
2069
|
* Per-process vocabulary cache, keyed by repositoryId. Read lazily by
|
|
@@ -1849,7 +2074,7 @@ var CosmosDbProvider = class {
|
|
|
1849
2074
|
vocabularyCache = /* @__PURE__ */ new Map();
|
|
1850
2075
|
constructor(config) {
|
|
1851
2076
|
this.config = config;
|
|
1852
|
-
this.reportUsage = (0,
|
|
2077
|
+
this.reportUsage = (0, import_deep_memory6.createSafeSink)(config.reportUsage);
|
|
1853
2078
|
this.conn = new CosmosDbConnection({
|
|
1854
2079
|
endpoint: config.endpoint,
|
|
1855
2080
|
key: config.key,
|
|
@@ -1912,8 +2137,8 @@ var CosmosDbProvider = class {
|
|
|
1912
2137
|
* ever reaching query construction.
|
|
1913
2138
|
*/
|
|
1914
2139
|
assertValidRepositoryId(repositoryId) {
|
|
1915
|
-
if (!(0,
|
|
1916
|
-
throw new
|
|
2140
|
+
if (!(0, import_deep_memory6.isValidUuid)(repositoryId)) {
|
|
2141
|
+
throw new import_deep_memory6.InvalidInputError(
|
|
1917
2142
|
"repositoryId",
|
|
1918
2143
|
`repositoryId must be a valid v4 UUID; got '${repositoryId}'`
|
|
1919
2144
|
);
|
|
@@ -1989,7 +2214,7 @@ var CosmosDbProvider = class {
|
|
|
1989
2214
|
schemaVersion: SCHEMA_VERSION
|
|
1990
2215
|
};
|
|
1991
2216
|
} catch (err) {
|
|
1992
|
-
throw new
|
|
2217
|
+
throw new import_deep_memory6.ProviderError(
|
|
1993
2218
|
`Failed to ensure CosmosDB schema: ${err instanceof Error ? err.message : String(err)}`,
|
|
1994
2219
|
"Verify the CosmosDB REST endpoint is accessible and the Gremlin endpoint is reachable."
|
|
1995
2220
|
);
|
|
@@ -2406,7 +2631,7 @@ Verify the ARM/Bicep template that provisioned the container.`
|
|
|
2406
2631
|
}
|
|
2407
2632
|
const layer = {};
|
|
2408
2633
|
const nextFrontier = /* @__PURE__ */ new Set();
|
|
2409
|
-
const
|
|
2634
|
+
const layerBucketSeen = /* @__PURE__ */ new Map();
|
|
2410
2635
|
for (const fv of frontier) {
|
|
2411
2636
|
const incident = edgesByVertex.get(fv) ?? [];
|
|
2412
2637
|
for (const rel of incident) {
|
|
@@ -2432,17 +2657,21 @@ Verify the ARM/Bicep template that provisioned the container.`
|
|
|
2432
2657
|
if (!matchesDirection || !connectedId) continue;
|
|
2433
2658
|
if (visited.has(connectedId)) continue;
|
|
2434
2659
|
if (options.relationshipPropertyFilters && options.relationshipPropertyFilters.length > 0) {
|
|
2435
|
-
if (!(0,
|
|
2660
|
+
if (!(0, import_deep_memory6.matchesPropertyFilters)(rel.properties, options.relationshipPropertyFilters)) continue;
|
|
2436
2661
|
}
|
|
2437
2662
|
const connectedEntity = raw.entityMap.get(connectedId);
|
|
2438
2663
|
if (!connectedEntity) continue;
|
|
2439
2664
|
if (options.entityTypes && options.entityTypes.length > 0 && !options.entityTypes.includes(connectedEntity.entityType)) {
|
|
2440
2665
|
continue;
|
|
2441
2666
|
}
|
|
2442
|
-
const edgeKey = `${rel.id}|${fv}->${connectedId}`;
|
|
2443
|
-
if (layerEdgeSeen.has(edgeKey)) continue;
|
|
2444
|
-
layerEdgeSeen.add(edgeKey);
|
|
2445
2667
|
const relType = rel.relationshipType;
|
|
2668
|
+
let bucketSeen = layerBucketSeen.get(relType);
|
|
2669
|
+
if (!bucketSeen) {
|
|
2670
|
+
bucketSeen = /* @__PURE__ */ new Set();
|
|
2671
|
+
layerBucketSeen.set(relType, bucketSeen);
|
|
2672
|
+
}
|
|
2673
|
+
if (bucketSeen.has(connectedId)) continue;
|
|
2674
|
+
bucketSeen.add(connectedId);
|
|
2446
2675
|
if (!layer[relType]) {
|
|
2447
2676
|
layer[relType] = { total: 0, entities: [], relationships: [] };
|
|
2448
2677
|
}
|
|
@@ -2660,7 +2889,7 @@ Verify the ARM/Bicep template that provisioned the container.`
|
|
|
2660
2889
|
const raw = await this.executeTraversal(repositoryId, spec);
|
|
2661
2890
|
const detailLevel = spec.detailLevel ?? "summary";
|
|
2662
2891
|
const projectStoredEntity = (stored) => {
|
|
2663
|
-
const projected = (0,
|
|
2892
|
+
const projected = (0, import_deep_memory6.projectEntity)(stored, detailLevel);
|
|
2664
2893
|
if (!spec.includeProvenance) {
|
|
2665
2894
|
delete projected["provenance"];
|
|
2666
2895
|
}
|
|
@@ -2677,8 +2906,11 @@ Verify the ARM/Bicep template that provisioned the container.`
|
|
|
2677
2906
|
let entities = [];
|
|
2678
2907
|
let relationships;
|
|
2679
2908
|
let paths;
|
|
2909
|
+
const aggregations = raw.aggregations;
|
|
2910
|
+
const projectionEmitted = aggregations !== void 0;
|
|
2680
2911
|
const rangeRowCount = spec.returnMode === "all" ? raw.allEntities.length + raw.allRelationships.length : 0;
|
|
2681
|
-
if (
|
|
2912
|
+
if (projectionEmitted) {
|
|
2913
|
+
} else if (spec.returnMode === "terminal") {
|
|
2682
2914
|
entities = raw.terminalEntities.map(projectStoredEntity);
|
|
2683
2915
|
relationships = void 0;
|
|
2684
2916
|
} else if (spec.returnMode === "all") {
|
|
@@ -2706,7 +2938,7 @@ Verify the ARM/Bicep template that provisioned the container.`
|
|
|
2706
2938
|
entities: row.entityIds.map((id) => {
|
|
2707
2939
|
const stored = raw.entityMap.get(id);
|
|
2708
2940
|
if (!stored) {
|
|
2709
|
-
throw new
|
|
2941
|
+
throw new import_deep_memory6.ProviderError(
|
|
2710
2942
|
"Unpacking Gremlin path: entity referenced by path is missing from the result",
|
|
2711
2943
|
"This indicates a Gremlin response shape mismatch \u2014 inspect compiledQuery."
|
|
2712
2944
|
);
|
|
@@ -2716,7 +2948,7 @@ Verify the ARM/Bicep template that provisioned the container.`
|
|
|
2716
2948
|
relationships: row.relationshipIds.map((id, i) => {
|
|
2717
2949
|
const stored = raw.relationshipMap.get(id);
|
|
2718
2950
|
if (!stored) {
|
|
2719
|
-
throw new
|
|
2951
|
+
throw new import_deep_memory6.ProviderError(
|
|
2720
2952
|
"Unpacking Gremlin path: relationship referenced by path is missing from the result",
|
|
2721
2953
|
"This indicates a Gremlin response shape mismatch \u2014 inspect compiledQuery."
|
|
2722
2954
|
);
|
|
@@ -2730,7 +2962,9 @@ Verify the ARM/Bicep template that provisioned the container.`
|
|
|
2730
2962
|
}
|
|
2731
2963
|
const limit = spec.limit ?? 50;
|
|
2732
2964
|
let total;
|
|
2733
|
-
if (
|
|
2965
|
+
if (projectionEmitted) {
|
|
2966
|
+
total = aggregations.length;
|
|
2967
|
+
} else if (spec.returnMode === "path") {
|
|
2734
2968
|
total = paths?.length ?? 0;
|
|
2735
2969
|
} else if (spec.returnMode === "all") {
|
|
2736
2970
|
total = entities.length + (relationships?.length ?? 0);
|
|
@@ -2755,6 +2989,7 @@ Verify the ARM/Bicep template that provisioned the container.`
|
|
|
2755
2989
|
entities,
|
|
2756
2990
|
relationships,
|
|
2757
2991
|
paths,
|
|
2992
|
+
...aggregations !== void 0 ? { aggregations } : {},
|
|
2758
2993
|
total,
|
|
2759
2994
|
returned: total,
|
|
2760
2995
|
hasMore: truncated,
|
|
@@ -2791,7 +3026,7 @@ Verify the ARM/Bicep template that provisioned the container.`
|
|
|
2791
3026
|
try {
|
|
2792
3027
|
result = await this.conn.submit(scopedQuery, scopedParams);
|
|
2793
3028
|
} catch (err) {
|
|
2794
|
-
throw new
|
|
3029
|
+
throw new import_deep_memory6.ProviderError(
|
|
2795
3030
|
`Gremlin traversal failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2796
3031
|
"Check the traversal spec and ensure the CosmosDB connection is healthy."
|
|
2797
3032
|
);
|
|
@@ -2809,7 +3044,14 @@ Verify the ARM/Bicep template that provisioned the container.`
|
|
|
2809
3044
|
requestCharge: result.requestCharge,
|
|
2810
3045
|
compiledQuery: scopedQuery
|
|
2811
3046
|
};
|
|
2812
|
-
|
|
3047
|
+
const emitsProjection = spec.projection !== void 0 && spec.returnMode !== "all" && spec.returnMode !== "path";
|
|
3048
|
+
if (emitsProjection) {
|
|
3049
|
+
raw.aggregations = parseProjectionRows(
|
|
3050
|
+
result.items,
|
|
3051
|
+
spec.projection.properties,
|
|
3052
|
+
spec.projection.mode ?? "values"
|
|
3053
|
+
);
|
|
3054
|
+
} else if (spec.returnMode === "terminal") {
|
|
2813
3055
|
for (const item of result.items) {
|
|
2814
3056
|
const stored = entityFromGremlin(item);
|
|
2815
3057
|
raw.terminalEntities.push(stored);
|
|
@@ -2897,6 +3139,81 @@ function unwrapGremlinValue(val) {
|
|
|
2897
3139
|
if (Array.isArray(val) && val.length > 0) return val[0];
|
|
2898
3140
|
return val;
|
|
2899
3141
|
}
|
|
3142
|
+
function parseProjectionRows(items, properties, mode) {
|
|
3143
|
+
const singleProperty = properties.length === 1;
|
|
3144
|
+
const aggregations = [];
|
|
3145
|
+
if (mode === "count") {
|
|
3146
|
+
for (const item of items) {
|
|
3147
|
+
const entry = readEntry(item);
|
|
3148
|
+
if (entry === void 0) continue;
|
|
3149
|
+
const values = singleProperty ? { [properties[0]]: normaliseProjectionScalar(entry.key) } : readProjectedMap(entry.key, properties);
|
|
3150
|
+
aggregations.push({ values, count: coerceCount(entry.value) });
|
|
3151
|
+
}
|
|
3152
|
+
return aggregations;
|
|
3153
|
+
}
|
|
3154
|
+
for (const item of items) {
|
|
3155
|
+
const values = singleProperty ? { [properties[0]]: normaliseProjectionScalar(item) } : readProjectedMap(item, properties);
|
|
3156
|
+
aggregations.push({ values });
|
|
3157
|
+
}
|
|
3158
|
+
return aggregations;
|
|
3159
|
+
}
|
|
3160
|
+
function readEntry(item) {
|
|
3161
|
+
if (item instanceof Map) {
|
|
3162
|
+
if (item.has("key") && item.has("value")) {
|
|
3163
|
+
return { key: item.get("key"), value: item.get("value") };
|
|
3164
|
+
}
|
|
3165
|
+
const first = item.entries().next();
|
|
3166
|
+
if (!first.done) {
|
|
3167
|
+
const [key, value] = first.value;
|
|
3168
|
+
return { key, value };
|
|
3169
|
+
}
|
|
3170
|
+
return void 0;
|
|
3171
|
+
}
|
|
3172
|
+
if (typeof item !== "object" || item === null) return void 0;
|
|
3173
|
+
const obj = item;
|
|
3174
|
+
if ("key" in obj && "value" in obj) {
|
|
3175
|
+
return { key: obj["key"], value: obj["value"] };
|
|
3176
|
+
}
|
|
3177
|
+
const keys = Object.keys(obj);
|
|
3178
|
+
if (keys.length === 1) {
|
|
3179
|
+
const k = keys[0];
|
|
3180
|
+
return { key: k, value: obj[k] };
|
|
3181
|
+
}
|
|
3182
|
+
return void 0;
|
|
3183
|
+
}
|
|
3184
|
+
function readProjectedMap(row, properties) {
|
|
3185
|
+
const out = {};
|
|
3186
|
+
if (row instanceof Map) {
|
|
3187
|
+
for (const prop of properties) {
|
|
3188
|
+
out[prop] = normaliseProjectionScalar(row.get(prop));
|
|
3189
|
+
}
|
|
3190
|
+
return out;
|
|
3191
|
+
}
|
|
3192
|
+
if (typeof row === "object" && row !== null) {
|
|
3193
|
+
const obj = row;
|
|
3194
|
+
for (const prop of properties) {
|
|
3195
|
+
out[prop] = normaliseProjectionScalar(obj[prop]);
|
|
3196
|
+
}
|
|
3197
|
+
return out;
|
|
3198
|
+
}
|
|
3199
|
+
for (const prop of properties) {
|
|
3200
|
+
out[prop] = void 0;
|
|
3201
|
+
}
|
|
3202
|
+
return out;
|
|
3203
|
+
}
|
|
3204
|
+
function normaliseProjectionScalar(value) {
|
|
3205
|
+
if (typeof value === "bigint") return Number(value);
|
|
3206
|
+
return value;
|
|
3207
|
+
}
|
|
3208
|
+
function coerceCount(value) {
|
|
3209
|
+
if (typeof value === "number") return value;
|
|
3210
|
+
if (typeof value === "bigint") return Number(value);
|
|
3211
|
+
if (typeof value === "string") {
|
|
3212
|
+
const n = Number(value);
|
|
3213
|
+
return Number.isFinite(n) ? n : 0;
|
|
3214
|
+
}
|
|
3215
|
+
return 0;
|
|
3216
|
+
}
|
|
2900
3217
|
function buildExploreSteps(depth, options) {
|
|
2901
3218
|
const base = { direction: "both" };
|
|
2902
3219
|
if (options.relationshipTypes && options.relationshipTypes.length > 0) {
|