@utaba/deep-memory-storage-cosmosdb 0.8.1 → 0.9.1

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.cjs CHANGED
@@ -41,6 +41,8 @@ var import_deep_memory5 = require("@utaba/deep-memory");
41
41
 
42
42
  // src/CosmosDbConnection.ts
43
43
  var import_gremlin = __toESM(require("gremlin"), 1);
44
+ var import_node_async_hooks = require("async_hooks");
45
+ var usageScope = new import_node_async_hooks.AsyncLocalStorage();
44
46
  var CosmosDbConnection = class {
45
47
  client = null;
46
48
  config;
@@ -88,11 +90,18 @@ var CosmosDbConnection = class {
88
90
  const resultSet = await this.client.submit(query, bindings);
89
91
  const items = resultSet.toArray();
90
92
  const requestCharge = extractRequestCharge(resultSet);
93
+ const acc = usageScope.getStore();
94
+ if (acc) {
95
+ acc.calls++;
96
+ if (typeof requestCharge === "number") acc.ru += requestCharge;
97
+ }
91
98
  return { items, requestCharge };
92
99
  } catch (err) {
93
100
  lastError = err;
94
101
  if (isTransientError(err) && attempt < this.config.maxRetries) {
95
102
  const retryAfterMs = getRetryAfterMs(err, attempt);
103
+ const acc = usageScope.getStore();
104
+ if (acc) acc.retries++;
96
105
  await sleep(retryAfterMs);
97
106
  continue;
98
107
  }
@@ -910,6 +919,7 @@ async function exploreNeighborhood(conn, repositoryId, entityId, options) {
910
919
  for (let depth = 0; depth < options.depth; depth++) {
911
920
  const layer = {};
912
921
  const nextFrontier = /* @__PURE__ */ new Set();
922
+ const layerCache = /* @__PURE__ */ new Map();
913
923
  for (const currentId of frontier) {
914
924
  const rels = await getRelationshipsForEntity(conn, repositoryId, currentId, options);
915
925
  for (const rel of rels) {
@@ -923,20 +933,20 @@ async function exploreNeighborhood(conn, repositoryId, entityId, options) {
923
933
  } else {
924
934
  connectedId = rel.sourceEntityId;
925
935
  }
926
- if (!visited.has(connectedId)) {
927
- if (options.entityTypes && options.entityTypes.length > 0) {
928
- const entity = await getEntityLight(conn, repositoryId, connectedId);
929
- if (!entity || !options.entityTypes.includes(entity.entityType)) continue;
930
- layer[relType].entities.push(entity);
936
+ if (visited.has(connectedId)) continue;
937
+ let entity = layerCache.get(connectedId);
938
+ if (entity === void 0) {
939
+ const loaded = await getEntityLight(conn, repositoryId, connectedId);
940
+ if (loaded && (!options.entityTypes || options.entityTypes.length === 0 || options.entityTypes.includes(loaded.entityType))) {
941
+ entity = loaded;
931
942
  } else {
932
- const entity = await getEntityLight(conn, repositoryId, connectedId);
933
- if (entity) {
934
- layer[relType].entities.push(entity);
935
- }
943
+ entity = null;
936
944
  }
937
- visited.add(connectedId);
938
- nextFrontier.add(connectedId);
945
+ layerCache.set(connectedId, entity);
946
+ if (entity) nextFrontier.add(connectedId);
939
947
  }
948
+ if (!entity) continue;
949
+ layer[relType].entities.push(entity);
940
950
  layer[relType].relationships.push(rel);
941
951
  layer[relType].total = layer[relType].entities.length;
942
952
  }
@@ -951,6 +961,9 @@ async function exploreNeighborhood(conn, repositoryId, entityId, options) {
951
961
  if (Object.keys(layer).length > 0) {
952
962
  layers.push(layer);
953
963
  }
964
+ for (const id of nextFrontier) {
965
+ visited.add(id);
966
+ }
954
967
  frontier = nextFrontier;
955
968
  if (frontier.size === 0) break;
956
969
  }
@@ -1328,14 +1341,17 @@ async function upsertRelationship(conn, repositoryId, rel) {
1328
1341
  }
1329
1342
 
1330
1343
  // src/CosmosDbProvider.ts
1344
+ var PROVIDER_NAME = "cosmosdb";
1331
1345
  var SCHEMA_VERSION = 1;
1332
1346
  var META_VERTEX_ID = "_meta:schema";
1333
1347
  var CosmosDbProvider = class {
1334
1348
  conn;
1335
1349
  config;
1336
1350
  compiler = new import_deep_memory5.GremlinCompiler();
1351
+ reportUsage;
1337
1352
  constructor(config) {
1338
1353
  this.config = config;
1354
+ this.reportUsage = (0, import_deep_memory5.createSafeSink)(config.reportUsage);
1339
1355
  this.conn = new CosmosDbConnection({
1340
1356
  endpoint: config.endpoint,
1341
1357
  key: config.key,
@@ -1346,6 +1362,39 @@ var CosmosDbProvider = class {
1346
1362
  rejectUnauthorized: config.rejectUnauthorized
1347
1363
  });
1348
1364
  }
1365
+ /**
1366
+ * Run the given operation inside an async-local usage scope. While the
1367
+ * operation runs, every `conn.submit()` call accumulates its request charge
1368
+ * into a shared accumulator. When the operation completes (success or
1369
+ * failure), a single {@link OperationUsage} record is emitted to the sink.
1370
+ *
1371
+ * Nested public-method calls (e.g. `traverse` internally calls
1372
+ * `getVocabulary`) are detected: the inner call joins the outer scope
1373
+ * instead of starting a new one, so one user-visible operation produces
1374
+ * exactly one usage record covering all its internal round-trips.
1375
+ *
1376
+ * If no sink is configured, this is a zero-overhead pass-through.
1377
+ */
1378
+ async track(operation, repositoryId, fn) {
1379
+ if (!this.reportUsage) return fn();
1380
+ if (usageScope.getStore()) return fn();
1381
+ const acc = { ru: 0, calls: 0, retries: 0 };
1382
+ try {
1383
+ return await usageScope.run(acc, fn);
1384
+ } finally {
1385
+ if (acc.calls > 0) {
1386
+ this.reportUsage({
1387
+ provider: PROVIDER_NAME,
1388
+ operation,
1389
+ unit: "RU",
1390
+ value: acc.ru,
1391
+ ...repositoryId ? { repositoryId } : {},
1392
+ timestamp: /* @__PURE__ */ new Date(),
1393
+ details: { calls: acc.calls, retries: acc.retries }
1394
+ });
1395
+ }
1396
+ }
1397
+ }
1349
1398
  /**
1350
1399
  * Reject any repositoryId that is not a valid v4 UUID.
1351
1400
  *
@@ -1371,6 +1420,9 @@ var CosmosDbProvider = class {
1371
1420
  await this.conn.close();
1372
1421
  }
1373
1422
  async ensureSchema() {
1423
+ return this.track("ensureSchema", void 0, () => this.ensureSchemaImpl());
1424
+ }
1425
+ async ensureSchemaImpl() {
1374
1426
  const restBase = this.getRestEndpoint();
1375
1427
  const partitionKey = this.config.partitionKey ?? "/repositoryId";
1376
1428
  let databaseCreated = false;
@@ -1449,72 +1501,139 @@ var CosmosDbProvider = class {
1449
1501
  // ─── Repository ────────────────────────────────────────────────────
1450
1502
  async createRepository(config) {
1451
1503
  this.assertValidRepositoryId(config.repositoryId);
1452
- return createRepository(this.conn, config);
1504
+ return this.track(
1505
+ "createRepository",
1506
+ config.repositoryId,
1507
+ () => createRepository(this.conn, config)
1508
+ );
1453
1509
  }
1454
1510
  async getRepository(repositoryId) {
1455
1511
  this.assertValidRepositoryId(repositoryId);
1456
- return getRepository(this.conn, repositoryId);
1512
+ return this.track(
1513
+ "getRepository",
1514
+ repositoryId,
1515
+ () => getRepository(this.conn, repositoryId)
1516
+ );
1457
1517
  }
1458
1518
  async listRepositories(filter) {
1459
- return listRepositories(this.conn, filter);
1519
+ return this.track(
1520
+ "listRepositories",
1521
+ void 0,
1522
+ () => listRepositories(this.conn, filter)
1523
+ );
1460
1524
  }
1461
1525
  async updateRepository(repositoryId, updates) {
1462
1526
  this.assertValidRepositoryId(repositoryId);
1463
- return updateRepository(this.conn, repositoryId, updates);
1527
+ return this.track(
1528
+ "updateRepository",
1529
+ repositoryId,
1530
+ () => updateRepository(this.conn, repositoryId, updates)
1531
+ );
1464
1532
  }
1465
1533
  async deleteRepository(repositoryId, onProgress) {
1466
1534
  this.assertValidRepositoryId(repositoryId);
1467
- return deleteRepository(this.conn, repositoryId, onProgress);
1535
+ return this.track(
1536
+ "deleteRepository",
1537
+ repositoryId,
1538
+ () => deleteRepository(this.conn, repositoryId, onProgress)
1539
+ );
1468
1540
  }
1469
1541
  async deleteAllContents(repositoryId, onProgress) {
1470
1542
  this.assertValidRepositoryId(repositoryId);
1471
- return deleteAllContents(this.conn, repositoryId, onProgress);
1543
+ return this.track(
1544
+ "deleteAllContents",
1545
+ repositoryId,
1546
+ () => deleteAllContents(this.conn, repositoryId, onProgress)
1547
+ );
1472
1548
  }
1473
1549
  async getRepositoryStats(repositoryId) {
1474
1550
  this.assertValidRepositoryId(repositoryId);
1475
- return getRepositoryStats(this.conn, repositoryId);
1551
+ return this.track(
1552
+ "getRepositoryStats",
1553
+ repositoryId,
1554
+ () => getRepositoryStats(this.conn, repositoryId)
1555
+ );
1476
1556
  }
1477
1557
  // ─── Vocabulary ────────────────────────────────────────────────────
1478
1558
  async getVocabulary(repositoryId) {
1479
1559
  this.assertValidRepositoryId(repositoryId);
1480
- return getVocabulary(this.conn, repositoryId);
1560
+ return this.track(
1561
+ "getVocabulary",
1562
+ repositoryId,
1563
+ () => getVocabulary(this.conn, repositoryId)
1564
+ );
1481
1565
  }
1482
1566
  async saveVocabulary(repositoryId, vocabulary) {
1483
1567
  this.assertValidRepositoryId(repositoryId);
1484
- return saveVocabulary(this.conn, repositoryId, vocabulary);
1568
+ return this.track(
1569
+ "saveVocabulary",
1570
+ repositoryId,
1571
+ () => saveVocabulary(this.conn, repositoryId, vocabulary)
1572
+ );
1485
1573
  }
1486
1574
  async getVocabularyChangeLog(repositoryId, options) {
1487
1575
  this.assertValidRepositoryId(repositoryId);
1488
- return getVocabularyChangeLog(this.conn, repositoryId, options);
1576
+ return this.track(
1577
+ "getVocabularyChangeLog",
1578
+ repositoryId,
1579
+ () => getVocabularyChangeLog(this.conn, repositoryId, options)
1580
+ );
1489
1581
  }
1490
1582
  // ─── Entities ──────────────────────────────────────────────────────
1491
1583
  async createEntity(repositoryId, entity) {
1492
1584
  this.assertValidRepositoryId(repositoryId);
1493
- return createEntity(this.conn, repositoryId, entity);
1585
+ return this.track(
1586
+ "createEntity",
1587
+ repositoryId,
1588
+ () => createEntity(this.conn, repositoryId, entity)
1589
+ );
1494
1590
  }
1495
1591
  async getEntity(repositoryId, entityId) {
1496
1592
  this.assertValidRepositoryId(repositoryId);
1497
- return getEntity(this.conn, repositoryId, entityId);
1593
+ return this.track(
1594
+ "getEntity",
1595
+ repositoryId,
1596
+ () => getEntity(this.conn, repositoryId, entityId)
1597
+ );
1498
1598
  }
1499
1599
  async getEntityBySlug(repositoryId, slug) {
1500
1600
  this.assertValidRepositoryId(repositoryId);
1501
- return getEntityBySlug(this.conn, repositoryId, slug);
1601
+ return this.track(
1602
+ "getEntityBySlug",
1603
+ repositoryId,
1604
+ () => getEntityBySlug(this.conn, repositoryId, slug)
1605
+ );
1502
1606
  }
1503
1607
  async getEntities(repositoryId, entityIds) {
1504
1608
  this.assertValidRepositoryId(repositoryId);
1505
- return getEntities(this.conn, repositoryId, entityIds);
1609
+ return this.track(
1610
+ "getEntities",
1611
+ repositoryId,
1612
+ () => getEntities(this.conn, repositoryId, entityIds)
1613
+ );
1506
1614
  }
1507
1615
  async updateEntity(repositoryId, entityId, updates) {
1508
1616
  this.assertValidRepositoryId(repositoryId);
1509
- return updateEntity(this.conn, repositoryId, entityId, updates);
1617
+ return this.track(
1618
+ "updateEntity",
1619
+ repositoryId,
1620
+ () => updateEntity(this.conn, repositoryId, entityId, updates)
1621
+ );
1510
1622
  }
1511
1623
  async deleteEntity(repositoryId, entityId) {
1512
1624
  this.assertValidRepositoryId(repositoryId);
1513
- return deleteEntity(this.conn, repositoryId, entityId);
1625
+ return this.track(
1626
+ "deleteEntity",
1627
+ repositoryId,
1628
+ () => deleteEntity(this.conn, repositoryId, entityId)
1629
+ );
1514
1630
  }
1515
1631
  async deleteEntities(repositoryId, ids) {
1516
1632
  if (ids.length === 0) return { deleted: [], notFound: [] };
1517
1633
  this.assertValidRepositoryId(repositoryId);
1634
+ return this.track("deleteEntities", repositoryId, () => this.deleteEntitiesImpl(repositoryId, ids));
1635
+ }
1636
+ async deleteEntitiesImpl(repositoryId, ids) {
1518
1637
  const deleted = [];
1519
1638
  const CHUNK = 100;
1520
1639
  for (let i = 0; i < ids.length; i += CHUNK) {
@@ -1553,32 +1672,59 @@ var CosmosDbProvider = class {
1553
1672
  }
1554
1673
  async deleteEntitiesByType(repositoryId, entityType) {
1555
1674
  this.assertValidRepositoryId(repositoryId);
1556
- return deleteEntitiesByType(this.conn, repositoryId, entityType);
1675
+ return this.track(
1676
+ "deleteEntitiesByType",
1677
+ repositoryId,
1678
+ () => deleteEntitiesByType(this.conn, repositoryId, entityType)
1679
+ );
1557
1680
  }
1558
1681
  async findEntities(repositoryId, query) {
1559
1682
  this.assertValidRepositoryId(repositoryId);
1560
- return findEntities(this.conn, repositoryId, query);
1683
+ return this.track(
1684
+ "findEntities",
1685
+ repositoryId,
1686
+ () => findEntities(this.conn, repositoryId, query)
1687
+ );
1561
1688
  }
1562
1689
  // ─── Relationships ─────────────────────────────────────────────────
1563
1690
  async createRelationship(repositoryId, relationship) {
1564
1691
  this.assertValidRepositoryId(repositoryId);
1565
- return createRelationship(this.conn, repositoryId, relationship);
1692
+ return this.track(
1693
+ "createRelationship",
1694
+ repositoryId,
1695
+ () => createRelationship(this.conn, repositoryId, relationship)
1696
+ );
1566
1697
  }
1567
1698
  async getRelationship(repositoryId, relationshipId) {
1568
1699
  this.assertValidRepositoryId(repositoryId);
1569
- return getRelationship(this.conn, repositoryId, relationshipId);
1700
+ return this.track(
1701
+ "getRelationship",
1702
+ repositoryId,
1703
+ () => getRelationship(this.conn, repositoryId, relationshipId)
1704
+ );
1570
1705
  }
1571
1706
  async getEntityRelationships(repositoryId, entityId, options) {
1572
1707
  this.assertValidRepositoryId(repositoryId);
1573
- return getEntityRelationships(this.conn, repositoryId, entityId, options);
1708
+ return this.track(
1709
+ "getEntityRelationships",
1710
+ repositoryId,
1711
+ () => getEntityRelationships(this.conn, repositoryId, entityId, options)
1712
+ );
1574
1713
  }
1575
1714
  async deleteRelationship(repositoryId, relationshipId) {
1576
1715
  this.assertValidRepositoryId(repositoryId);
1577
- return deleteRelationship(this.conn, repositoryId, relationshipId);
1716
+ return this.track(
1717
+ "deleteRelationship",
1718
+ repositoryId,
1719
+ () => deleteRelationship(this.conn, repositoryId, relationshipId)
1720
+ );
1578
1721
  }
1579
1722
  async deleteRelationships(repositoryId, ids) {
1580
1723
  if (ids.length === 0) return { deleted: [], notFound: [] };
1581
1724
  this.assertValidRepositoryId(repositoryId);
1725
+ return this.track("deleteRelationships", repositoryId, () => this.deleteRelationshipsImpl(repositoryId, ids));
1726
+ }
1727
+ async deleteRelationshipsImpl(repositoryId, ids) {
1582
1728
  const deleted = [];
1583
1729
  const CHUNK = 100;
1584
1730
  for (let i = 0; i < ids.length; i += CHUNK) {
@@ -1614,30 +1760,99 @@ var CosmosDbProvider = class {
1614
1760
  }
1615
1761
  async deleteRelationshipsByType(repositoryId, relationshipType) {
1616
1762
  this.assertValidRepositoryId(repositoryId);
1617
- return deleteRelationshipsByType(this.conn, repositoryId, relationshipType);
1763
+ return this.track(
1764
+ "deleteRelationshipsByType",
1765
+ repositoryId,
1766
+ () => deleteRelationshipsByType(this.conn, repositoryId, relationshipType)
1767
+ );
1618
1768
  }
1619
1769
  // ─── Graph Traversal (StorageProvider) ─────────────────────────────
1620
1770
  async exploreNeighborhood(repositoryId, entityId, options) {
1621
1771
  this.assertValidRepositoryId(repositoryId);
1622
- return exploreNeighborhood(this.conn, repositoryId, entityId, options);
1772
+ return this.track(
1773
+ "exploreNeighborhood",
1774
+ repositoryId,
1775
+ () => exploreNeighborhood(this.conn, repositoryId, entityId, options)
1776
+ );
1623
1777
  }
1624
1778
  async findPaths(repositoryId, sourceId, targetId, options) {
1625
1779
  this.assertValidRepositoryId(repositoryId);
1626
- return findPaths(this.conn, repositoryId, sourceId, targetId, options);
1780
+ return this.track(
1781
+ "findPaths",
1782
+ repositoryId,
1783
+ () => findPaths(this.conn, repositoryId, sourceId, targetId, options)
1784
+ );
1627
1785
  }
1628
1786
  // ─── Timeline ──────────────────────────────────────────────────────
1629
1787
  async getTimeline(repositoryId, entityId, options) {
1630
1788
  this.assertValidRepositoryId(repositoryId);
1631
- return getTimeline(this.conn, repositoryId, entityId, options);
1789
+ return this.track(
1790
+ "getTimeline",
1791
+ repositoryId,
1792
+ () => getTimeline(this.conn, repositoryId, entityId, options)
1793
+ );
1632
1794
  }
1633
1795
  // ─── Bulk Operations ───────────────────────────────────────────────
1634
1796
  exportAll(repositoryId) {
1635
1797
  this.assertValidRepositoryId(repositoryId);
1636
- return exportAll(this.conn, repositoryId);
1798
+ return this.trackIterable("exportAll", repositoryId, exportAll(this.conn, repositoryId));
1637
1799
  }
1638
1800
  async importBulk(repositoryId, data, options) {
1639
1801
  this.assertValidRepositoryId(repositoryId);
1640
- return importBulk(this.conn, repositoryId, data, options);
1802
+ return this.track(
1803
+ "importBulk",
1804
+ repositoryId,
1805
+ () => importBulk(this.conn, repositoryId, data, options)
1806
+ );
1807
+ }
1808
+ /**
1809
+ * Wrap an AsyncIterable so every emitted chunk is generated inside the
1810
+ * usage scope. On each iteration step, the scope aggregates charges from
1811
+ * the next chunk's submits; when the iterator completes (or is closed), a
1812
+ * single usage record is emitted for the whole stream.
1813
+ */
1814
+ trackIterable(operation, repositoryId, source) {
1815
+ if (!this.reportUsage) return source;
1816
+ const sink = this.reportUsage;
1817
+ return {
1818
+ [Symbol.asyncIterator]: () => {
1819
+ const iter = source[Symbol.asyncIterator]();
1820
+ const acc = { ru: 0, calls: 0, retries: 0 };
1821
+ let emitted = false;
1822
+ const emit = () => {
1823
+ if (emitted) return;
1824
+ emitted = true;
1825
+ if (acc.calls > 0) {
1826
+ sink({
1827
+ provider: PROVIDER_NAME,
1828
+ operation,
1829
+ unit: "RU",
1830
+ value: acc.ru,
1831
+ repositoryId,
1832
+ timestamp: /* @__PURE__ */ new Date(),
1833
+ details: { calls: acc.calls, retries: acc.retries }
1834
+ });
1835
+ }
1836
+ };
1837
+ return {
1838
+ async next() {
1839
+ const step = await usageScope.run(acc, () => iter.next());
1840
+ if (step.done) emit();
1841
+ return step;
1842
+ },
1843
+ async return(value) {
1844
+ emit();
1845
+ if (iter.return) return iter.return(value);
1846
+ return { done: true, value };
1847
+ },
1848
+ async throw(err) {
1849
+ emit();
1850
+ if (iter.throw) return iter.throw(err);
1851
+ throw err;
1852
+ }
1853
+ };
1854
+ }
1855
+ };
1641
1856
  }
1642
1857
  // ─── GraphTraversalProvider ────────────────────────────────────────
1643
1858
  getCapabilities() {
@@ -1655,6 +1870,9 @@ var CosmosDbProvider = class {
1655
1870
  }
1656
1871
  async traverse(repositoryId, spec) {
1657
1872
  this.assertValidRepositoryId(repositoryId);
1873
+ return this.track("traverse", repositoryId, () => this.traverseImpl(repositoryId, spec));
1874
+ }
1875
+ async traverseImpl(repositoryId, spec) {
1658
1876
  const startTime = Date.now();
1659
1877
  const vocabulary = await this.getVocabulary(repositoryId);
1660
1878
  const compiled = this.compiler.compile(spec, vocabulary);
@@ -1748,6 +1966,9 @@ var CosmosDbProvider = class {
1748
1966
  * repositoryId partition predicate.
1749
1967
  */
1750
1968
  async executeNativeQuery(_repositoryId, query, params) {
1969
+ return this.track("executeNativeQuery", void 0, () => this.executeNativeQueryImpl(query, params));
1970
+ }
1971
+ async executeNativeQueryImpl(query, params) {
1751
1972
  const startTime = Date.now();
1752
1973
  try {
1753
1974
  const result = await this.conn.submit(query, params);