@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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { StorageProvider, GraphTraversalProvider, EnsureSchemaResult, GraphTraversalCapabilities } from '@utaba/deep-memory/providers';
2
- import { StorageRepositoryConfig, StoredRepository, RepositoryFilter, PaginatedResult, StoredRepositorySummary, RepositoryUpdate, DeleteProgressCallback, RepositoryStats, MemoryVocabulary, PaginationOptions, VocabularyChangeRecord, StoredEntity, StoredEntityUpdate, StorageFindQuery, StoredRelationship, RelationshipQueryOptions, StorageExploreOptions, StorageNeighborhood, StoragePathOptions, StoragePathResult, StorageTimelineOptions, StorageTimelineResult, ExportChunk, ImportChunk, BulkImportOptions, BulkImportResult, TraversalSpec, TraversalResult } from '@utaba/deep-memory/types';
2
+ import { UsageSink, StorageRepositoryConfig, StoredRepository, RepositoryFilter, PaginatedResult, StoredRepositorySummary, RepositoryUpdate, DeleteProgressCallback, RepositoryStats, MemoryVocabulary, PaginationOptions, VocabularyChangeRecord, StoredEntity, StoredEntityUpdate, StorageFindQuery, StoredRelationship, RelationshipQueryOptions, StorageExploreOptions, StorageNeighborhood, StoragePathOptions, StoragePathResult, StorageTimelineOptions, StorageTimelineResult, ExportChunk, ImportChunk, BulkImportOptions, BulkImportResult, TraversalSpec, TraversalResult } from '@utaba/deep-memory/types';
3
3
  import gremlin from 'gremlin';
4
4
 
5
5
  /** Configuration for CosmosDbProvider. */
@@ -22,6 +22,13 @@ interface CosmosDbProviderConfig {
22
22
  defaultTimeoutMs?: number;
23
23
  /** Whether to reject unauthorized TLS certs — set false for emulator (default: true) */
24
24
  rejectUnauthorized?: boolean;
25
+ /**
26
+ * Optional usage sink. When provided, the provider emits one
27
+ * {@link OperationUsage} record per public method call with the aggregated
28
+ * CosmosDB request charge (RU). Never exposed to AI agents — the MCP server
29
+ * deliberately does not plumb this sink through.
30
+ */
31
+ reportUsage?: UsageSink;
25
32
  }
26
33
  /**
27
34
  * CosmosDB Gremlin storage provider for deep-memory.
@@ -31,7 +38,22 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
31
38
  private readonly conn;
32
39
  private readonly config;
33
40
  private readonly compiler;
41
+ private readonly reportUsage;
34
42
  constructor(config: CosmosDbProviderConfig);
43
+ /**
44
+ * Run the given operation inside an async-local usage scope. While the
45
+ * operation runs, every `conn.submit()` call accumulates its request charge
46
+ * into a shared accumulator. When the operation completes (success or
47
+ * failure), a single {@link OperationUsage} record is emitted to the sink.
48
+ *
49
+ * Nested public-method calls (e.g. `traverse` internally calls
50
+ * `getVocabulary`) are detected: the inner call joins the outer scope
51
+ * instead of starting a new one, so one user-visible operation produces
52
+ * exactly one usage record covering all its internal round-trips.
53
+ *
54
+ * If no sink is configured, this is a zero-overhead pass-through.
55
+ */
56
+ private track;
35
57
  /**
36
58
  * Reject any repositoryId that is not a valid v4 UUID.
37
59
  *
@@ -45,6 +67,7 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
45
67
  initialize(): Promise<void>;
46
68
  dispose(): Promise<void>;
47
69
  ensureSchema(): Promise<EnsureSchemaResult>;
70
+ private ensureSchemaImpl;
48
71
  /** Derive the REST endpoint from config — either explicit or from the Gremlin endpoint host. */
49
72
  private getRestEndpoint;
50
73
  createRepository(config: StorageRepositoryConfig): Promise<StoredRepository>;
@@ -70,6 +93,7 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
70
93
  deleted: string[];
71
94
  notFound: string[];
72
95
  }>;
