@utaba/deep-memory-storage-cosmosdb 0.3.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 ADDED
@@ -0,0 +1,1630 @@
1
+ // src/CosmosDbProvider.ts
2
+ import crypto from "crypto";
3
+ import { GremlinCompiler, ProviderError } from "@utaba/deep-memory";
4
+
5
+ // src/CosmosDbConnection.ts
6
+ import gremlin from "gremlin";
7
+ var CosmosDbConnection = class {
8
+ client = null;
9
+ config;
10
+ constructor(config) {
11
+ this.config = {
12
+ ...config,
13
+ maxRetries: config.maxRetries ?? 3,
14
+ defaultTimeoutMs: config.defaultTimeoutMs ?? 3e4,
15
+ rejectUnauthorized: config.rejectUnauthorized ?? true
16
+ };
17
+ }
18
+ /** Open the WebSocket connection to CosmosDB Gremlin endpoint. */
19
+ async connect() {
20
+ if (this.client) return;
21
+ const authenticator = new gremlin.driver.auth.PlainTextSaslAuthenticator(
22
+ `/dbs/${this.config.database}/colls/${this.config.container}`,
23
+ this.config.key
24
+ );
25
+ this.client = new gremlin.driver.Client(this.config.endpoint, {
26
+ authenticator,
27
+ traversalsource: "g",
28
+ rejectUnauthorized: this.config.rejectUnauthorized,
29
+ mimeType: "application/vnd.gremlin-v2.0+json"
30
+ });
31
+ await this.client.open();
32
+ }
33
+ /** Close the WebSocket connection. */
34
+ async close() {
35
+ if (this.client) {
36
+ await this.client.close();
37
+ this.client = null;
38
+ }
39
+ }
40
+ /**
41
+ * Submit a parameterized Gremlin query with retry on transient errors.
42
+ * All user values should be passed as bindings (never interpolated into the query string).
43
+ */
44
+ async submit(query, bindings) {
45
+ if (!this.client) {
46
+ throw new Error("CosmosDbConnection: not connected. Call connect() first.");
47
+ }
48
+ let lastError;
49
+ for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
50
+ try {
51
+ const resultSet = await this.client.submit(query, bindings);
52
+ const items = resultSet.toArray();
53
+ const requestCharge = extractRequestCharge(resultSet);
54
+ return { items, requestCharge };
55
+ } catch (err) {
56
+ lastError = err;
57
+ if (isTransientError(err) && attempt < this.config.maxRetries) {
58
+ const retryAfterMs = getRetryAfterMs(err, attempt);
59
+ await sleep(retryAfterMs);
60
+ continue;
61
+ }
62
+ throw err;
63
+ }
64
+ }
65
+ throw lastError;
66
+ }
67
+ /** Get the underlying Gremlin client (for advanced usage). */
68
+ getClient() {
69
+ if (!this.client) {
70
+ throw new Error("CosmosDbConnection: not connected. Call connect() first.");
71
+ }
72
+ return this.client;
73
+ }
74
+ };
75
+ function isTransientError(err) {
76
+ if (err instanceof Error) {
77
+ const msg = err.message;
78
+ if (msg.includes("429") || msg.includes("RequestRateTooLarge")) return true;
79
+ if (msg.includes("503") || msg.includes("ServiceUnavailable")) return true;
80
+ }
81
+ const statusCode = err?.["statusCode"];
82
+ if (statusCode === 429 || statusCode === 503) return true;
83
+ return false;
84
+ }
85
+ function getRetryAfterMs(err, attempt) {
86
+ const retryAfter = err?.["retryAfterMs"];
87
+ if (typeof retryAfter === "number" && retryAfter > 0) {
88
+ return retryAfter;
89
+ }
90
+ return Math.min(500 * Math.pow(2, attempt), 1e4);
91
+ }
92
+ function extractRequestCharge(resultSet) {
93
+ const attrs = resultSet.attributes;
94
+ if (!attrs) return void 0;
95
+ const charge = attrs["x-ms-request-charge"] ?? attrs["x-ms-total-request-charge"];
96
+ return typeof charge === "number" ? charge : void 0;
97
+ }
98
+ function sleep(ms) {
99
+ return new Promise((resolve) => setTimeout(resolve, ms));
100
+ }
101
+
102
+ // src/mapping.ts
103
+ function unwrap(val) {
104
+ if (Array.isArray(val) && val.length > 0) return val[0];
105
+ return val;
106
+ }
107
+ function unwrapStr(val) {
108
+ const v = unwrap(val);
109
+ return typeof v === "string" ? v : String(v ?? "");
110
+ }
111
+ function unwrapOptStr(val) {
112
+ const v = unwrap(val);
113
+ return v != null && v !== "" ? String(v) : void 0;
114
+ }
115
+ function safeParseJson(val, fallback) {
116
+ if (val == null) return fallback;
117
+ const str = typeof val === "string" ? val : String(unwrap(val));
118
+ if (!str || str === "") return fallback;
119
+ try {
120
+ return JSON.parse(str);
121
+ } catch {
122
+ return fallback;
123
+ }
124
+ }
125
+ function provenanceFromGremlin(props) {
126
+ return {
127
+ createdBy: unwrapStr(props["createdBy"]),
128
+ createdByType: unwrapStr(props["createdByType"]) || "agent",
129
+ createdAt: unwrapStr(props["createdAt"]),
130
+ createdInConversation: unwrapOptStr(props["createdInConversation"]),
131
+ createdFromMessage: unwrapOptStr(props["createdFromMessage"]),
132
+ modifiedBy: unwrapStr(props["modifiedBy"]),
133
+ modifiedByType: unwrapStr(props["modifiedByType"]) || "agent",
134
+ modifiedAt: unwrapStr(props["modifiedAt"]),
135
+ modifiedInConversation: unwrapOptStr(props["modifiedInConversation"]),
136
+ modifiedFromMessage: unwrapOptStr(props["modifiedFromMessage"])
137
+ };
138
+ }
139
+ function entityFromGremlin(props) {
140
+ const embeddingStr = unwrapOptStr(props["embedding"]);
141
+ return {
142
+ id: unwrapStr(props["id"]),
143
+ slug: unwrapStr(props["slug"]),
144
+ entityType: unwrapStr(props["entityType"]),
145
+ label: unwrapStr(props["entityLabel"]),
146
+ summary: unwrapOptStr(props["summary"]),
147
+ properties: safeParseJson(unwrap(props["properties"]), {}),
148
+ data: unwrapOptStr(props["data"]),
149
+ dataFormat: unwrapOptStr(props["dataFormat"]),
150
+ provenance: provenanceFromGremlin(props),
151
+ embedding: embeddingStr ? safeParseJson(embeddingStr, void 0) : void 0
152
+ };
153
+ }
154
+ function relationshipFromGremlin(props) {
155
+ const bidir = unwrap(props["bidirectional"]);
156
+ return {
157
+ id: unwrapStr(props["id"]),
158
+ relationshipType: unwrapStr(props["relationshipType"]),
159
+ sourceEntityId: unwrapStr(props["sourceEntityId"]),
160
+ targetEntityId: unwrapStr(props["targetEntityId"]),
161
+ properties: safeParseJson(unwrap(props["properties"]), {}),
162
+ bidirectional: bidir === true || bidir === "true",
163
+ provenance: provenanceFromGremlin(props)
164
+ };
165
+ }
166
+ function repositoryFromGremlin(props) {
167
+ return {
168
+ repositoryId: unwrapStr(props["repositoryId"]),
169
+ type: unwrapOptStr(props["type"]),
170
+ label: unwrapStr(props["repoLabel"]),
171
+ description: unwrapOptStr(props["description"]),
172
+ legal: unwrapOptStr(props["legal"]),
173
+ owner: unwrapOptStr(props["owner"]),
174
+ governanceConfig: safeParseJson(unwrap(props["governanceConfig"]), { mode: "open" }),
175
+ metadata: safeParseJson(unwrap(props["metadata"]), void 0),
176
+ createdAt: unwrapStr(props["createdAt"]),
177
+ createdBy: unwrapStr(props["createdBy"])
178
+ };
179
+ }
180
+ function repositorySummaryFromGremlin(props) {
181
+ return {
182
+ repositoryId: unwrapStr(props["repositoryId"]),
183
+ type: unwrapOptStr(props["type"]),
184
+ label: unwrapStr(props["repoLabel"]),
185
+ description: unwrapOptStr(props["description"]),
186
+ governanceConfig: safeParseJson(unwrap(props["governanceConfig"]), { mode: "open" })
187
+ };
188
+ }
189
+ function vocabularyFromGremlin(props) {
190
+ return safeParseJson(unwrap(props["vocabulary"]), {
191
+ version: "0.0.0",
192
+ lastModified: (/* @__PURE__ */ new Date()).toISOString(),
193
+ modifiedBy: "system",
194
+ entityTypes: [],
195
+ relationshipTypes: []
196
+ });
197
+ }
198
+ function changeRecordFromGremlin(props) {
199
+ return {
200
+ changeId: unwrapStr(props["changeId"]),
201
+ changeType: unwrapStr(props["changeType"]),
202
+ typeName: unwrapStr(props["typeName"]),
203
+ previousVersion: unwrapOptStr(props["previousVersion"]),
204
+ newVersion: unwrapStr(props["newVersion"]),
205
+ proposedBy: unwrapStr(props["proposedBy"]),
206
+ proposedAt: unwrapStr(props["proposedAt"]),
207
+ approvedBy: unwrapOptStr(props["approvedBy"]),
208
+ approvedAt: unwrapOptStr(props["approvedAt"]),
209
+ reason: unwrapStr(props["reason"])
210
+ };
211
+ }
212
+ function entityToGremlinProps(repositoryId, entity) {
213
+ const props = {
214
+ repositoryId,
215
+ entityType: entity.entityType,
216
+ entityLabel: entity.label,
217
+ slug: entity.slug,
218
+ properties: JSON.stringify(entity.properties ?? {}),
219
+ createdBy: entity.provenance.createdBy,
220
+ createdByType: entity.provenance.createdByType,
221
+ createdAt: entity.provenance.createdAt,
222
+ modifiedBy: entity.provenance.modifiedBy,
223
+ modifiedByType: entity.provenance.modifiedByType,
224
+ modifiedAt: entity.provenance.modifiedAt
225
+ };
226
+ if (entity.summary != null) props["summary"] = entity.summary;
227
+ if (entity.data != null) props["data"] = entity.data;
228
+ if (entity.dataFormat != null) props["dataFormat"] = entity.dataFormat;
229
+ if (entity.provenance.createdInConversation != null) props["createdInConversation"] = entity.provenance.createdInConversation;
230
+ if (entity.provenance.createdFromMessage != null) props["createdFromMessage"] = entity.provenance.createdFromMessage;
231
+ if (entity.provenance.modifiedInConversation != null) props["modifiedInConversation"] = entity.provenance.modifiedInConversation;
232
+ if (entity.provenance.modifiedFromMessage != null) props["modifiedFromMessage"] = entity.provenance.modifiedFromMessage;
233
+ if (entity.embedding != null) props["embedding"] = JSON.stringify(entity.embedding);
234
+ return props;
235
+ }
236
+ function relationshipToGremlinProps(repositoryId, rel) {
237
+ const props = {
238
+ repositoryId,
239
+ relationshipType: rel.relationshipType,
240
+ sourceEntityId: rel.sourceEntityId,
241
+ targetEntityId: rel.targetEntityId,
242
+ bidirectional: rel.bidirectional,
243
+ properties: JSON.stringify(rel.properties ?? {}),
244
+ createdBy: rel.provenance.createdBy,
245
+ createdByType: rel.provenance.createdByType,
246
+ createdAt: rel.provenance.createdAt,
247
+ modifiedBy: rel.provenance.modifiedBy,
248
+ modifiedByType: rel.provenance.modifiedByType,
249
+ modifiedAt: rel.provenance.modifiedAt
250
+ };
251
+ if (rel.provenance.createdInConversation != null) props["createdInConversation"] = rel.provenance.createdInConversation;
252
+ if (rel.provenance.createdFromMessage != null) props["createdFromMessage"] = rel.provenance.createdFromMessage;
253
+ if (rel.provenance.modifiedInConversation != null) props["modifiedInConversation"] = rel.provenance.modifiedInConversation;
254
+ if (rel.provenance.modifiedFromMessage != null) props["modifiedFromMessage"] = rel.provenance.modifiedFromMessage;
255
+ return props;
256
+ }
257
+
258
+ // src/queries/repository.ts
259
+ import { DuplicateRepositoryError, RepositoryNotFoundError } from "@utaba/deep-memory";
260
+ var REPO_LABEL = "_repository";
261
+ function repoVertexId(repositoryId) {
262
+ return `repo:${repositoryId}`;
263
+ }
264
+ function propertyChain(bindings, props, startIndex) {
265
+ const parts = [];
266
+ let idx = startIndex;
267
+ for (const [key, value] of Object.entries(props)) {
268
+ if (value == null) continue;
269
+ const paramName = `p${idx++}`;
270
+ bindings[paramName] = value;
271
+ parts.push(`.property('${key}', ${paramName})`);
272
+ }
273
+ return { chain: parts.join(""), nextIndex: idx };
274
+ }
275
+ async function createRepository(conn, config) {
276
+ const vertexId = repoVertexId(config.repositoryId);
277
+ const existing = await conn.submit(
278
+ "g.V().has('id', vid).has('label', lbl).count()",
279
+ { vid: vertexId, lbl: REPO_LABEL }
280
+ );
281
+ if (existing.items.length > 0 && Number(existing.items[0]) > 0) {
282
+ throw new DuplicateRepositoryError(config.repositoryId);
283
+ }
284
+ const bindings = {
285
+ vid: vertexId,
286
+ rid: config.repositoryId
287
+ };
288
+ const props = {
289
+ repoLabel: config.label,
290
+ description: config.description,
291
+ type: config.type,
292
+ legal: config.legal,
293
+ owner: config.owner,
294
+ governanceConfig: JSON.stringify(config.governanceConfig),
295
+ metadata: config.metadata ? JSON.stringify(config.metadata) : void 0,
296
+ createdAt: config.createdAt,
297
+ createdBy: config.createdBy
298
+ };
299
+ const { chain } = propertyChain(bindings, props, 0);
300
+ const query = `g.addV('${REPO_LABEL}').property('id', vid).property('repositoryId', rid)${chain}`;
301
+ await conn.submit(query, bindings);
302
+ return {
303
+ repositoryId: config.repositoryId,
304
+ type: config.type,
305
+ label: config.label,
306
+ description: config.description,
307
+ legal: config.legal,
308
+ owner: config.owner,
309
+ governanceConfig: config.governanceConfig,
310
+ metadata: config.metadata,
311
+ createdAt: config.createdAt,
312
+ createdBy: config.createdBy
313
+ };
314
+ }
315
+ async function getRepository(conn, repositoryId) {
316
+ const result = await conn.submit(
317
+ "g.V().has('id', vid).hasLabel('_repository').valueMap(true)",
318
+ { vid: repoVertexId(repositoryId) }
319
+ );
320
+ if (result.items.length === 0) return null;
321
+ return repositoryFromGremlin(result.items[0]);
322
+ }
323
+ async function listRepositories(conn, filter) {
324
+ const limit = filter?.limit ?? 20;
325
+ const offset = filter?.offset ?? 0;
326
+ let countQuery = "g.V().hasLabel('_repository')";
327
+ let dataQuery = "g.V().hasLabel('_repository')";
328
+ const bindings = {};
329
+ if (filter?.type) {
330
+ countQuery += ".has('type', filterType)";
331
+ dataQuery += ".has('type', filterType)";
332
+ bindings["filterType"] = filter.type;
333
+ }
334
+ const countResult = await conn.submit(`${countQuery}.count()`, bindings);
335
+ const total = Number(countResult.items[0] ?? 0);
336
+ bindings["rangeStart"] = offset;
337
+ bindings["rangeEnd"] = offset + limit;
338
+ const dataResult = await conn.submit(
339
+ `${dataQuery}.range(rangeStart, rangeEnd).valueMap(true)`,
340
+ bindings
341
+ );
342
+ const items = dataResult.items.map(repositorySummaryFromGremlin);
343
+ return {
344
+ items,
345
+ total,
346
+ hasMore: offset + limit < total,
347
+ limit,
348
+ offset
349
+ };
350
+ }
351
+ async function updateRepository(conn, repositoryId, updates) {
352
+ const vertexId = repoVertexId(repositoryId);
353
+ const existing = await getRepository(conn, repositoryId);
354
+ if (!existing) throw new RepositoryNotFoundError(repositoryId);
355
+ const bindings = { vid: vertexId };
356
+ const props = {};
357
+ if (updates.label !== void 0) props["repoLabel"] = updates.label;
358
+ if (updates.description !== void 0) props["description"] = updates.description;
359
+ if (updates.type !== void 0) props["type"] = updates.type;
360
+ if (updates.legal !== void 0) props["legal"] = updates.legal;
361
+ if (updates.owner !== void 0) props["owner"] = updates.owner;
362
+ if (updates.governanceConfig !== void 0) props["governanceConfig"] = JSON.stringify(updates.governanceConfig);
363
+ if (updates.metadata !== void 0) {
364
+ const merged = { ...existing.metadata, ...updates.metadata };
365
+ props["metadata"] = JSON.stringify(merged);
366
+ }
367
+ if (Object.keys(props).length === 0) return existing;
368
+ const { chain } = propertyChain(bindings, props, 0);
369
+ const query = `g.V().has('id', vid).hasLabel('_repository')${chain}`;
370
+ await conn.submit(query, bindings);
371
+ return await getRepository(conn, repositoryId);
372
+ }
373
+ var DELETE_BATCH_SIZE = 500;
374
+ async function deleteRepository(conn, repositoryId, onProgress) {
375
+ const entityCountResult = await conn.submit(
376
+ "g.V().has('repositoryId', rid).has('entityType').count()",
377
+ { rid: repositoryId }
378
+ );
379
+ const totalEntities = Number(entityCountResult.items[0] ?? 0);
380
+ const relCountResult = await conn.submit(
381
+ "g.E().has('repositoryId', rid).count()",
382
+ { rid: repositoryId }
383
+ );
384
+ const totalRelationships = Number(relCountResult.items[0] ?? 0);
385
+ let relationshipsDeleted = 0;
386
+ let entitiesDeleted = 0;
387
+ while (true) {
388
+ await conn.submit(
389
+ "g.E().has('repositoryId', rid).limit(batchSize).drop()",
390
+ { rid: repositoryId, batchSize: DELETE_BATCH_SIZE }
391
+ );
392
+ const remaining = await conn.submit(
393
+ "g.E().has('repositoryId', rid).limit(1).count()",
394
+ { rid: repositoryId }
395
+ );
396
+ const remainingCount = Number(remaining.items[0] ?? 0);
397
+ relationshipsDeleted = totalRelationships - remainingCount;
398
+ await onProgress?.({ entitiesDeleted, relationshipsDeleted, totalEntities, totalRelationships });
399
+ if (remainingCount === 0) break;
400
+ }
401
+ while (true) {
402
+ await conn.submit(
403
+ "g.V().has('repositoryId', rid).limit(batchSize).drop()",
404
+ { rid: repositoryId, batchSize: DELETE_BATCH_SIZE }
405
+ );
406
+ const remaining = await conn.submit(
407
+ "g.V().has('repositoryId', rid).limit(1).count()",
408
+ { rid: repositoryId }
409
+ );
410
+ const remainingCount = Number(remaining.items[0] ?? 0);
411
+ entitiesDeleted = totalEntities - remainingCount;
412
+ await onProgress?.({ entitiesDeleted, relationshipsDeleted, totalEntities, totalRelationships });
413
+ if (remainingCount === 0) break;
414
+ }
415
+ }
416
+ async function deleteAllContents(conn, repositoryId, onProgress) {
417
+ const entityCountResult = await conn.submit(
418
+ "g.V().has('repositoryId', rid).has('entityType').count()",
419
+ { rid: repositoryId }
420
+ );
421
+ const totalEntities = Number(entityCountResult.items[0] ?? 0);
422
+ const relCountResult = await conn.submit(
423
+ "g.E().has('repositoryId', rid).count()",
424
+ { rid: repositoryId }
425
+ );
426
+ const totalRelationships = Number(relCountResult.items[0] ?? 0);
427
+ let relationshipsDeleted = 0;
428
+ let entitiesDeleted = 0;
429
+ while (true) {
430
+ await conn.submit(
431
+ "g.E().has('repositoryId', rid).limit(batchSize).drop()",
432
+ { rid: repositoryId, batchSize: DELETE_BATCH_SIZE }
433
+ );
434
+ const remaining = await conn.submit(
435
+ "g.E().has('repositoryId', rid).limit(1).count()",
436
+ { rid: repositoryId }
437
+ );
438
+ const remainingCount = Number(remaining.items[0] ?? 0);
439
+ relationshipsDeleted = totalRelationships - remainingCount;
440
+ await onProgress?.({ entitiesDeleted, relationshipsDeleted, totalEntities, totalRelationships });
441
+ if (remainingCount === 0) break;
442
+ }
443
+ while (true) {
444
+ await conn.submit(
445
+ "g.V().has('repositoryId', rid).has('entityType').limit(batchSize).drop()",
446
+ { rid: repositoryId, batchSize: DELETE_BATCH_SIZE }
447
+ );
448
+ const remaining = await conn.submit(
449
+ "g.V().has('repositoryId', rid).has('entityType').limit(1).count()",
450
+ { rid: repositoryId }
451
+ );
452
+ const remainingCount = Number(remaining.items[0] ?? 0);
453
+ entitiesDeleted = totalEntities - remainingCount;
454
+ await onProgress?.({ entitiesDeleted, relationshipsDeleted, totalEntities, totalRelationships });
455
+ if (remainingCount === 0) break;
456
+ }
457
+ return { deletedEntities: totalEntities, deletedRelationships: totalRelationships };
458
+ }
459
+ async function getRepositoryStats(conn, repositoryId) {
460
+ const vocabResult = await conn.submit(
461
+ "g.V().has('repositoryId', rid).hasLabel('_vocabulary').values('vocabulary')",
462
+ { rid: repositoryId }
463
+ );
464
+ let vocabVersion = "0.0.0";
465
+ if (vocabResult.items.length > 0) {
466
+ try {
467
+ const vocab = JSON.parse(vocabResult.items[0]);
468
+ vocabVersion = vocab.version ?? "0.0.0";
469
+ } catch {
470
+ }
471
+ }
472
+ const entityResult = await conn.submit(
473
+ "g.V().has('repositoryId', rid).has('entityType').group().by('entityType').by(count())",
474
+ { rid: repositoryId }
475
+ );
476
+ const entityTypeBreakdown = {};
477
+ let entityCount = 0;
478
+ if (entityResult.items.length > 0) {
479
+ const grouped = entityResult.items[0];
480
+ for (const [type, count] of Object.entries(grouped)) {
481
+ entityTypeBreakdown[type] = Number(count);
482
+ entityCount += Number(count);
483
+ }
484
+ }
485
+ const relResult = await conn.submit(
486
+ "g.E().has('repositoryId', rid).group().by('relationshipType').by(count())",
487
+ { rid: repositoryId }
488
+ );
489
+ const relationshipTypeBreakdown = {};
490
+ let relationshipCount = 0;
491
+ if (relResult.items.length > 0) {
492
+ const grouped = relResult.items[0];
493
+ for (const [type, count] of Object.entries(grouped)) {
494
+ relationshipTypeBreakdown[type] = Number(count);
495
+ relationshipCount += Number(count);
496
+ }
497
+ }
498
+ return {
499
+ entityCount,
500
+ relationshipCount,
501
+ vocabularyVersion: vocabVersion,
502
+ entityTypeBreakdown,
503
+ relationshipTypeBreakdown
504
+ };
505
+ }
506
+
507
+ // src/queries/vocabulary.ts
508
+ function vocabVertexId(repositoryId) {
509
+ return `vocab:${repositoryId}`;
510
+ }
511
+ async function getVocabulary(conn, repositoryId) {
512
+ const result = await conn.submit(
513
+ "g.V().has('id', vid).hasLabel('_vocabulary').valueMap(true)",
514
+ { vid: vocabVertexId(repositoryId) }
515
+ );
516
+ if (result.items.length === 0) {
517
+ return {
518
+ version: "0.0.0",
519
+ lastModified: (/* @__PURE__ */ new Date()).toISOString(),
520
+ modifiedBy: "system",
521
+ entityTypes: [],
522
+ relationshipTypes: []
523
+ };
524
+ }
525
+ return vocabularyFromGremlin(result.items[0]);
526
+ }
527
+ async function saveVocabulary(conn, repositoryId, vocabulary) {
528
+ const vid = vocabVertexId(repositoryId);
529
+ const vocabJson = JSON.stringify(vocabulary);
530
+ const existing = await conn.submit(
531
+ "g.V().has('id', vid).hasLabel('_vocabulary').count()",
532
+ { vid }
533
+ );
534
+ if (Number(existing.items[0] ?? 0) > 0) {
535
+ await conn.submit(
536
+ "g.V().has('id', vid).hasLabel('_vocabulary').property('vocabulary', vocabJson)",
537
+ { vid, vocabJson }
538
+ );
539
+ } else {
540
+ await conn.submit(
541
+ "g.addV('_vocabulary').property('id', vid).property('repositoryId', rid).property('vocabulary', vocabJson)",
542
+ { vid, rid: repositoryId, vocabJson }
543
+ );
544
+ }
545
+ }
546
+ async function getVocabularyChangeLog(conn, repositoryId, options) {
547
+ const limit = options?.limit ?? 10;
548
+ const offset = options?.offset ?? 0;
549
+ const countResult = await conn.submit(
550
+ "g.V().has('repositoryId', rid).hasLabel('_vocabularyChangeLog').count()",
551
+ { rid: repositoryId }
552
+ );
553
+ const total = Number(countResult.items[0] ?? 0);
554
+ const dataResult = await conn.submit(
555
+ "g.V().has('repositoryId', rid).hasLabel('_vocabularyChangeLog').order().by('proposedAt', decr).range(rangeStart, rangeEnd).valueMap(true)",
556
+ { rid: repositoryId, rangeStart: offset, rangeEnd: offset + limit }
557
+ );
558
+ const items = dataResult.items.map(changeRecordFromGremlin);
559
+ return {
560
+ items,
561
+ total,
562
+ hasMore: offset + limit < total,
563
+ limit,
564
+ offset
565
+ };
566
+ }
567
+
568
+ // src/queries/entity.ts
569
+ import { DuplicateEntityError } from "@utaba/deep-memory";
570
+ async function createEntity(conn, repositoryId, entity) {
571
+ const existing = await conn.submit(
572
+ "g.V().has('repositoryId', rid).has('id', eid).count()",
573
+ { rid: repositoryId, eid: entity.id }
574
+ );
575
+ if (Number(existing.items[0] ?? 0) > 0) {
576
+ throw new DuplicateEntityError(entity.id);
577
+ }
578
+ const props = entityToGremlinProps(repositoryId, entity);
579
+ const bindings = { vid: entity.id };
580
+ const propParts = [];
581
+ let idx = 0;
582
+ for (const [key, value] of Object.entries(props)) {
583
+ const paramName = `p${idx++}`;
584
+ bindings[paramName] = value;
585
+ propParts.push(`.property('${key}', ${paramName})`);
586
+ }
587
+ bindings["vertexLabel"] = entity.entityType;
588
+ const query = `g.addV(vertexLabel).property('id', vid)${propParts.join("")}`;
589
+ await conn.submit(query, bindings);
590
+ return entity;
591
+ }
592
+ async function getEntity(conn, repositoryId, entityId) {
593
+ const result = await conn.submit(
594
+ "g.V().has('repositoryId', rid).has('id', eid).has('entityType').valueMap(true)",
595
+ { rid: repositoryId, eid: entityId }
596
+ );
597
+ if (result.items.length === 0) return null;
598
+ return entityFromGremlin(result.items[0]);
599
+ }
600
+ async function getEntityBySlug(conn, repositoryId, slug) {
601
+ const result = await conn.submit(
602
+ "g.V().has('repositoryId', rid).has('slug', slugVal).has('entityType').valueMap(true)",
603
+ { rid: repositoryId, slugVal: slug }
604
+ );
605
+ if (result.items.length === 0) return null;
606
+ return entityFromGremlin(result.items[0]);
607
+ }
608
+ async function getEntities(conn, repositoryId, entityIds) {
609
+ if (entityIds.length === 0) return /* @__PURE__ */ new Map();
610
+ const bindings = { rid: repositoryId };
611
+ const idParams = [];
612
+ entityIds.forEach((id, i) => {
613
+ const paramName = `eid${i}`;
614
+ bindings[paramName] = id;
615
+ idParams.push(paramName);
616
+ });
617
+ const withinClause = `within(${idParams.join(", ")})`;
618
+ const result = await conn.submit(
619
+ `g.V().has('repositoryId', rid).has('id', ${withinClause}).has('entityType').valueMap(true)`,
620
+ bindings
621
+ );
622
+ const map = /* @__PURE__ */ new Map();
623
+ for (const item of result.items) {
624
+ const entity = entityFromGremlin(item);
625
+ map.set(entity.id, entity);
626
+ }
627
+ return map;
628
+ }
629
+ async function updateEntity(conn, repositoryId, entityId, updates) {
630
+ const bindings = { rid: repositoryId, eid: entityId };
631
+ const propParts = [];
632
+ let idx = 0;
633
+ const addProp = (key, value) => {
634
+ const paramName = `p${idx++}`;
635
+ bindings[paramName] = value;
636
+ propParts.push(`.property('${key}', ${paramName})`);
637
+ };
638
+ if (updates.label !== void 0) addProp("entityLabel", updates.label);
639
+ if (updates.slug !== void 0) addProp("slug", updates.slug);
640
+ if (updates.summary !== void 0) addProp("summary", updates.summary);
641
+ if (updates.properties !== void 0) addProp("properties", JSON.stringify(updates.properties));
642
+ if (updates.data !== void 0) addProp("data", updates.data);
643
+ if (updates.dataFormat !== void 0) addProp("dataFormat", updates.dataFormat);
644
+ if (updates.embedding !== void 0) addProp("embedding", JSON.stringify(updates.embedding));
645
+ addProp("modifiedBy", updates.provenance.modifiedBy);
646
+ addProp("modifiedByType", updates.provenance.modifiedByType);
647
+ addProp("modifiedAt", updates.provenance.modifiedAt);
648
+ if (updates.provenance.modifiedInConversation != null) addProp("modifiedInConversation", updates.provenance.modifiedInConversation);
649
+ if (updates.provenance.modifiedFromMessage != null) addProp("modifiedFromMessage", updates.provenance.modifiedFromMessage);
650
+ const query = `g.V().has('repositoryId', rid).has('id', eid).has('entityType')${propParts.join("")}`;
651
+ await conn.submit(query, bindings);
652
+ return await getEntity(conn, repositoryId, entityId);
653
+ }
654
+ async function deleteEntity(conn, repositoryId, entityId) {
655
+ await conn.submit(
656
+ "g.V().has('repositoryId', rid).has('id', eid).has('entityType').drop()",
657
+ { rid: repositoryId, eid: entityId }
658
+ );
659
+ }
660
+ async function deleteEntitiesByType(conn, repositoryId, entityType) {
661
+ const entityCountResult = await conn.submit(
662
+ "g.V().has('repositoryId', rid).has('entityType', etype).count()",
663
+ { rid: repositoryId, etype: entityType }
664
+ );
665
+ const deletedEntities = Number(entityCountResult.items[0] ?? 0);
666
+ const relCountResult = await conn.submit(
667
+ "g.V().has('repositoryId', rid).has('entityType', etype).bothE().dedup().count()",
668
+ { rid: repositoryId, etype: entityType }
669
+ );
670
+ const deletedRelationships = Number(relCountResult.items[0] ?? 0);
671
+ if (deletedEntities > 0) {
672
+ await conn.submit(
673
+ "g.V().has('repositoryId', rid).has('entityType', etype).drop()",
674
+ { rid: repositoryId, etype: entityType }
675
+ );
676
+ }
677
+ return { deletedEntities, deletedRelationships };
678
+ }
679
+ async function findEntities(conn, repositoryId, query) {
680
+ const bindings = { rid: repositoryId };
681
+ let filterClause = ".has('repositoryId', rid).has('entityType')";
682
+ if (query.entityTypes && query.entityTypes.length > 0) {
683
+ const typeParams = [];
684
+ query.entityTypes.forEach((t, i) => {
685
+ const paramName = `etype${i}`;
686
+ bindings[paramName] = t;
687
+ typeParams.push(paramName);
688
+ });
689
+ filterClause += `.has('entityType', within(${typeParams.join(", ")}))`;
690
+ }
691
+ if (query.searchTerm) {
692
+ bindings["searchTerm"] = query.searchTerm.toLowerCase();
693
+ filterClause += ".has('slug', containing(searchTerm))";
694
+ }
695
+ const countResult = await conn.submit(
696
+ `g.V()${filterClause}.count()`,
697
+ bindings
698
+ );
699
+ const total = Number(countResult.items[0] ?? 0);
700
+ bindings["rangeStart"] = query.offset;
701
+ bindings["rangeEnd"] = query.offset + query.limit;
702
+ const dataResult = await conn.submit(
703
+ `g.V()${filterClause}.range(rangeStart, rangeEnd).valueMap(true)`,
704
+ bindings
705
+ );
706
+ let items = dataResult.items.map(entityFromGremlin);
707
+ if (query.properties && Object.keys(query.properties).length > 0) {
708
+ items = items.filter((entity) => {
709
+ for (const [key, value] of Object.entries(query.properties)) {
710
+ if (entity.properties[key] !== value) return false;
711
+ }
712
+ return true;
713
+ });
714
+ }
715
+ return {
716
+ items,
717
+ total,
718
+ hasMore: query.offset + query.limit < total,
719
+ limit: query.limit,
720
+ offset: query.offset
721
+ };
722
+ }
723
+
724
+ // src/queries/relationship.ts
725
+ import { DuplicateRelationshipError, matchesPropertyFilters } from "@utaba/deep-memory";
726
+ async function createRelationship(conn, repositoryId, relationship) {
727
+ const existing = await conn.submit(
728
+ "g.E().has('id', relId).count()",
729
+ { relId: relationship.id }
730
+ );
731
+ if (Number(existing.items[0] ?? 0) > 0) {
732
+ throw new DuplicateRelationshipError(relationship.id);
733
+ }
734
+ const props = relationshipToGremlinProps(repositoryId, relationship);
735
+ const bindings = {
736
+ relId: relationship.id,
737
+ srcId: relationship.sourceEntityId,
738
+ tgtId: relationship.targetEntityId,
739
+ rid: repositoryId,
740
+ edgeLabel: relationship.relationshipType
741
+ };
742
+ const propParts = [];
743
+ let idx = 0;
744
+ for (const [key, value] of Object.entries(props)) {
745
+ const paramName = `p${idx++}`;
746
+ bindings[paramName] = value;
747
+ propParts.push(`.property('${key}', ${paramName})`);
748
+ }
749
+ const query = `g.V().has('repositoryId', rid).has('id', srcId).has('entityType').addE(edgeLabel).to(g.V().has('repositoryId', rid).has('id', tgtId).has('entityType')).property('id', relId)${propParts.join("")}`;
750
+ await conn.submit(query, bindings);
751
+ return relationship;
752
+ }
753
+ async function getRelationship(conn, repositoryId, relationshipId) {
754
+ const result = await conn.submit(
755
+ "g.E().has('id', relId).has('repositoryId', rid).valueMap(true)",
756
+ { relId: relationshipId, rid: repositoryId }
757
+ );
758
+ if (result.items.length === 0) return null;
759
+ return relationshipFromGremlin(result.items[0]);
760
+ }
761
+ async function getEntityRelationships(conn, repositoryId, entityId, options) {
762
+ const limit = options?.limit ?? 50;
763
+ const offset = options?.offset ?? 0;
764
+ const direction = options?.direction ?? "both";
765
+ const bindings = {
766
+ rid: repositoryId,
767
+ eid: entityId
768
+ };
769
+ let edgeTraversal;
770
+ switch (direction) {
771
+ case "outbound":
772
+ edgeTraversal = "g.V().has('repositoryId', rid).has('id', eid).has('entityType').outE()";
773
+ break;
774
+ case "inbound":
775
+ edgeTraversal = "g.V().has('repositoryId', rid).has('id', eid).has('entityType').inE()";
776
+ break;
777
+ case "both":
778
+ default:
779
+ edgeTraversal = "g.V().has('repositoryId', rid).has('id', eid).has('entityType').bothE()";
780
+ break;
781
+ }
782
+ let typeFilter = "";
783
+ if (options?.relationshipTypes && options.relationshipTypes.length > 0) {
784
+ const typeParams = [];
785
+ options.relationshipTypes.forEach((t, i) => {
786
+ const paramName = `rtype${i}`;
787
+ bindings[paramName] = t;
788
+ typeParams.push(paramName);
789
+ });
790
+ typeFilter = `.hasLabel(${typeParams.join(", ")})`;
791
+ }
792
+ let unionQuery = null;
793
+ if (direction === "outbound") {
794
+ unionQuery = `g.V().has('repositoryId', rid).has('id', eid).has('entityType').union(outE()${typeFilter}, inE()${typeFilter}.has('bidirectional', true))`;
795
+ } else if (direction === "inbound") {
796
+ unionQuery = `g.V().has('repositoryId', rid).has('id', eid).has('entityType').union(inE()${typeFilter}, outE()${typeFilter}.has('bidirectional', true))`;
797
+ }
798
+ const baseQuery = unionQuery ?? `${edgeTraversal}${typeFilter}`;
799
+ const countResult = await conn.submit(`${baseQuery}.dedup().count()`, bindings);
800
+ const total = Number(countResult.items[0] ?? 0);
801
+ bindings["rangeStart"] = offset;
802
+ bindings["rangeEnd"] = offset + limit;
803
+ const dataResult = await conn.submit(
804
+ `${baseQuery}.dedup().range(rangeStart, rangeEnd).valueMap(true)`,
805
+ bindings
806
+ );
807
+ let items = dataResult.items.map(relationshipFromGremlin);
808
+ if (options?.propertyFilters && options.propertyFilters.length > 0) {
809
+ items = items.filter((rel) => matchesPropertyFilters(rel.properties, options.propertyFilters));
810
+ }
811
+ return {
812
+ items,
813
+ total,
814
+ hasMore: offset + limit < total,
815
+ limit,
816
+ offset
817
+ };
818
+ }
819
+ async function deleteRelationship(conn, repositoryId, relationshipId) {
820
+ await conn.submit(
821
+ "g.E().has('id', relId).has('repositoryId', rid).drop()",
822
+ { relId: relationshipId, rid: repositoryId }
823
+ );
824
+ }
825
+ async function deleteRelationshipsByType(conn, repositoryId, relationshipType) {
826
+ const countResult = await conn.submit(
827
+ "g.E().has('repositoryId', rid).hasLabel(rtype).count()",
828
+ { rid: repositoryId, rtype: relationshipType }
829
+ );
830
+ const deletedRelationships = Number(countResult.items[0] ?? 0);
831
+ if (deletedRelationships > 0) {
832
+ await conn.submit(
833
+ "g.E().has('repositoryId', rid).hasLabel(rtype).drop()",
834
+ { rid: repositoryId, rtype: relationshipType }
835
+ );
836
+ }
837
+ return { deletedRelationships };
838
+ }
839
+
840
+ // src/queries/traversal.ts
841
+ import { matchesPropertyFilters as matchesPropertyFilters2 } from "@utaba/deep-memory";
842
+ async function exploreNeighbourhood(conn, repositoryId, entityId, options) {
843
+ const layers = [];
844
+ let frontier = /* @__PURE__ */ new Set([entityId]);
845
+ const visited = /* @__PURE__ */ new Set([entityId]);
846
+ for (let depth = 0; depth < options.depth; depth++) {
847
+ const layer = {};
848
+ const nextFrontier = /* @__PURE__ */ new Set();
849
+ for (const currentId of frontier) {
850
+ const rels = await getRelationshipsForEntity(conn, repositoryId, currentId, options);
851
+ for (const rel of rels) {
852
+ const relType = rel.relationshipType;
853
+ if (!layer[relType]) {
854
+ layer[relType] = { total: 0, entities: [], relationships: [] };
855
+ }
856
+ let connectedId;
857
+ if (rel.sourceEntityId === currentId) {
858
+ connectedId = rel.targetEntityId;
859
+ } else {
860
+ connectedId = rel.sourceEntityId;
861
+ }
862
+ if (!visited.has(connectedId)) {
863
+ if (options.entityTypes && options.entityTypes.length > 0) {
864
+ const entity = await getEntityLight(conn, repositoryId, connectedId);
865
+ if (!entity || !options.entityTypes.includes(entity.entityType)) continue;
866
+ layer[relType].entities.push(entity);
867
+ } else {
868
+ const entity = await getEntityLight(conn, repositoryId, connectedId);
869
+ if (entity) {
870
+ layer[relType].entities.push(entity);
871
+ }
872
+ }
873
+ visited.add(connectedId);
874
+ nextFrontier.add(connectedId);
875
+ }
876
+ layer[relType].relationships.push(rel);
877
+ layer[relType].total = layer[relType].entities.length;
878
+ }
879
+ }
880
+ for (const relType of Object.keys(layer)) {
881
+ const group = layer[relType];
882
+ const start = options.offsetPerType;
883
+ const end = start + options.limitPerType;
884
+ group.entities = group.entities.slice(start, end);
885
+ group.relationships = group.relationships.slice(start, end);
886
+ }
887
+ if (Object.keys(layer).length > 0) {
888
+ layers.push(layer);
889
+ }
890
+ frontier = nextFrontier;
891
+ if (frontier.size === 0) break;
892
+ }
893
+ return { centreId: entityId, layers };
894
+ }
895
+ async function getRelationshipsForEntity(conn, repositoryId, entityId, options) {
896
+ const bindings = { rid: repositoryId, eid: entityId };
897
+ let edgeTraversal;
898
+ switch (options.direction) {
899
+ case "outbound":
900
+ edgeTraversal = "g.V().has('repositoryId', rid).has('id', eid).has('entityType').union(outE(), inE().has('bidirectional', true))";
901
+ break;
902
+ case "inbound":
903
+ edgeTraversal = "g.V().has('repositoryId', rid).has('id', eid).has('entityType').union(inE(), outE().has('bidirectional', true))";
904
+ break;
905
+ case "both":
906
+ default:
907
+ edgeTraversal = "g.V().has('repositoryId', rid).has('id', eid).has('entityType').bothE()";
908
+ break;
909
+ }
910
+ let typeFilter = "";
911
+ if (options.relationshipTypes && options.relationshipTypes.length > 0) {
912
+ const typeParams = [];
913
+ options.relationshipTypes.forEach((t, i) => {
914
+ const paramName = `rtype${i}`;
915
+ bindings[paramName] = t;
916
+ typeParams.push(paramName);
917
+ });
918
+ typeFilter = `.hasLabel(${typeParams.join(", ")})`;
919
+ }
920
+ const result = await conn.submit(
921
+ `${edgeTraversal}${typeFilter}.dedup().valueMap(true)`,
922
+ bindings
923
+ );
924
+ let rels = result.items.map(relationshipFromGremlin);
925
+ if (options.relationshipPropertyFilters && options.relationshipPropertyFilters.length > 0) {
926
+ rels = rels.filter((rel) => matchesPropertyFilters2(rel.properties, options.relationshipPropertyFilters));
927
+ }
928
+ return rels;
929
+ }
930
+ async function getEntityLight(conn, repositoryId, entityId) {
931
+ const result = await conn.submit(
932
+ "g.V().has('repositoryId', rid).has('id', eid).has('entityType').valueMap(true)",
933
+ { rid: repositoryId, eid: entityId }
934
+ );
935
+ if (result.items.length === 0) return null;
936
+ return entityFromGremlin(result.items[0]);
937
+ }
938
+ async function findPaths(conn, repositoryId, sourceId, targetId, options) {
939
+ const paths = [];
940
+ const maxDepth = options.maxDepth;
941
+ const limit = options.limit;
942
+ let queue = [{ entityId: sourceId, path: [sourceId], relationshipIds: [] }];
943
+ for (let depth = 0; depth < maxDepth && paths.length < limit; depth++) {
944
+ const nextQueue = [];
945
+ for (const state of queue) {
946
+ if (paths.length >= limit) break;
947
+ const bindings = { rid: repositoryId, eid: state.entityId };
948
+ let edgeQuery = "g.V().has('repositoryId', rid).has('id', eid).has('entityType').bothE()";
949
+ if (options.relationshipTypes && options.relationshipTypes.length > 0) {
950
+ const typeParams = [];
951
+ options.relationshipTypes.forEach((t, i) => {
952
+ const paramName = `rtype${i}`;
953
+ bindings[paramName] = t;
954
+ typeParams.push(paramName);
955
+ });
956
+ edgeQuery += `.hasLabel(${typeParams.join(", ")})`;
957
+ }
958
+ const relResult = await conn.submit(`${edgeQuery}.valueMap(true)`, bindings);
959
+ const rels = relResult.items.map(relationshipFromGremlin);
960
+ let filteredRels = rels;
961
+ if (options.relationshipPropertyFilters && options.relationshipPropertyFilters.length > 0) {
962
+ filteredRels = rels.filter((r) => matchesPropertyFilters2(r.properties, options.relationshipPropertyFilters));
963
+ }
964
+ for (const rel of filteredRels) {
965
+ const nextId = rel.sourceEntityId === state.entityId ? rel.targetEntityId : rel.sourceEntityId;
966
+ if (state.path.includes(nextId)) continue;
967
+ if (options.entityTypes && options.entityTypes.length > 0 && nextId !== targetId) {
968
+ const entity = await getEntityLight(conn, repositoryId, nextId);
969
+ if (!entity || !options.entityTypes.includes(entity.entityType)) continue;
970
+ }
971
+ const newPath = [...state.path, nextId];
972
+ const newRelIds = [...state.relationshipIds, rel.id];
973
+ if (nextId === targetId) {
974
+ paths.push({ entityIds: newPath, relationshipIds: newRelIds });
975
+ if (paths.length >= limit) break;
976
+ } else {
977
+ nextQueue.push({ entityId: nextId, path: newPath, relationshipIds: newRelIds });
978
+ }
979
+ }
980
+ }
981
+ queue = nextQueue;
982
+ if (queue.length === 0) break;
983
+ }
984
+ return {
985
+ paths,
986
+ totalPaths: paths.length
987
+ };
988
+ }
989
+
990
+ // src/queries/timeline.ts
991
+ async function getTimeline(conn, repositoryId, entityId, options) {
992
+ const events = [];
993
+ const entityResult = await conn.submit(
994
+ "g.V().has('repositoryId', rid).has('id', eid).has('entityType').valueMap('createdAt', 'modifiedAt')",
995
+ { rid: repositoryId, eid: entityId }
996
+ );
997
+ if (entityResult.items.length > 0) {
998
+ const props = entityResult.items[0];
999
+ const createdAt = unwrapValue(props["createdAt"]);
1000
+ const modifiedAt = unwrapValue(props["modifiedAt"]);
1001
+ if (createdAt && isInTimeRange(createdAt, options.timeRange)) {
1002
+ events.push({
1003
+ timestamp: createdAt,
1004
+ eventType: "entity_created",
1005
+ entityId
1006
+ });
1007
+ }
1008
+ if (modifiedAt && modifiedAt !== createdAt && isInTimeRange(modifiedAt, options.timeRange)) {
1009
+ events.push({
1010
+ timestamp: modifiedAt,
1011
+ eventType: "entity_modified",
1012
+ entityId
1013
+ });
1014
+ }
1015
+ }
1016
+ const relResult = await conn.submit(
1017
+ "g.V().has('repositoryId', rid).has('id', eid).has('entityType').bothE().valueMap('id', 'createdAt')",
1018
+ { rid: repositoryId, eid: entityId }
1019
+ );
1020
+ for (const item of relResult.items) {
1021
+ const props = item;
1022
+ const relId = unwrapValue(props["id"]);
1023
+ const relCreatedAt = unwrapValue(props["createdAt"]);
1024
+ if (relCreatedAt && isInTimeRange(relCreatedAt, options.timeRange)) {
1025
+ events.push({
1026
+ timestamp: relCreatedAt,
1027
+ eventType: "relationship_created",
1028
+ entityId,
1029
+ relationshipId: relId
1030
+ });
1031
+ }
1032
+ }
1033
+ events.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
1034
+ let filtered = events;
1035
+ if (options.eventTypes && options.eventTypes.length > 0) {
1036
+ filtered = events.filter((e) => options.eventTypes.includes(e.eventType));
1037
+ }
1038
+ const total = filtered.length;
1039
+ const paged = filtered.slice(options.offset, options.offset + options.limit);
1040
+ return { events: paged, total };
1041
+ }
1042
+ function unwrapValue(val) {
1043
+ if (Array.isArray(val) && val.length > 0) return String(val[0]);
1044
+ return String(val ?? "");
1045
+ }
1046
+ function isInTimeRange(timestamp, timeRange) {
1047
+ if (!timeRange) return true;
1048
+ return timestamp >= timeRange.from && timestamp <= timeRange.to;
1049
+ }
1050
+
1051
+ // src/queries/bulk.ts
1052
+ var EXPORT_BATCH_SIZE = 100;
1053
+ var IMPORT_CONCURRENCY = 8;
1054
+ async function runWithConcurrency(items, concurrency, fn) {
1055
+ const results = new Array(items.length);
1056
+ let nextIndex = 0;
1057
+ const workers = [];
1058
+ for (let w = 0; w < Math.min(concurrency, items.length); w++) {
1059
+ workers.push(
1060
+ (async () => {
1061
+ while (nextIndex < items.length) {
1062
+ const idx = nextIndex++;
1063
+ results[idx] = await fn(items[idx]);
1064
+ }
1065
+ })()
1066
+ );
1067
+ }
1068
+ await Promise.all(workers);
1069
+ return results;
1070
+ }
1071
+ async function* exportAll(conn, repositoryId) {
1072
+ let sequence = 0;
1073
+ let cursor = "";
1074
+ while (true) {
1075
+ const result = cursor === "" ? await conn.submit(
1076
+ "g.V().has('repositoryId', rid).has('entityType').order().by('id').limit(batchSize).valueMap(true)",
1077
+ { rid: repositoryId, batchSize: EXPORT_BATCH_SIZE }
1078
+ ) : await conn.submit(
1079
+ "g.V().has('repositoryId', rid).has('entityType').has('id', gt(cursor)).order().by('id').limit(batchSize).valueMap(true)",
1080
+ { rid: repositoryId, cursor, batchSize: EXPORT_BATCH_SIZE }
1081
+ );
1082
+ const entities = result.items.map(entityFromGremlin);
1083
+ const isLast = entities.length < EXPORT_BATCH_SIZE;
1084
+ if (entities.length > 0) {
1085
+ cursor = entities[entities.length - 1].id;
1086
+ yield {
1087
+ type: "entities",
1088
+ data: entities,
1089
+ sequence: sequence++,
1090
+ isLast
1091
+ };
1092
+ }
1093
+ if (isLast) break;
1094
+ }
1095
+ cursor = "";
1096
+ while (true) {
1097
+ const result = cursor === "" ? await conn.submit(
1098
+ "g.E().has('repositoryId', rid).order().by('id').limit(batchSize).valueMap(true)",
1099
+ { rid: repositoryId, batchSize: EXPORT_BATCH_SIZE }
1100
+ ) : await conn.submit(
1101
+ "g.E().has('repositoryId', rid).has('id', gt(cursor)).order().by('id').limit(batchSize).valueMap(true)",
1102
+ { rid: repositoryId, cursor, batchSize: EXPORT_BATCH_SIZE }
1103
+ );
1104
+ const relationships = result.items.map(relationshipFromGremlin);
1105
+ const isLast = relationships.length < EXPORT_BATCH_SIZE;
1106
+ if (relationships.length > 0) {
1107
+ cursor = relationships[relationships.length - 1].id;
1108
+ yield {
1109
+ type: "relationships",
1110
+ data: relationships,
1111
+ sequence: sequence++,
1112
+ isLast
1113
+ };
1114
+ }
1115
+ if (isLast) break;
1116
+ }
1117
+ if (sequence === 0) {
1118
+ yield {
1119
+ type: "entities",
1120
+ data: [],
1121
+ sequence: 0,
1122
+ isLast: true
1123
+ };
1124
+ }
1125
+ }
1126
+ async function importBulk(conn, repositoryId, data, options) {
1127
+ let entitiesImported = 0;
1128
+ let relationshipsImported = 0;
1129
+ const errors = [];
1130
+ const skipCheck = options?.skipExistenceCheck ?? false;
1131
+ for (const chunk of data) {
1132
+ if (chunk.entities && chunk.entities.length > 0) {
1133
+ const results = await runWithConcurrency(
1134
+ chunk.entities,
1135
+ IMPORT_CONCURRENCY,
1136
+ async (entity) => {
1137
+ try {
1138
+ if (skipCheck) {
1139
+ await insertEntity(conn, repositoryId, entity);
1140
+ } else {
1141
+ await upsertEntity(conn, repositoryId, entity);
1142
+ }
1143
+ return { ok: true, id: entity.id };
1144
+ } catch (err) {
1145
+ return {
1146
+ ok: false,
1147
+ id: entity.id,
1148
+ error: err instanceof Error ? err.message : String(err)
1149
+ };
1150
+ }
1151
+ }
1152
+ );
1153
+ for (const r of results) {
1154
+ if (r.ok) {
1155
+ entitiesImported++;
1156
+ } else {
1157
+ errors.push({ item: `entity:${r.id}`, error: r.error });
1158
+ }
1159
+ }
1160
+ }
1161
+ if (chunk.relationships && chunk.relationships.length > 0) {
1162
+ const results = await runWithConcurrency(
1163
+ chunk.relationships,
1164
+ IMPORT_CONCURRENCY,
1165
+ async (rel) => {
1166
+ try {
1167
+ if (skipCheck) {
1168
+ await insertRelationship(conn, repositoryId, rel);
1169
+ } else {
1170
+ await upsertRelationship(conn, repositoryId, rel);
1171
+ }
1172
+ return { ok: true, id: rel.id };
1173
+ } catch (err) {
1174
+ return {
1175
+ ok: false,
1176
+ id: rel.id,
1177
+ error: err instanceof Error ? err.message : String(err)
1178
+ };
1179
+ }
1180
+ }
1181
+ );
1182
+ for (const r of results) {
1183
+ if (r.ok) {
1184
+ relationshipsImported++;
1185
+ } else {
1186
+ errors.push({ item: `relationship:${r.id}`, error: r.error });
1187
+ }
1188
+ }
1189
+ }
1190
+ }
1191
+ return { entitiesImported, relationshipsImported, errors };
1192
+ }
1193
+ async function insertEntity(conn, repositoryId, entity) {
1194
+ const props = entityToGremlinProps(repositoryId, entity);
1195
+ const bindings = {
1196
+ vid: entity.id,
1197
+ vertexLabel: entity.entityType
1198
+ };
1199
+ const propParts = [];
1200
+ let idx = 0;
1201
+ for (const [key, value] of Object.entries(props)) {
1202
+ const paramName = `p${idx++}`;
1203
+ bindings[paramName] = value;
1204
+ propParts.push(`.property('${key}', ${paramName})`);
1205
+ }
1206
+ const query = `g.addV(vertexLabel).property('id', vid)${propParts.join("")}`;
1207
+ await conn.submit(query, bindings);
1208
+ }
1209
+ async function insertRelationship(conn, repositoryId, rel) {
1210
+ const props = relationshipToGremlinProps(repositoryId, rel);
1211
+ const bindings = {
1212
+ relId: rel.id,
1213
+ srcId: rel.sourceEntityId,
1214
+ tgtId: rel.targetEntityId,
1215
+ rid: repositoryId,
1216
+ edgeLabel: rel.relationshipType
1217
+ };
1218
+ const propParts = [];
1219
+ let idx = 0;
1220
+ for (const [key, value] of Object.entries(props)) {
1221
+ const paramName = `p${idx++}`;
1222
+ bindings[paramName] = value;
1223
+ propParts.push(`.property('${key}', ${paramName})`);
1224
+ }
1225
+ const query = `g.V().has('repositoryId', rid).has('id', srcId).has('entityType').addE(edgeLabel).to(g.V().has('repositoryId', rid).has('id', tgtId).has('entityType')).property('id', relId)${propParts.join("")}`;
1226
+ await conn.submit(query, bindings);
1227
+ }
1228
+ async function upsertEntity(conn, repositoryId, entity) {
1229
+ const props = entityToGremlinProps(repositoryId, entity);
1230
+ const bindings = {
1231
+ vid: entity.id,
1232
+ rid: repositoryId,
1233
+ vertexLabel: entity.entityType
1234
+ };
1235
+ const propParts = [];
1236
+ let idx = 0;
1237
+ for (const [key, value] of Object.entries(props)) {
1238
+ const paramName = `p${idx++}`;
1239
+ bindings[paramName] = value;
1240
+ propParts.push(`.property('${key}', ${paramName})`);
1241
+ }
1242
+ const query = `g.V().has('repositoryId', rid).has('id', vid).has('entityType').fold().coalesce(unfold()${propParts.join("")}, addV(vertexLabel).property('id', vid)${propParts.join("")})`;
1243
+ await conn.submit(query, bindings);
1244
+ }
1245
+ async function upsertRelationship(conn, repositoryId, rel) {
1246
+ const props = relationshipToGremlinProps(repositoryId, rel);
1247
+ const bindings = {
1248
+ relId: rel.id,
1249
+ srcId: rel.sourceEntityId,
1250
+ tgtId: rel.targetEntityId,
1251
+ rid: repositoryId,
1252
+ edgeLabel: rel.relationshipType
1253
+ };
1254
+ const propParts = [];
1255
+ let idx = 0;
1256
+ for (const [key, value] of Object.entries(props)) {
1257
+ const paramName = `p${idx++}`;
1258
+ bindings[paramName] = value;
1259
+ propParts.push(`.property('${key}', ${paramName})`);
1260
+ }
1261
+ const createEdge = `g.V().has('repositoryId', rid).has('id', srcId).has('entityType').addE(edgeLabel).to(g.V().has('repositoryId', rid).has('id', tgtId).has('entityType')).property('id', relId)${propParts.join("")}`;
1262
+ const query = `g.E().has('id', relId).fold().coalesce(unfold()${propParts.join("")}, ${createEdge})`;
1263
+ await conn.submit(query, bindings);
1264
+ }
1265
+
1266
+ // src/CosmosDbProvider.ts
1267
+ var SCHEMA_VERSION = 1;
1268
+ var META_VERTEX_ID = "_meta:schema";
1269
+ var CosmosDbProvider = class {
1270
+ conn;
1271
+ config;
1272
+ compiler = new GremlinCompiler();
1273
+ constructor(config) {
1274
+ this.config = config;
1275
+ this.conn = new CosmosDbConnection({
1276
+ endpoint: config.endpoint,
1277
+ key: config.key,
1278
+ database: config.database,
1279
+ container: config.container,
1280
+ maxRetries: config.maxRetries,
1281
+ defaultTimeoutMs: config.defaultTimeoutMs,
1282
+ rejectUnauthorized: config.rejectUnauthorized
1283
+ });
1284
+ }
1285
+ // ─── Lifecycle ─────────────────────────────────────────────────────
1286
+ async initialise() {
1287
+ await this.conn.connect();
1288
+ }
1289
+ async dispose() {
1290
+ await this.conn.close();
1291
+ }
1292
+ async ensureSchema() {
1293
+ const restBase = this.getRestEndpoint();
1294
+ const partitionKey = this.config.partitionKey ?? "/repositoryId";
1295
+ let databaseCreated = false;
1296
+ let schemaCreated = false;
1297
+ try {
1298
+ const dbCreated = await cosmosRestPut(
1299
+ restBase,
1300
+ this.config.key,
1301
+ "dbs",
1302
+ "",
1303
+ "dbs",
1304
+ { id: this.config.database },
1305
+ this.config.rejectUnauthorized ?? true
1306
+ );
1307
+ databaseCreated = dbCreated;
1308
+ const containerCreated = await cosmosRestPut(
1309
+ restBase,
1310
+ this.config.key,
1311
+ `dbs/${this.config.database}/colls`,
1312
+ `dbs/${this.config.database}`,
1313
+ "colls",
1314
+ {
1315
+ id: this.config.container,
1316
+ partitionKey: { paths: [partitionKey], kind: "Hash" }
1317
+ },
1318
+ this.config.rejectUnauthorized ?? true
1319
+ );
1320
+ schemaCreated = containerCreated;
1321
+ await this.conn.close();
1322
+ await this.conn.connect();
1323
+ const existing = await this.conn.submit(
1324
+ "g.V().has('id', metaId).hasLabel('_meta').valueMap(true)",
1325
+ { metaId: META_VERTEX_ID }
1326
+ );
1327
+ if (existing.items.length > 0) {
1328
+ const props = existing.items[0];
1329
+ const version = Number(unwrapGremlinValue(props["schemaVersion"]) ?? 0);
1330
+ if (version >= SCHEMA_VERSION && !databaseCreated && !schemaCreated) {
1331
+ return {
1332
+ databaseCreated: false,
1333
+ schemaCreated: false,
1334
+ alreadyUpToDate: true,
1335
+ schemaVersion: SCHEMA_VERSION
1336
+ };
1337
+ }
1338
+ await this.conn.submit(
1339
+ "g.V().has('id', metaId).hasLabel('_meta').property('schemaVersion', ver)",
1340
+ { metaId: META_VERTEX_ID, ver: SCHEMA_VERSION }
1341
+ );
1342
+ } else {
1343
+ await this.conn.submit(
1344
+ "g.addV('_meta').property('id', metaId).property('repositoryId', pk).property('schemaVersion', ver)",
1345
+ { metaId: META_VERTEX_ID, pk: "_system", ver: SCHEMA_VERSION }
1346
+ );
1347
+ schemaCreated = true;
1348
+ }
1349
+ return {
1350
+ databaseCreated,
1351
+ schemaCreated,
1352
+ alreadyUpToDate: !databaseCreated && !schemaCreated,
1353
+ schemaVersion: SCHEMA_VERSION
1354
+ };
1355
+ } catch (err) {
1356
+ throw new ProviderError(
1357
+ `Failed to ensure CosmosDB schema: ${err instanceof Error ? err.message : String(err)}`,
1358
+ "Verify the CosmosDB REST endpoint is accessible and the Gremlin endpoint is reachable."
1359
+ );
1360
+ }
1361
+ }
1362
+ /** Derive the REST endpoint from config — either explicit or from the Gremlin endpoint host. */
1363
+ getRestEndpoint() {
1364
+ if (this.config.restEndpoint) return this.config.restEndpoint.replace(/\/+$/, "");
1365
+ const url = new URL(this.config.endpoint);
1366
+ return `https://${url.hostname}:8081`;
1367
+ }
1368
+ // ─── Repository ────────────────────────────────────────────────────
1369
+ async createRepository(config) {
1370
+ return createRepository(this.conn, config);
1371
+ }
1372
+ async getRepository(repositoryId) {
1373
+ return getRepository(this.conn, repositoryId);
1374
+ }
1375
+ async listRepositories(filter) {
1376
+ return listRepositories(this.conn, filter);
1377
+ }
1378
+ async updateRepository(repositoryId, updates) {
1379
+ return updateRepository(this.conn, repositoryId, updates);
1380
+ }
1381
+ async deleteRepository(repositoryId, onProgress) {
1382
+ return deleteRepository(this.conn, repositoryId, onProgress);
1383
+ }
1384
+ async deleteAllContents(repositoryId, onProgress) {
1385
+ return deleteAllContents(this.conn, repositoryId, onProgress);
1386
+ }
1387
+ async getRepositoryStats(repositoryId) {
1388
+ return getRepositoryStats(this.conn, repositoryId);
1389
+ }
1390
+ // ─── Vocabulary ────────────────────────────────────────────────────
1391
+ async getVocabulary(repositoryId) {
1392
+ return getVocabulary(this.conn, repositoryId);
1393
+ }
1394
+ async saveVocabulary(repositoryId, vocabulary) {
1395
+ return saveVocabulary(this.conn, repositoryId, vocabulary);
1396
+ }
1397
+ async getVocabularyChangeLog(repositoryId, options) {
1398
+ return getVocabularyChangeLog(this.conn, repositoryId, options);
1399
+ }
1400
+ // ─── Entities ──────────────────────────────────────────────────────
1401
+ async createEntity(repositoryId, entity) {
1402
+ return createEntity(this.conn, repositoryId, entity);
1403
+ }
1404
+ async getEntity(repositoryId, entityId) {
1405
+ return getEntity(this.conn, repositoryId, entityId);
1406
+ }
1407
+ async getEntityBySlug(repositoryId, slug) {
1408
+ return getEntityBySlug(this.conn, repositoryId, slug);
1409
+ }
1410
+ async getEntities(repositoryId, entityIds) {
1411
+ return getEntities(this.conn, repositoryId, entityIds);
1412
+ }
1413
+ async updateEntity(repositoryId, entityId, updates) {
1414
+ return updateEntity(this.conn, repositoryId, entityId, updates);
1415
+ }
1416
+ async deleteEntity(repositoryId, entityId) {
1417
+ return deleteEntity(this.conn, repositoryId, entityId);
1418
+ }
1419
+ async deleteEntitiesByType(repositoryId, entityType) {
1420
+ return deleteEntitiesByType(this.conn, repositoryId, entityType);
1421
+ }
1422
+ async findEntities(repositoryId, query) {
1423
+ return findEntities(this.conn, repositoryId, query);
1424
+ }
1425
+ // ─── Relationships ─────────────────────────────────────────────────
1426
+ async createRelationship(repositoryId, relationship) {
1427
+ return createRelationship(this.conn, repositoryId, relationship);
1428
+ }
1429
+ async getRelationship(repositoryId, relationshipId) {
1430
+ return getRelationship(this.conn, repositoryId, relationshipId);
1431
+ }
1432
+ async getEntityRelationships(repositoryId, entityId, options) {
1433
+ return getEntityRelationships(this.conn, repositoryId, entityId, options);
1434
+ }
1435
+ async deleteRelationship(repositoryId, relationshipId) {
1436
+ return deleteRelationship(this.conn, repositoryId, relationshipId);
1437
+ }
1438
+ async deleteRelationshipsByType(repositoryId, relationshipType) {
1439
+ return deleteRelationshipsByType(this.conn, repositoryId, relationshipType);
1440
+ }
1441
+ // ─── Graph Traversal (StorageProvider) ─────────────────────────────
1442
+ async exploreNeighbourhood(repositoryId, entityId, options) {
1443
+ return exploreNeighbourhood(this.conn, repositoryId, entityId, options);
1444
+ }
1445
+ async findPaths(repositoryId, sourceId, targetId, options) {
1446
+ return findPaths(this.conn, repositoryId, sourceId, targetId, options);
1447
+ }
1448
+ // ─── Timeline ──────────────────────────────────────────────────────
1449
+ async getTimeline(repositoryId, entityId, options) {
1450
+ return getTimeline(this.conn, repositoryId, entityId, options);
1451
+ }
1452
+ // ─── Bulk Operations ───────────────────────────────────────────────
1453
+ exportAll(repositoryId) {
1454
+ return exportAll(this.conn, repositoryId);
1455
+ }
1456
+ async importBulk(repositoryId, data, options) {
1457
+ return importBulk(this.conn, repositoryId, data, options);
1458
+ }
1459
+ // ─── GraphTraversalProvider ────────────────────────────────────────
1460
+ getCapabilities() {
1461
+ return {
1462
+ supportsNativeQuery: true,
1463
+ nativeQueryLanguage: "gremlin",
1464
+ maxTraversalDepth: 10,
1465
+ supportsRelationshipPropertyFilters: true,
1466
+ supportsEntityPropertyFilters: true,
1467
+ supportsAggregation: false,
1468
+ supportsRepeat: true,
1469
+ supportsDedup: true,
1470
+ supportsRelationshipSummary: false
1471
+ };
1472
+ }
1473
+ async traverse(repositoryId, spec, compiledQuery) {
1474
+ const startTime = Date.now();
1475
+ const vocabulary = await this.getVocabulary(repositoryId);
1476
+ const compiled = compiledQuery ? { query: compiledQuery, params: {}, estimatedFanOut: 100 } : this.compiler.compile(spec, vocabulary);
1477
+ const scopedQuery = compiled.query.replace(
1478
+ "g.V()",
1479
+ `g.V().has('repositoryId', '${repositoryId}')`
1480
+ );
1481
+ try {
1482
+ const result = await this.conn.submit(scopedQuery, compiled.params);
1483
+ const executionTimeMs = Date.now() - startTime;
1484
+ const entities = [];
1485
+ const relationships = [];
1486
+ const paths = [];
1487
+ if (spec.returnMode === "path") {
1488
+ for (const item of result.items) {
1489
+ const pathData = item;
1490
+ if (pathData.objects) {
1491
+ const pathEntities = [];
1492
+ for (const obj of pathData.objects) {
1493
+ const props = obj;
1494
+ if (props["entityType"]) {
1495
+ pathEntities.push(entityFromGremlin(props));
1496
+ }
1497
+ }
1498
+ if (pathEntities.length > 0) {
1499
+ paths.push({
1500
+ length: pathEntities.length - 1,
1501
+ entities: pathEntities,
1502
+ relationships: []
1503
+ });
1504
+ }
1505
+ }
1506
+ }
1507
+ } else {
1508
+ for (const item of result.items) {
1509
+ const entity = entityFromGremlin(item);
1510
+ entities.push(entity);
1511
+ }
1512
+ }
1513
+ const limit = spec.limit ?? 50;
1514
+ const queryMetadata = {
1515
+ executionTimeMs,
1516
+ resourceCost: result.requestCharge != null ? { units: "RU", value: result.requestCharge } : void 0,
1517
+ compiledQuery: scopedQuery,
1518
+ compiledQueryLanguage: "gremlin",
1519
+ appliedLimits: {
1520
+ maxResults: limit,
1521
+ maxDepth: spec.steps?.length
1522
+ },
1523
+ truncated: entities.length >= limit,
1524
+ truncationReason: entities.length >= limit ? "result_limit" : void 0
1525
+ };
1526
+ return {
1527
+ entities: spec.returnMode === "path" ? [] : entities,
1528
+ relationships: spec.returnMode === "path" || spec.returnMode === "all" ? relationships : void 0,
1529
+ paths: spec.returnMode === "path" ? paths : void 0,
1530
+ total: entities.length + paths.length,
1531
+ returned: entities.length + paths.length,
1532
+ hasMore: entities.length >= limit || paths.length >= limit,
1533
+ queryMetadata
1534
+ };
1535
+ } catch (err) {
1536
+ throw new ProviderError(
1537
+ `Gremlin traversal failed: ${err instanceof Error ? err.message : String(err)}`,
1538
+ "Check the traversal spec and ensure the CosmosDB connection is healthy."
1539
+ );
1540
+ }
1541
+ }
1542
+ async executeNativeQuery(_repositoryId, query, params) {
1543
+ const startTime = Date.now();
1544
+ try {
1545
+ const result = await this.conn.submit(query, params);
1546
+ const executionTimeMs = Date.now() - startTime;
1547
+ const entities = [];
1548
+ for (const item of result.items) {
1549
+ try {
1550
+ const entity = entityFromGremlin(item);
1551
+ if (entity.id) {
1552
+ entities.push(entity);
1553
+ }
1554
+ } catch {
1555
+ }
1556
+ }
1557
+ const queryMetadata = {
1558
+ executionTimeMs,
1559
+ resourceCost: result.requestCharge != null ? { units: "RU", value: result.requestCharge } : void 0,
1560
+ compiledQuery: query,
1561
+ compiledQueryLanguage: "gremlin",
1562
+ appliedLimits: { maxResults: entities.length },
1563
+ truncated: false
1564
+ };
1565
+ return {
1566
+ entities,
1567
+ total: entities.length,
1568
+ returned: entities.length,
1569
+ hasMore: false,
1570
+ queryMetadata
1571
+ };
1572
+ } catch (err) {
1573
+ throw new ProviderError(
1574
+ `Native Gremlin query failed: ${err instanceof Error ? err.message : String(err)}`,
1575
+ "Verify the Gremlin query syntax is valid for CosmosDB."
1576
+ );
1577
+ }
1578
+ }
1579
+ };
1580
+ function unwrapGremlinValue(val) {
1581
+ if (Array.isArray(val) && val.length > 0) return val[0];
1582
+ return val;
1583
+ }
1584
+ function cosmosAuthToken(verb, resourceType, resourceLink, date, key) {
1585
+ const payload = `${verb.toLowerCase()}
1586
+ ${resourceType.toLowerCase()}
1587
+ ${resourceLink}
1588
+ ${date.toLowerCase()}
1589
+
1590
+ `;
1591
+ const keyBuffer = Buffer.from(key, "base64");
1592
+ const hmac = crypto.createHmac("sha256", keyBuffer);
1593
+ hmac.update(payload);
1594
+ const signature = hmac.digest("base64");
1595
+ return encodeURIComponent(`type=master&ver=1.0&sig=${signature}`);
1596
+ }
1597
+ async function cosmosRestPut(restBase, key, urlPath, resourceLink, resourceType, body, rejectUnauthorized) {
1598
+ const date = (/* @__PURE__ */ new Date()).toUTCString();
1599
+ const token = cosmosAuthToken("post", resourceType, resourceLink, date, key);
1600
+ const url = `${restBase}/${urlPath}`;
1601
+ const options = {
1602
+ method: "POST",
1603
+ headers: {
1604
+ "Authorization": token,
1605
+ "x-ms-version": "2018-12-31",
1606
+ "x-ms-date": date,
1607
+ "Content-Type": "application/json"
1608
+ },
1609
+ body: JSON.stringify(body)
1610
+ };
1611
+ if (!rejectUnauthorized) {
1612
+ try {
1613
+ options.dispatcher = new (await import("undici")).Agent({
1614
+ connect: { rejectUnauthorized: false }
1615
+ });
1616
+ } catch {
1617
+ process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
1618
+ }
1619
+ }
1620
+ const response = await fetch(url, options);
1621
+ if (response.status === 201) return true;
1622
+ if (response.status === 409) return false;
1623
+ const text = await response.text();
1624
+ throw new Error(`CosmosDB REST ${response.status}: ${text}`);
1625
+ }
1626
+ export {
1627
+ CosmosDbConnection,
1628
+ CosmosDbProvider
1629
+ };
1630
+ //# sourceMappingURL=index.js.map