@utaba/deep-memory-storage-cosmosdb 0.8.1 → 0.9.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.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
  }
@@ -1328,14 +1337,17 @@ async function upsertRelationship(conn, repositoryId, rel) {
1328
1337
  }
1329
1338
 
1330
1339
  // src/CosmosDbProvider.ts
1340
+ var PROVIDER_NAME = "cosmosdb";
1331
1341
  var SCHEMA_VERSION = 1;
1332
1342
  var META_VERTEX_ID = "_meta:schema";
1333
1343
  var CosmosDbProvider = class {
1334
1344
  conn;
1335
1345
  config;
1336
1346
  compiler = new import_deep_memory5.GremlinCompiler();
1347
+ reportUsage;
1337
1348
  constructor(config) {
1338
1349
  this.config = config;
1350
+ this.reportUsage = (0, import_deep_memory5.createSafeSink)(config.reportUsage);
1339
1351
  this.conn = new CosmosDbConnection({
1340
1352
  endpoint: config.endpoint,
1341
1353
  key: config.key,
@@ -1346,6 +1358,39 @@ var CosmosDbProvider = class {
1346
1358
  rejectUnauthorized: config.rejectUnauthorized
1347
1359
  });
1348
1360
  }
1361
+ /**
1362
+ * Run the given operation inside an async-local usage scope. While the
1363
+ * operation runs, every `conn.submit()` call accumulates its request charge
1364
+ * into a shared accumulator. When the operation completes (success or
1365
+ * failure), a single {@link OperationUsage} record is emitted to the sink.
1366
+ *
1367
+ * Nested public-method calls (e.g. `traverse` internally calls
1368
+ * `getVocabulary`) are detected: the inner call joins the outer scope
1369
+ * instead of starting a new one, so one user-visible operation produces
1370
+ * exactly one usage record covering all its internal round-trips.
1371
+ *
1372
+ * If no sink is configured, this is a zero-overhead pass-through.
1373
+ */
1374
+ async track(operation, repositoryId, fn) {
1375
+ if (!this.reportUsage) return fn();
1376
+ if (usageScope.getStore()) return fn();
1377
+ const acc = { ru: 0, calls: 0, retries: 0 };
1378
+ try {
1379
+ return await usageScope.run(acc, fn);
1380
+ } finally {
1381
+ if (acc.calls > 0) {
1382
+ this.reportUsage({
1383
+ provider: PROVIDER_NAME,
1384
+ operation,
1385
+ unit: "RU",
1386
+ value: acc.ru,
1387
+ ...repositoryId ? { repositoryId } : {},
1388
+ timestamp: /* @__PURE__ */ new Date(),
1389
+ details: { calls: acc.calls, retries: acc.retries }
1390
+ });
1391
+ }
1392
+ }
1393
+ }
1349
1394
  /**
1350
1395
  * Reject any repositoryId that is not a valid v4 UUID.
1351
1396
  *
@@ -1371,6 +1416,9 @@ var CosmosDbProvider = class {
1371
1416
  await this.conn.close();
1372
1417
  }
1373
1418
  async ensureSchema() {
1419
+ return this.track("ensureSchema", void 0, () => this.ensureSchemaImpl());
1420
+ }
1421
+ async ensureSchemaImpl() {
1374
1422
  const restBase = this.getRestEndpoint();
1375
1423
  const partitionKey = this.config.partitionKey ?? "/repositoryId";
1376
1424
  let databaseCreated = false;
@@ -1449,72 +1497,139 @@ var CosmosDbProvider = class {
1449
1497
  // ─── Repository ────────────────────────────────────────────────────
1450
1498
  async createRepository(config) {
1451
1499
  this.assertValidRepositoryId(config.repositoryId);
1452
- return createRepository(this.conn, config);
1500
+ return this.track(
1501
+ "createRepository",
1502
+ config.repositoryId,
1503
+ () => createRepository(this.conn, config)
1504
+ );
1453
1505
  }
1454
1506
  async getRepository(repositoryId) {
1455
1507
  this.assertValidRepositoryId(repositoryId);
1456
- return getRepository(this.conn, repositoryId);
1508
+ return this.track(
1509
+ "getRepository",
1510
+ repositoryId,
1511
+ () => getRepository(this.conn, repositoryId)
1512
+ );
1457
1513
  }
1458
1514
  async listRepositories(filter) {
1459
- return listRepositories(this.conn, filter);
1515
+ return this.track(
1516
+ "listRepositories",
1517
+ void 0,
1518
+ () => listRepositories(this.conn, filter)
1519
+ );
1460
1520
  }
1461
1521
  async updateRepository(repositoryId, updates) {
1462
1522
  this.assertValidRepositoryId(repositoryId);
1463
- return updateRepository(this.conn, repositoryId, updates);
1523
+ return this.track(
1524
+ "updateRepository",
1525
+ repositoryId,
1526
+ () => updateRepository(this.conn, repositoryId, updates)
1527
+ );
1464
1528
  }
1465
1529
  async deleteRepository(repositoryId, onProgress) {
1466
1530
  this.assertValidRepositoryId(repositoryId);
1467
- return deleteRepository(this.conn, repositoryId, onProgress);
1531
+ return this.track(
1532
+ "deleteRepository",
1533
+ repositoryId,
1534
+ () => deleteRepository(this.conn, repositoryId, onProgress)
1535
+ );
1468
1536
  }
1469
1537
  async deleteAllContents(repositoryId, onProgress) {
1470
1538
  this.assertValidRepositoryId(repositoryId);
1471
- return deleteAllContents(this.conn, repositoryId, onProgress);
1539
+ return this.track(
1540
+ "deleteAllContents",
1541
+ repositoryId,
1542
+ () => deleteAllContents(this.conn, repositoryId, onProgress)
1543
+ );
1472
1544
  }
1473
1545
  async getRepositoryStats(repositoryId) {
1474
1546
  this.assertValidRepositoryId(repositoryId);
1475
- return getRepositoryStats(this.conn, repositoryId);
1547
+ return this.track(
1548
+ "getRepositoryStats",
1549
+ repositoryId,
1550
+ () => getRepositoryStats(this.conn, repositoryId)
1551
+ );
1476
1552
  }
1477
1553
  // ─── Vocabulary ────────────────────────────────────────────────────
1478
1554
  async getVocabulary(repositoryId) {
1479
1555
  this.assertValidRepositoryId(repositoryId);
1480
- return getVocabulary(this.conn, repositoryId);
1556
+ return this.track(
1557
+ "getVocabulary",
1558
+ repositoryId,
1559
+ () => getVocabulary(this.conn, repositoryId)
1560
+ );
1481
1561
  }
1482
1562
  async saveVocabulary(repositoryId, vocabulary) {
1483
1563
  this.assertValidRepositoryId(repositoryId);
1484
- return saveVocabulary(this.conn, repositoryId, vocabulary);
1564
+ return this.track(
1565
+ "saveVocabulary",
1566
+ repositoryId,
1567
+ () => saveVocabulary(this.conn, repositoryId, vocabulary)
1568
+ );
1485
1569
  }
1486
1570
  async getVocabularyChangeLog(repositoryId, options) {
1487
1571
  this.assertValidRepositoryId(repositoryId);
1488
- return getVocabularyChangeLog(this.conn, repositoryId, options);
1572
+ return this.track(
1573
+ "getVocabularyChangeLog",
1574
+ repositoryId,
1575
+ () => getVocabularyChangeLog(this.conn, repositoryId, options)
1576
+ );
1489
1577
  }
1490
1578
  // ─── Entities ──────────────────────────────────────────────────────
1491
1579
  async createEntity(repositoryId, entity) {
1492
1580
  this.assertValidRepositoryId(repositoryId);
1493
- return createEntity(this.conn, repositoryId, entity);
1581
+ return this.track(
1582
+ "createEntity",
1583
+ repositoryId,
1584
+ () => createEntity(this.conn, repositoryId, entity)
1585
+ );
1494
1586
  }
1495
1587
  async getEntity(repositoryId, entityId) {
1496
1588
  this.assertValidRepositoryId(repositoryId);
1497
- return getEntity(this.conn, repositoryId, entityId);
1589
+ return this.track(
1590
+ "getEntity",
1591
+ repositoryId,
1592
+ () => getEntity(this.conn, repositoryId, entityId)
1593
+ );
1498
1594
  }
1499
1595
  async getEntityBySlug(repositoryId, slug) {
1500
1596
  this.assertValidRepositoryId(repositoryId);
1501
- return getEntityBySlug(this.conn, repositoryId, slug);
1597
+ return this.track(
1598
+ "getEntityBySlug",
1599
+ repositoryId,
1600
+ () => getEntityBySlug(this.conn, repositoryId, slug)
1601
+ );
1502
1602
  }
1503
1603
  async getEntities(repositoryId, entityIds) {
1504
1604
  this.assertValidRepositoryId(repositoryId);
1505
- return getEntities(this.conn, repositoryId, entityIds);
1605
+ return this.track(
1606
+ "getEntities",
1607
+ repositoryId,
1608
+ () => getEntities(this.conn, repositoryId, entityIds)
1609
+ );
1506
1610
  }
1507
1611
  async updateEntity(repositoryId, entityId, updates) {
1508
1612
  this.assertValidRepositoryId(repositoryId);
1509
- return updateEntity(this.conn, repositoryId, entityId, updates);
1613
+ return this.track(
1614
+ "updateEntity",
1615
+ repositoryId,
1616
+ () => updateEntity(this.conn, repositoryId, entityId, updates)
1617
+ );
1510
1618
  }
1511
1619
  async deleteEntity(repositoryId, entityId) {
1512
1620
  this.assertValidRepositoryId(repositoryId);
1513
- return deleteEntity(this.conn, repositoryId, entityId);
1621
+ return this.track(
1622
+ "deleteEntity",
1623
+ repositoryId,
1624
+ () => deleteEntity(this.conn, repositoryId, entityId)
1625
+ );
1514
1626
  }
1515
1627
  async deleteEntities(repositoryId, ids) {
1516
1628
  if (ids.length === 0) return { deleted: [], notFound: [] };
1517
1629
  this.assertValidRepositoryId(repositoryId);
1630
+ return this.track("deleteEntities", repositoryId, () => this.deleteEntitiesImpl(repositoryId, ids));
1631
+ }
1632
+ async deleteEntitiesImpl(repositoryId, ids) {
1518
1633
  const deleted = [];
1519
1634
  const CHUNK = 100;
1520
1635
  for (let i = 0; i < ids.length; i += CHUNK) {
@@ -1553,32 +1668,59 @@ var CosmosDbProvider = class {
1553
1668
  }
1554
1669
  async deleteEntitiesByType(repositoryId, entityType) {
1555
1670
  this.assertValidRepositoryId(repositoryId);
1556
- return deleteEntitiesByType(this.conn, repositoryId, entityType);
1671
+ return this.track(
1672
+ "deleteEntitiesByType",
1673
+ repositoryId,
1674
+ () => deleteEntitiesByType(this.conn, repositoryId, entityType)
1675
+ );
1557
1676
  }
1558
1677
  async findEntities(repositoryId, query) {
1559
1678
  this.assertValidRepositoryId(repositoryId);
1560
- return findEntities(this.conn, repositoryId, query);
1679
+ return this.track(
1680
+ "findEntities",
1681
+ repositoryId,
1682
+ () => findEntities(this.conn, repositoryId, query)
1683
+ );
1561
1684
  }
1562
1685
  // ─── Relationships ─────────────────────────────────────────────────
1563
1686
  async createRelationship(repositoryId, relationship) {
1564
1687
  this.assertValidRepositoryId(repositoryId);
1565
- return createRelationship(this.conn, repositoryId, relationship);
1688
+ return this.track(
1689
+ "createRelationship",
1690
+ repositoryId,
1691
+ () => createRelationship(this.conn, repositoryId, relationship)
1692
+ );
1566
1693
  }
1567
1694
  async getRelationship(repositoryId, relationshipId) {
1568
1695
  this.assertValidRepositoryId(repositoryId);
1569
- return getRelationship(this.conn, repositoryId, relationshipId);
1696
+ return this.track(
1697
+ "getRelationship",
1698
+ repositoryId,
1699
+ () => getRelationship(this.conn, repositoryId, relationshipId)
1700
+ );
1570
1701
  }
1571
1702
  async getEntityRelationships(repositoryId, entityId, options) {
1572
1703
  this.assertValidRepositoryId(repositoryId);
1573
- return getEntityRelationships(this.conn, repositoryId, entityId, options);
1704
+ return this.track(
1705
+ "getEntityRelationships",
1706
+ repositoryId,
1707
+ () => getEntityRelationships(this.conn, repositoryId, entityId, options)
1708
+ );
1574
1709
  }
1575
1710
  async deleteRelationship(repositoryId, relationshipId) {
1576
1711
  this.assertValidRepositoryId(repositoryId);
1577
- return deleteRelationship(this.conn, repositoryId, relationshipId);
1712
+ return this.track(
1713
+ "deleteRelationship",
1714
+ repositoryId,
1715
+ () => deleteRelationship(this.conn, repositoryId, relationshipId)
1716
+ );
1578
1717
  }
1579
1718
  async deleteRelationships(repositoryId, ids) {
1580
1719
  if (ids.length === 0) return { deleted: [], notFound: [] };
1581
1720
  this.assertValidRepositoryId(repositoryId);
1721
+ return this.track("deleteRelationships", repositoryId, () => this.deleteRelationshipsImpl(repositoryId, ids));
1722
+ }
1723
+ async deleteRelationshipsImpl(repositoryId, ids) {
1582
1724
  const deleted = [];
1583
1725
  const CHUNK = 100;
1584
1726
  for (let i = 0; i < ids.length; i += CHUNK) {
@@ -1614,30 +1756,99 @@ var CosmosDbProvider = class {
1614
1756
  }
1615
1757
  async deleteRelationshipsByType(repositoryId, relationshipType) {
1616
1758
  this.assertValidRepositoryId(repositoryId);
1617
- return deleteRelationshipsByType(this.conn, repositoryId, relationshipType);
1759
+ return this.track(
1760
+ "deleteRelationshipsByType",
1761
+ repositoryId,
1762
+ () => deleteRelationshipsByType(this.conn, repositoryId, relationshipType)
1763
+ );
1618
1764
  }
1619
1765
  // ─── Graph Traversal (StorageProvider) ─────────────────────────────
1620
1766
  async exploreNeighborhood(repositoryId, entityId, options) {
1621
1767
  this.assertValidRepositoryId(repositoryId);
1622
- return exploreNeighborhood(this.conn, repositoryId, entityId, options);
1768
+ return this.track(
1769
+ "exploreNeighborhood",
1770
+ repositoryId,
1771
+ () => exploreNeighborhood(this.conn, repositoryId, entityId, options)
1772
+ );
1623
1773
  }
1624
1774
  async findPaths(repositoryId, sourceId, targetId, options) {
1625
1775
  this.assertValidRepositoryId(repositoryId);
1626
- return findPaths(this.conn, repositoryId, sourceId, targetId, options);
1776
+ return this.track(
1777
+ "findPaths",
1778
+ repositoryId,
1779
+ () => findPaths(this.conn, repositoryId, sourceId, targetId, options)
1780
+ );
1627
1781
  }
1628
1782
  // ─── Timeline ──────────────────────────────────────────────────────
1629
1783
  async getTimeline(repositoryId, entityId, options) {
1630
1784
  this.assertValidRepositoryId(repositoryId);
1631
- return getTimeline(this.conn, repositoryId, entityId, options);
1785
+ return this.track(
1786
+ "getTimeline",
1787
+ repositoryId,
1788
+ () => getTimeline(this.conn, repositoryId, entityId, options)
1789
+ );
1632
1790
  }
1633
1791
  // ─── Bulk Operations ───────────────────────────────────────────────
1634
1792
  exportAll(repositoryId) {
1635
1793
  this.assertValidRepositoryId(repositoryId);
1636
- return exportAll(this.conn, repositoryId);
1794
+ return this.trackIterable("exportAll", repositoryId, exportAll(this.conn, repositoryId));
1637
1795
  }
1638
1796
  async importBulk(repositoryId, data, options) {
1639
1797
  this.assertValidRepositoryId(repositoryId);
1640
- return importBulk(this.conn, repositoryId, data, options);
1798
+ return this.track(
1799
+ "importBulk",
1800
+ repositoryId,
1801
+ () => importBulk(this.conn, repositoryId, data, options)
1802
+ );
1803
+ }
1804
+ /**
1805
+ * Wrap an AsyncIterable so every emitted chunk is generated inside the
1806
+ * usage scope. On each iteration step, the scope aggregates charges from
1807
+ * the next chunk's submits; when the iterator completes (or is closed), a
1808
+ * single usage record is emitted for the whole stream.
1809
+ */
1810
+ trackIterable(operation, repositoryId, source) {
1811
+ if (!this.reportUsage) return source;
1812
+ const sink = this.reportUsage;
1813
+ return {
1814
+ [Symbol.asyncIterator]: () => {
1815
+ const iter = source[Symbol.asyncIterator]();
1816
+ const acc = { ru: 0, calls: 0, retries: 0 };
1817
+ let emitted = false;
1818
+ const emit = () => {
1819
+ if (emitted) return;
1820
+ emitted = true;
1821
+ if (acc.calls > 0) {
1822
+ sink({
1823
+ provider: PROVIDER_NAME,
1824
+ operation,
1825
+ unit: "RU",
1826
+ value: acc.ru,
1827
+ repositoryId,
1828
+ timestamp: /* @__PURE__ */ new Date(),
1829
+ details: { calls: acc.calls, retries: acc.retries }
1830
+ });
1831
+ }
1832
+ };
1833
+ return {
1834
+ async next() {
1835
+ const step = await usageScope.run(acc, () => iter.next());
1836
+ if (step.done) emit();
1837
+ return step;
1838
+ },
1839
+ async return(value) {
1840
+ emit();
1841
+ if (iter.return) return iter.return(value);
1842
+ return { done: true, value };
1843
+ },
1844
+ async throw(err) {
1845
+ emit();
1846
+ if (iter.throw) return iter.throw(err);
1847
+ throw err;
1848
+ }
1849
+ };
1850
+ }
1851
+ };
1641
1852
  }
1642
1853
  // ─── GraphTraversalProvider ────────────────────────────────────────
1643
1854
  getCapabilities() {
@@ -1655,6 +1866,9 @@ var CosmosDbProvider = class {
1655
1866
  }
1656
1867
  async traverse(repositoryId, spec) {
1657
1868
  this.assertValidRepositoryId(repositoryId);
1869
+ return this.track("traverse", repositoryId, () => this.traverseImpl(repositoryId, spec));
1870
+ }
1871
+ async traverseImpl(repositoryId, spec) {
1658
1872
  const startTime = Date.now();
1659
1873
  const vocabulary = await this.getVocabulary(repositoryId);
1660
1874
  const compiled = this.compiler.compile(spec, vocabulary);
@@ -1748,6 +1962,9 @@ var CosmosDbProvider = class {
1748
1962
  * repositoryId partition predicate.
1749
1963
  */
1750
1964
  async executeNativeQuery(_repositoryId, query, params) {
1965
+ return this.track("executeNativeQuery", void 0, () => this.executeNativeQueryImpl(query, params));
1966
+ }
1967
+ async executeNativeQueryImpl(query, params) {
1751
1968
  const startTime = Date.now();
1752
1969
  try {
1753
1970
  const result = await this.conn.submit(query, params);