96
+ private deleteEntitiesImpl;
73
97
  deleteEntitiesByType(repositoryId: string, entityType: string): Promise<{
74
98
  deletedEntities: number;
75
99
  deletedRelationships: number;
@@ -83,6 +107,7 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
83
107
  deleted: string[];
84
108
  notFound: string[];
85
109
  }>;
110
+ private deleteRelationshipsImpl;
86
111
  deleteRelationshipsByType(repositoryId: string, relationshipType: string): Promise<{
87
112
  deletedRelationships: number;
88
113
  }>;
@@ -91,8 +116,16 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
91
116
  getTimeline(repositoryId: string, entityId: string, options: StorageTimelineOptions): Promise<StorageTimelineResult>;
92
117
  exportAll(repositoryId: string): AsyncIterable<ExportChunk>;
93
118
  importBulk(repositoryId: string, data: ImportChunk[], options?: BulkImportOptions): Promise<BulkImportResult>;
119
+ /**
120
+ * Wrap an AsyncIterable so every emitted chunk is generated inside the
121
+ * usage scope. On each iteration step, the scope aggregates charges from
122
+ * the next chunk's submits; when the iterator completes (or is closed), a
123
+ * single usage record is emitted for the whole stream.
124
+ */
125
+ private trackIterable;
94
126
  getCapabilities(): GraphTraversalCapabilities;
95
127
  traverse(repositoryId: string, spec: TraversalSpec): Promise<TraversalResult>;
128
+ private traverseImpl;
96
129
  /**
97
130
  * Execute a raw Gremlin query with caller-supplied bindings.
98
131
  *
@@ -115,6 +148,7 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
115
148
  * repositoryId partition predicate.
116
149
  */
117
150
  executeNativeQuery(_repositoryId: string, query: string, params?: Record<string, unknown>): Promise<TraversalResult>;
151
+ private executeNativeQueryImpl;
118
152
  }
119
153
 
120
154
  interface CosmosDbConnectionConfig {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { StorageProvider, GraphTraversalProvider, EnsureSchemaResult, GraphTraversalCapabilities } from '@utaba/deep-memory/providers';
2
- import { StorageRepositoryConfig, StoredRepository, RepositoryFilter, PaginatedResult, StoredRepositorySummary, RepositoryUpdate, DeleteProgressCallback, RepositoryStats, MemoryVocabulary, PaginationOptions, VocabularyChangeRecord, StoredEntity, StoredEntityUpdate, StorageFindQuery, StoredRelationship, RelationshipQueryOptions, StorageExploreOptions, StorageNeighborhood, StoragePathOptions, StoragePathResult, StorageTimelineOptions, StorageTimelineResult, ExportChunk, ImportChunk, BulkImportOptions, BulkImportResult, TraversalSpec, TraversalResult } from '@utaba/deep-memory/types';
2
+ import { UsageSink, StorageRepositoryConfig, StoredRepository, RepositoryFilter, PaginatedResult, StoredRepositorySummary, RepositoryUpdate, DeleteProgressCallback, RepositoryStats, MemoryVocabulary, PaginationOptions, VocabularyChangeRecord, StoredEntity, StoredEntityUpdate, StorageFindQuery, StoredRelationship, RelationshipQueryOptions, StorageExploreOptions, StorageNeighborhood, StoragePathOptions, StoragePathResult, StorageTimelineOptions, StorageTimelineResult, ExportChunk, ImportChunk, BulkImportOptions, BulkImportResult, TraversalSpec, TraversalResult } from '@utaba/deep-memory/types';
3
3
  import gremlin from 'gremlin';
4
4
 
5
5
  /** Configuration for CosmosDbProvider. */
@@ -22,6 +22,13 @@ interface CosmosDbProviderConfig {
22
22
  defaultTimeoutMs?: number;
23
23
  /** Whether to reject unauthorized TLS certs — set false for emulator (default: true) */
24
24
  rejectUnauthorized?: boolean;
25
+ /**
26
+ * Optional usage sink. When provided, the provider emits one
27
+ * {@link OperationUsage} record per public method call with the aggregated
28
+ * CosmosDB request charge (RU). Never exposed to AI agents — the MCP server
29
+ * deliberately does not plumb this sink through.
30
+ */
31
+ reportUsage?: UsageSink;
25
32
  }
26
33
  /**
27
34
  * CosmosDB Gremlin storage provider for deep-memory.
@@ -31,7 +38,22 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
31
38
  private readonly conn;
32
39
  private readonly config;
33
40
  private readonly compiler;
41
+ private readonly reportUsage;
34
42
  constructor(config: CosmosDbProviderConfig);
43
+ /**
44
+ * Run the given operation inside an async-local usage scope. While the
45
+ * operation runs, every `conn.submit()` call accumulates its request charge
46
+ * into a shared accumulator. When the operation completes (success or
47
+ * failure), a single {@link OperationUsage} record is emitted to the sink.
48
+ *
49
+ * Nested public-method calls (e.g. `traverse` internally calls
50
+ * `getVocabulary`) are detected: the inner call joins the outer scope
51
+ * instead of starting a new one, so one user-visible operation produces
52
+ * exactly one usage record covering all its internal round-trips.
53
+ *
54
+ * If no sink is configured, this is a zero-overhead pass-through.
55
+ */
56
+ private track;
35
57
  /**
36
58
  * Reject any repositoryId that is not a valid v4 UUID.
37
59
  *
@@ -45,6 +67,7 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
45
67
  initialize(): Promise<void>;
46
68
  dispose(): Promise<void>;
47
69
  ensureSchema(): Promise<EnsureSchemaResult>;
70
+ private ensureSchemaImpl;
48
71
  /** Derive the REST endpoint from config — either explicit or from the Gremlin endpoint host. */
49
72
  private getRestEndpoint;
50
73
  createRepository(config: StorageRepositoryConfig): Promise<StoredRepository>;
@@ -70,6 +93,7 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
70
93
  deleted: string[];
71
94
  notFound: string[];
72
95
  }>;
96
+ private deleteEntitiesImpl;
73
97
  deleteEntitiesByType(repositoryId: string, entityType: string): Promise<{
74
98
  deletedEntities: number;
75
99
  deletedRelationships: number;
@@ -83,6 +107,7 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
83
107
  deleted: string[];
84
108
  notFound: string[];
85
109
  }>;
110
+ private deleteRelationshipsImpl;
86
111
  deleteRelationshipsByType(repositoryId: string, relationshipType: string): Promise<{
87
112
  deletedRelationships: number;
88
113
  }>;
@@ -91,8 +116,16 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
91
116
  getTimeline(repositoryId: string, entityId: string, options: StorageTimelineOptions): Promise<StorageTimelineResult>;
92
117
  exportAll(repositoryId: string): AsyncIterable<ExportChunk>;
93
118
  importBulk(repositoryId: string, data: ImportChunk[], options?: BulkImportOptions): Promise<BulkImportResult>;
119
+ /**
120
+ * Wrap an AsyncIterable so every emitted chunk is generated inside the
121
+ * usage scope. On each iteration step, the scope aggregates charges from
122
+ * the next chunk's submits; when the iterator completes (or is closed), a
123
+ * single usage record is emitted for the whole stream.
124
+ */
125
+ private trackIterable;
94
126
  getCapabilities(): GraphTraversalCapabilities;
95
127
  traverse(repositoryId: string, spec: TraversalSpec): Promise<TraversalResult>;
128
+ private traverseImpl;
96
129
  /**
97
130
  * Execute a raw Gremlin query with caller-supplied bindings.
98
131
  *
@@ -115,6 +148,7 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
115
148
  * repositoryId partition predicate.
116
149
  */
117
150
  executeNativeQuery(_repositoryId: string, query: string, params?: Record<string, unknown>): Promise<TraversalResult>;
151
+ private executeNativeQueryImpl;
118
152
  }
119
153
 
120
154
  interface CosmosDbConnectionConfig {
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  // src/CosmosDbProvider.ts
2
2
  import crypto from "crypto";
3
- import { GremlinCompiler, ProviderError, InvalidInputError, isValidUuid, projectEntity } from "@utaba/deep-memory";
3
+ import { GremlinCompiler, ProviderError, InvalidInputError, isValidUuid, projectEntity, createSafeSink } from "@utaba/deep-memory";
4
4
 
5
5
  // src/CosmosDbConnection.ts
6
6
  import gremlin from "gremlin";
7
+ import { AsyncLocalStorage } from "async_hooks";
8
+ var usageScope = new AsyncLocalStorage();
7
9
  var CosmosDbConnection = class {
8
10
  client = null;
9
11
  config;
@@ -51,11 +53,18 @@ var CosmosDbConnection = class {
51
53
  const resultSet = await this.client.submit(query, bindings);
52
54
  const items = resultSet.toArray();
53
55
  const requestCharge = extractRequestCharge(resultSet);
56
+ const acc = usageScope.getStore();
57
+ if (acc) {
58
+ acc.calls++;
59
+ if (typeof requestCharge === "number") acc.ru += requestCharge;
60
+ }
54
61
  return { items, requestCharge };
55
62
  } catch (err) {
56
63
  lastError = err;
57
64
  if (isTransientError(err) && attempt < this.config.maxRetries) {
58
65
  const retryAfterMs = getRetryAfterMs(err, attempt);
66
+ const acc = usageScope.getStore();
67
+ if (acc) acc.retries++;
59
68
  await sleep(retryAfterMs);
60
69
  continue;
61
70
  }
@@ -1291,14 +1300,17 @@ async function upsertRelationship(conn, repositoryId, rel) {
1291
1300
  }
1292
1301
 
1293
1302
  // src/CosmosDbProvider.ts
1303
+ var PROVIDER_NAME = "cosmosdb";
1294
1304
  var SCHEMA_VERSION = 1;
1295
1305
  var META_VERTEX_ID = "_meta:schema";
1296
1306
  var CosmosDbProvider = class {
1297
1307
  conn;
1298
1308
  config;
1299
1309
  compiler = new GremlinCompiler();
1310
+ reportUsage;
1300
1311
  constructor(config) {
1301
1312
  this.config = config;
1313
+ this.reportUsage = createSafeSink(config.reportUsage);
1302
1314
  this.conn = new CosmosDbConnection({
1303
1315
  endpoint: config.endpoint,
1304
1316
  key: config.key,
@@ -1309,6 +1321,39 @@ var CosmosDbProvider = class {
1309
1321
  rejectUnauthorized: config.rejectUnauthorized
1310
1322
  });
1311
1323
  }
1324
+ /**
1325
+ * Run the given operation inside an async-local usage scope. While the
1326
+ * operation runs, every `conn.submit()` call accumulates its request charge
1327
+ * into a shared accumulator. When the operation completes (success or
1328
+ * failure), a single {@link OperationUsage} record is emitted to the sink.
1329
+ *
1330
+ * Nested public-method calls (e.g. `traverse` internally calls
1331
+ * `getVocabulary`) are detected: the inner call joins the outer scope
1332
+ * instead of starting a new one, so one user-visible operation produces
1333
+ * exactly one usage record covering all its internal round-trips.
1334
+ *
1335
+ * If no sink is configured, this is a zero-overhead pass-through.
1336
+ */
1337
+ async track(operation, repositoryId, fn) {
1338
+ if (!this.reportUsage) return fn();
1339
+ if (usageScope.getStore()) return fn();
1340
+ const acc = { ru: 0, calls: 0, retries: 0 };
1341
+ try {
1342
+ return await usageScope.run(acc, fn);
1343
+ } finally {
1344
+ if (acc.calls > 0) {
1345
+ this.reportUsage({
1346
+ provider: PROVIDER_NAME,
1347
+ operation,
1348
+ unit: "RU",
1349
+ value: acc.ru,
1350
+ ...repositoryId ? { repositoryId } : {},
1351
+ timestamp: /* @__PURE__ */ new Date(),
1352
+ details: { calls: acc.calls, retries: acc.retries }
1353
+ });
1354
+ }
1355
+ }
1356
+ }
1312
1357
  /**
1313
1358
  * Reject any repositoryId that is not a valid v4 UUID.
1314
1359
  *
@@ -1334,6 +1379,9 @@ var CosmosDbProvider = class {
1334
1379
  await this.conn.close();
1335
1380
  }
1336
1381
  async ensureSchema() {
1382
+ return this.track("ensureSchema", void 0, () => this.ensureSchemaImpl());
1383
+ }
1384
+ async ensureSchemaImpl() {
1337
1385
  const restBase = this.getRestEndpoint();
1338
1386
  const partitionKey = this.config.partitionKey ?? "/repositoryId";
1339
1387
  let databaseCreated = false;
@@ -1412,72 +1460,139 @@ var CosmosDbProvider = class {
1412
1460
  // ─── Repository ────────────────────────────────────────────────────
1413
1461
  async createRepository(config) {
1414
1462
  this.assertValidRepositoryId(config.repositoryId);
1415
- return createRepository(this.conn, config);
1463
+ return this.track(
1464
+ "createRepository",
1465
+ config.repositoryId,
1466
+ () => createRepository(this.conn, config)
1467
+ );
1416
1468
  }
1417
1469
  async getRepository(repositoryId) {
1418
1470
  this.assertValidRepositoryId(repositoryId);
1419
- return getRepository(this.conn, repositoryId);
1471
+ return this.track(
1472
+ "getRepository",
1473
+ repositoryId,
1474
+ () => getRepository(this.conn, repositoryId)
1475
+ );
1420
1476
  }
1421
1477
  async listRepositories(filter) {
1422
- return listRepositories(this.conn, filter);
1478
+ return this.track(
1479
+ "listRepositories",
1480
+ void 0,
1481
+ () => listRepositories(this.conn, filter)
1482
+ );
1423
1483
  }
1424
1484
  async updateRepository(repositoryId, updates) {
1425
1485
  this.assertValidRepositoryId(repositoryId);
1426
- return updateRepository(this.conn, repositoryId, updates);
1486
+ return this.track(
1487
+ "updateRepository",
1488
+ repositoryId,
1489
+ () => updateRepository(this.conn, repositoryId, updates)
1490
+ );
1427
1491
  }
1428
1492
  async deleteRepository(repositoryId, onProgress) {
1429
1493
  this.assertValidRepositoryId(repositoryId);
1430
- return deleteRepository(this.conn, repositoryId, onProgress);
1494
+ return this.track(
1495
+ "deleteRepository",
1496
+ repositoryId,
1497
+ () => deleteRepository(this.conn, repositoryId, onProgress)
1498
+ );
1431
1499
  }
1432
1500
  async deleteAllContents(repositoryId, onProgress) {
1433
1501
  this.assertValidRepositoryId(repositoryId);
1434
- return deleteAllContents(this.conn, repositoryId, onProgress);
1502
+ return this.track(
1503
+ "deleteAllContents",
1504
+ repositoryId,
1505
+ () => deleteAllContents(this.conn, repositoryId, onProgress)
1506
+ );
1435
1507
  }
1436
1508
  async getRepositoryStats(repositoryId) {
1437
1509
  this.assertValidRepositoryId(repositoryId);
1438
- return getRepositoryStats(this.conn, repositoryId);
1510
+ return this.track(
1511
+ "getRepositoryStats",
1512
+ repositoryId,
1513
+ () => getRepositoryStats(this.conn, repositoryId)
1514
+ );
1439
1515
  }
1440
1516
  // ─── Vocabulary ────────────────────────────────────────────────────
1441
1517
  async getVocabulary(repositoryId) {
1442
1518
  this.assertValidRepositoryId(repositoryId);
1443
- return getVocabulary(this.conn, repositoryId);
1519
+ return this.track(
1520
+ "getVocabulary",
1521
+ repositoryId,
1522
+ () => getVocabulary(this.conn, repositoryId)
1523
+ );
1444
1524
  }
1445
1525
  async saveVocabulary(repositoryId, vocabulary) {
1446
1526
  this.assertValidRepositoryId(repositoryId);
1447
- return saveVocabulary(this.conn, repositoryId, vocabulary);
1527
+ return this.track(
1528
+ "saveVocabulary",
1529
+ repositoryId,
1530
+ () => saveVocabulary(this.conn, repositoryId, vocabulary)
1531
+ );
1448
1532
  }
1449
1533
  async getVocabularyChangeLog(repositoryId, options) {
1450
1534
  this.assertValidRepositoryId(repositoryId);
1451
- return getVocabularyChangeLog(this.conn, repositoryId, options);
1535
+ return this.track(
1536
+ "getVocabularyChangeLog",
1537
+ repositoryId,
1538
+ () => getVocabularyChangeLog(this.conn, repositoryId, options)
1539
+ );
1452
1540
  }
1453
1541
  // ─── Entities ──────────────────────────────────────────────────────
1454
1542
  async createEntity(repositoryId, entity) {
1455
1543
  this.assertValidRepositoryId(repositoryId);
1456
- return createEntity(this.conn, repositoryId, entity);
1544
+ return this.track(
1545
+ "createEntity",
1546
+ repositoryId,
1547
+ () => createEntity(this.conn, repositoryId, entity)
1548
+ );
1457
1549
  }
1458
1550
  async getEntity(repositoryId, entityId) {
1459
1551
  this.assertValidRepositoryId(repositoryId);
1460
- return getEntity(this.conn, repositoryId, entityId);
1552
+ return this.track(
1553
+ "getEntity",
1554
+ repositoryId,
1555
+ () => getEntity(this.conn, repositoryId, entityId)
1556
+ );
1461
1557
  }
1462
1558
  async getEntityBySlug(repositoryId, slug) {
1463
1559
  this.assertValidRepositoryId(repositoryId);
1464
- return getEntityBySlug(this.conn, repositoryId, slug);
1560
+ return this.track(
1561
+ "getEntityBySlug",
1562
+ repositoryId,
1563
+ () => getEntityBySlug(this.conn, repositoryId, slug)
1564
+ );
1465
1565
  }
1466
1566
  async getEntities(repositoryId, entityIds) {
1467
1567
  this.assertValidRepositoryId(repositoryId);
1468
- return getEntities(this.conn, repositoryId, entityIds);
1568
+ return this.track(
1569
+ "getEntities",
1570
+ repositoryId,
1571
+ () => getEntities(this.conn, repositoryId, entityIds)
1572
+ );
1469
1573
  }
1470
1574
  async updateEntity(repositoryId, entityId, updates) {
1471
1575
  this.assertValidRepositoryId(repositoryId);
1472
- return updateEntity(this.conn, repositoryId, entityId, updates);
1576
+ return this.track(
1577
+ "updateEntity",
1578
+ repositoryId,
1579
+ () => updateEntity(this.conn, repositoryId, entityId, updates)
1580
+ );
1473
1581
  }
1474
1582
  async deleteEntity(repositoryId, entityId) {
1475
1583
  this.assertValidRepositoryId(repositoryId);
1476
- return deleteEntity(this.conn, repositoryId, entityId);
1584
+ return this.track(
1585
+ "deleteEntity",
1586
+ repositoryId,
1587
+ () => deleteEntity(this.conn, repositoryId, entityId)
1588
+ );
1477
1589
  }
1478
1590
  async deleteEntities(repositoryId, ids) {
1479
1591
  if (ids.length === 0) return { deleted: [], notFound: [] };
1480
1592
  this.assertValidRepositoryId(repositoryId);
1593
+ return this.track("deleteEntities", repositoryId, () => this.deleteEntitiesImpl(repositoryId, ids));
1594
+ }
1595
+ async deleteEntitiesImpl(repositoryId, ids) {
1481
1596
  const deleted = [];
1482
1597
  const CHUNK = 100;
1483
1598
  for (let i = 0; i < ids.length; i += CHUNK) {
@@ -1516,32 +1631,59 @@ var CosmosDbProvider = class {
1516
1631
  }
1517
1632
  async deleteEntitiesByType(repositoryId, entityType) {
1518
1633
  this.assertValidRepositoryId(repositoryId);
1519
- return deleteEntitiesByType(this.conn, repositoryId, entityType);
1634
+ return this.track(
1635
+ "deleteEntitiesByType",
1636
+ repositoryId,
1637
+ () => deleteEntitiesByType(this.conn, repositoryId, entityType)
1638
+ );
1520
1639
  }
1521
1640
  async findEntities(repositoryId, query) {
1522
1641
  this.assertValidRepositoryId(repositoryId);
1523
- return findEntities(this.conn, repositoryId, query);
1642
+ return this.track(
1643
+ "findEntities",
1644
+ repositoryId,
1645
+ () => findEntities(this.conn, repositoryId, query)
1646
+ );
1524
1647
  }
1525
1648
  // ─── Relationships ─────────────────────────────────────────────────
1526
1649
  async createRelationship(repositoryId, relationship) {
1527
1650
  this.assertValidRepositoryId(repositoryId);
1528
- return createRelationship(this.conn, repositoryId, relationship);
1651
+ return this.track(
1652
+ "createRelationship",
1653
+ repositoryId,
1654
+ () => createRelationship(this.conn, repositoryId, relationship)
1655
+ );
1529
1656
  }
1530
1657
  async getRelationship(repositoryId, relationshipId) {
1531
1658
  this.assertValidRepositoryId(repositoryId);
1532
- return getRelationship(this.conn, repositoryId, relationshipId);
1659
+ return this.track(
1660
+ "getRelationship",
1661
+ repositoryId,
1662
+ () => getRelationship(this.conn, repositoryId, relationshipId)
1663
+ );
1533
1664
  }
1534
1665
  async getEntityRelationships(repositoryId, entityId, options) {
1535
1666
  this.assertValidRepositoryId(repositoryId);
1536
- return getEntityRelationships(this.conn, repositoryId, entityId, options);
1667
+ return this.track(
1668
+ "getEntityRelationships",
1669
+ repositoryId,
1670
+ () => getEntityRelationships(this.conn, repositoryId, entityId, options)
1671
+ );
1537
1672
  }
1538
1673
  async deleteRelationship(repositoryId, relationshipId) {
1539
1674
  this.assertValidRepositoryId(repositoryId);
1540
- return deleteRelationship(this.conn, repositoryId, relationshipId);
1675
+ return this.track(
1676
+ "deleteRelationship",
1677
+ repositoryId,
1678
+ () => deleteRelationship(this.conn, repositoryId, relationshipId)
1679
+ );
1541
1680
  }
1542
1681
  async deleteRelationships(repositoryId, ids) {
1543
1682
  if (ids.length === 0) return { deleted: [], notFound: [] };
1544
1683
  this.assertValidRepositoryId(repositoryId);
1684
+ return this.track("deleteRelationships", repositoryId, () => this.deleteRelationshipsImpl(repositoryId, ids));
1685
+ }
1686
+ async deleteRelationshipsImpl(repositoryId, ids) {
1545
1687
  const deleted = [];
1546
1688
  const CHUNK = 100;
1547
1689
  for (let i = 0; i < ids.length; i += CHUNK) {
@@ -1577,30 +1719,99 @@ var CosmosDbProvider = class {
1577
1719
  }
1578
1720
  async deleteRelationshipsByType(repositoryId, relationshipType) {
1579
1721
  this.assertValidRepositoryId(repositoryId);
1580
- return deleteRelationshipsByType(this.conn, repositoryId, relationshipType);
1722
+ return this.track(
1723
+ "deleteRelationshipsByType",
1724
+ repositoryId,
1725
+ () => deleteRelationshipsByType(this.conn, repositoryId, relationshipType)
1726
+ );
1581
1727
  }
1582
1728
  // ─── Graph Traversal (StorageProvider) ─────────────────────────────
1583
1729
  async exploreNeighborhood(repositoryId, entityId, options) {
1584
1730
  this.assertValidRepositoryId(repositoryId);
1585
- return exploreNeighborhood(this.conn, repositoryId, entityId, options);
1731
+ return this.track(
1732
+ "exploreNeighborhood",
1733
+ repositoryId,
1734
+ () => exploreNeighborhood(this.conn, repositoryId, entityId, options)
1735
+ );
1586
1736
  }
1587
1737
  async findPaths(repositoryId, sourceId, targetId, options) {
1588
1738
  this.assertValidRepositoryId(repositoryId);
1589
- return findPaths(this.conn, repositoryId, sourceId, targetId, options);
1739
+ return this.track(
1740
+ "findPaths",
1741
+ repositoryId,
1742
+ () => findPaths(this.conn, repositoryId, sourceId, targetId, options)
1743
+ );
1590
1744
  }
1591
1745
  // ─── Timeline ──────────────────────────────────────────────────────
1592
1746
  async getTimeline(repositoryId, entityId, options) {
1593
1747
  this.assertValidRepositoryId(repositoryId);
1594
- return getTimeline(this.conn, repositoryId, entityId, options);
1748
+ return this.track(
1749
+ "getTimeline",
1750
+ repositoryId,
1751
+ () => getTimeline(this.conn, repositoryId, entityId, options)
1752
+ );
1595
1753
  }
1596
1754
  // ─── Bulk Operations ───────────────────────────────────────────────
1597
1755
  exportAll(repositoryId) {
1598
1756
  this.assertValidRepositoryId(repositoryId);
1599
- return exportAll(this.conn, repositoryId);
1757
+ return this.trackIterable("exportAll", repositoryId, exportAll(this.conn, repositoryId));
1600
1758
  }
1601
1759
  async importBulk(repositoryId, data, options) {
1602
1760
  this.assertValidRepositoryId(repositoryId);
1603
- return importBulk(this.conn, repositoryId, data, options);
1761
+ return this.track(
1762
+ "importBulk",
1763
+ repositoryId,
1764
+ () => importBulk(this.conn, repositoryId, data, options)
1765
+ );
1766
+ }
1767
+ /**
1768
+ * Wrap an AsyncIterable so every emitted chunk is generated inside the
1769
+ * usage scope. On each iteration step, the scope aggregates charges from
1770
+ * the next chunk's submits; when the iterator completes (or is closed), a
1771
+ * single usage record is emitted for the whole stream.
1772
+ */
1773
+ trackIterable(operation, repositoryId, source) {
1774
+ if (!this.reportUsage) return source;
1775
+ const sink = this.reportUsage;
1776
+ return {
1777
+ [Symbol.asyncIterator]: () => {
1778
+ const iter = source[Symbol.asyncIterator]();
1779
+ const acc = { ru: 0, calls: 0, retries: 0 };
1780
+ let emitted = false;
1781
+ const emit = () => {
1782
+ if (emitted) return;
1783
+ emitted = true;
1784
+ if (acc.calls > 0) {
1785
+ sink({
1786
+ provider: PROVIDER_NAME,
1787
+ operation,
1788
+ unit: "RU",
1789
+ value: acc.ru,
1790
+ repositoryId,
1791
+ timestamp: /* @__PURE__ */ new Date(),
1792
+ details: { calls: acc.calls, retries: acc.retries }
1793
+ });
1794
+ }
1795
+ };
1796
+ return {
1797
+ async next() {
1798
+ const step = await usageScope.run(acc, () => iter.next());
1799
+ if (step.done) emit();
1800
+ return step;
1801
+ },
1802
+ async return(value) {
1803
+ emit();
1804
+ if (iter.return) return iter.return(value);
1805
+ return { done: true, value };
1806
+ },
1807
+ async throw(err) {
1808
+ emit();
1809
+ if (iter.throw) return iter.throw(err);
1810
+ throw err;
1811
+ }
1812
+ };
1813
+ }
1814
+ };
1604
1815
  }
1605
1816
  // ─── GraphTraversalProvider ────────────────────────────────────────
1606
1817
  getCapabilities() {
@@ -1618,6 +1829,9 @@ var CosmosDbProvider = class {
1618
1829
  }
1619
1830
  async traverse(repositoryId, spec) {
1620
1831
  this.assertValidRepositoryId(repositoryId);
1832
+ return this.track("traverse", repositoryId, () => this.traverseImpl(repositoryId, spec));
1833
+ }
1834
+ async traverseImpl(repositoryId, spec) {
1621
1835
  const startTime = Date.now();
1622
1836
  const vocabulary = await this.getVocabulary(repositoryId);
1623
1837
  const compiled = this.compiler.compile(spec, vocabulary);
@@ -1711,6 +1925,9 @@ var CosmosDbProvider = class {
1711
1925
  * repositoryId partition predicate.
1712
1926
  */
1713
1927
  async executeNativeQuery(_repositoryId, query, params) {
1928
+ return this.track("executeNativeQuery", void 0, () => this.executeNativeQueryImpl(query, params));
1929
+ }
1930
+ async executeNativeQueryImpl(query, params) {
1714
1931
  const startTime = Date.now();
1715
1932
  try {
1716
1933
  const result = await this.conn.submit(query, params);