@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.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
  }
@@ -873,6 +882,7 @@ async function exploreNeighborhood(conn, repositoryId, entityId, options) {
873
882
  for (let depth = 0; depth < options.depth; depth++) {
874
883
  const layer = {};
875
884
  const nextFrontier = /* @__PURE__ */ new Set();
885
+ const layerCache = /* @__PURE__ */ new Map();
876
886
  for (const currentId of frontier) {
877
887
  const rels = await getRelationshipsForEntity(conn, repositoryId, currentId, options);
878
888
  for (const rel of rels) {
@@ -886,20 +896,20 @@ async function exploreNeighborhood(conn, repositoryId, entityId, options) {
886
896
  } else {
887
897
  connectedId = rel.sourceEntityId;
888
898
  }
889
- if (!visited.has(connectedId)) {
890
- if (options.entityTypes && options.entityTypes.length > 0) {
891
- const entity = await getEntityLight(conn, repositoryId, connectedId);
892
- if (!entity || !options.entityTypes.includes(entity.entityType)) continue;
893
- layer[relType].entities.push(entity);
899
+ if (visited.has(connectedId)) continue;
900
+ let entity = layerCache.get(connectedId);
901
+ if (entity === void 0) {
902
+ const loaded = await getEntityLight(conn, repositoryId, connectedId);
903
+ if (loaded && (!options.entityTypes || options.entityTypes.length === 0 || options.entityTypes.includes(loaded.entityType))) {
904
+ entity = loaded;
894
905
  } else {
895
- const entity = await getEntityLight(conn, repositoryId, connectedId);
896
- if (entity) {
897
- layer[relType].entities.push(entity);
898
- }
906
+ entity = null;
899
907
  }
900
- visited.add(connectedId);
901
- nextFrontier.add(connectedId);
908
+ layerCache.set(connectedId, entity);
909
+ if (entity) nextFrontier.add(connectedId);
902
910
  }
911
+ if (!entity) continue;
912
+ layer[relType].entities.push(entity);
903
913
  layer[relType].relationships.push(rel);
904
914
  layer[relType].total = layer[relType].entities.length;
905
915
  }
@@ -914,6 +924,9 @@ async function exploreNeighborhood(conn, repositoryId, entityId, options) {
914
924
  if (Object.keys(layer).length > 0) {
915
925
  layers.push(layer);
916
926
  }
927
+ for (const id of nextFrontier) {
928
+ visited.add(id);
929
+ }
917
930
  frontier = nextFrontier;
918
931
  if (frontier.size === 0) break;
919
932
  }
@@ -1291,14 +1304,17 @@ async function upsertRelationship(conn, repositoryId, rel) {
1291
1304
  }
1292
1305
 
1293
1306
  // src/CosmosDbProvider.ts
1307
+ var PROVIDER_NAME = "cosmosdb";
1294
1308
  var SCHEMA_VERSION = 1;
1295
1309
  var META_VERTEX_ID = "_meta:schema";
1296
1310
  var CosmosDbProvider = class {
1297
1311
  conn;
1298
1312
  config;
1299
1313
  compiler = new GremlinCompiler();
1314
+ reportUsage;
1300
1315
  constructor(config) {
1301
1316
  this.config = config;
1317
+ this.reportUsage = createSafeSink(config.reportUsage);
1302
1318
  this.conn = new CosmosDbConnection({
1303
1319
  endpoint: config.endpoint,
1304
1320
  key: config.key,
@@ -1309,6 +1325,39 @@ var CosmosDbProvider = class {
1309
1325
  rejectUnauthorized: config.rejectUnauthorized
1310
1326
  });
1311
1327
  }
1328
+ /**
1329
+ * Run the given operation inside an async-local usage scope. While the
1330
+ * operation runs, every `conn.submit()` call accumulates its request charge
1331
+ * into a shared accumulator. When the operation completes (success or
1332
+ * failure), a single {@link OperationUsage} record is emitted to the sink.
1333
+ *
1334
+ * Nested public-method calls (e.g. `traverse` internally calls
1335
+ * `getVocabulary`) are detected: the inner call joins the outer scope
1336
+ * instead of starting a new one, so one user-visible operation produces
1337
+ * exactly one usage record covering all its internal round-trips.
1338
+ *
1339
+ * If no sink is configured, this is a zero-overhead pass-through.
1340
+ */
1341
+ async track(operation, repositoryId, fn) {
1342
+ if (!this.reportUsage) return fn();
1343
+ if (usageScope.getStore()) return fn();
1344
+ const acc = { ru: 0, calls: 0, retries: 0 };
1345
+ try {
1346
+ return await usageScope.run(acc, fn);
1347
+ } finally {
1348
+ if (acc.calls > 0) {
1349
+ this.reportUsage({
1350
+ provider: PROVIDER_NAME,
1351
+ operation,
1352
+ unit: "RU",
1353
+ value: acc.ru,
1354
+ ...repositoryId ? { repositoryId } : {},
1355
+ timestamp: /* @__PURE__ */ new Date(),
1356
+ details: { calls: acc.calls, retries: acc.retries }
1357
+ });
1358
+ }
1359
+ }
1360
+ }
1312
1361
  /**
1313
1362
  * Reject any repositoryId that is not a valid v4 UUID.
1314
1363
  *
@@ -1334,6 +1383,9 @@ var CosmosDbProvider = class {
1334
1383
  await this.conn.close();
1335
1384
  }
1336
1385
  async ensureSchema() {
1386
+ return this.track("ensureSchema", void 0, () => this.ensureSchemaImpl());
1387
+ }
1388
+ async ensureSchemaImpl() {
1337
1389
  const restBase = this.getRestEndpoint();
1338
1390
  const partitionKey = this.config.partitionKey ?? "/repositoryId";
1339
1391
  let databaseCreated = false;
@@ -1412,72 +1464,139 @@ var CosmosDbProvider = class {
1412
1464
  // ─── Repository ────────────────────────────────────────────────────
1413
1465
  async createRepository(config) {
1414
1466
  this.assertValidRepositoryId(config.repositoryId);
1415
- return createRepository(this.conn, config);
1467
+ return this.track(
1468
+ "createRepository",
1469
+ config.repositoryId,
1470
+ () => createRepository(this.conn, config)
1471
+ );
1416
1472
  }
1417
1473
  async getRepository(repositoryId) {
1418
1474
  this.assertValidRepositoryId(repositoryId);
1419
- return getRepository(this.conn, repositoryId);
1475
+ return this.track(
1476
+ "getRepository",
1477
+ repositoryId,
1478
+ () => getRepository(this.conn, repositoryId)
1479
+ );
1420
1480
  }
1421
1481
  async listRepositories(filter) {
1422
- return listRepositories(this.conn, filter);
1482
+ return this.track(
1483
+ "listRepositories",
1484
+ void 0,
1485
+ () => listRepositories(this.conn, filter)
1486
+ );
1423
1487
  }
1424
1488
  async updateRepository(repositoryId, updates) {
1425
1489
  this.assertValidRepositoryId(repositoryId);
1426
- return updateRepository(this.conn, repositoryId, updates);
1490
+ return this.track(
1491
+ "updateRepository",
1492
+ repositoryId,
1493
+ () => updateRepository(this.conn, repositoryId, updates)
1494
+ );
1427
1495
  }
1428
1496
  async deleteRepository(repositoryId, onProgress) {
1429
1497
  this.assertValidRepositoryId(repositoryId);
1430
- return deleteRepository(this.conn, repositoryId, onProgress);
1498
+ return this.track(
1499
+ "deleteRepository",
1500
+ repositoryId,
1501
+ () => deleteRepository(this.conn, repositoryId, onProgress)
1502
+ );
1431
1503
  }
1432
1504
  async deleteAllContents(repositoryId, onProgress) {
1433
1505
  this.assertValidRepositoryId(repositoryId);
1434
- return deleteAllContents(this.conn, repositoryId, onProgress);
1506
+ return this.track(
1507
+ "deleteAllContents",
1508
+ repositoryId,
1509
+ () => deleteAllContents(this.conn, repositoryId, onProgress)
1510
+ );
1435
1511
  }
1436
1512
  async getRepositoryStats(repositoryId) {
1437
1513
  this.assertValidRepositoryId(repositoryId);
1438
- return getRepositoryStats(this.conn, repositoryId);
1514
+ return this.track(
1515
+ "getRepositoryStats",
1516
+ repositoryId,
1517
+ () => getRepositoryStats(this.conn, repositoryId)
1518
+ );
1439
1519
  }
1440
1520
  // ─── Vocabulary ────────────────────────────────────────────────────
1441
1521
  async getVocabulary(repositoryId) {
1442
1522
  this.assertValidRepositoryId(repositoryId);
1443
- return getVocabulary(this.conn, repositoryId);
1523
+ return this.track(
1524
+ "getVocabulary",
1525
+ repositoryId,
1526
+ () => getVocabulary(this.conn, repositoryId)
1527
+ );
1444
1528
  }
1445
1529
  async saveVocabulary(repositoryId, vocabulary) {
1446
1530
  this.assertValidRepositoryId(repositoryId);
1447
- return saveVocabulary(this.conn, repositoryId, vocabulary);
1531
+ return this.track(
1532
+ "saveVocabulary",
1533
+ repositoryId,
1534
+ () => saveVocabulary(this.conn, repositoryId, vocabulary)
1535
+ );
1448
1536
  }
1449
1537
  async getVocabularyChangeLog(repositoryId, options) {
1450
1538
  this.assertValidRepositoryId(repositoryId);
1451
- return getVocabularyChangeLog(this.conn, repositoryId, options);
1539
+ return this.track(
1540
+ "getVocabularyChangeLog",
1541
+ repositoryId,
1542
+ () => getVocabularyChangeLog(this.conn, repositoryId, options)
1543
+ );
1452
1544
  }
1453
1545
  // ─── Entities ──────────────────────────────────────────────────────
1454
1546
  async createEntity(repositoryId, entity) {
1455
1547
  this.assertValidRepositoryId(repositoryId);
1456
- return createEntity(this.conn, repositoryId, entity);
1548
+ return this.track(
1549
+ "createEntity",
1550
+ repositoryId,
1551
+ () => createEntity(this.conn, repositoryId, entity)
1552
+ );
1457
1553
  }
1458
1554
  async getEntity(repositoryId, entityId) {
1459
1555
  this.assertValidRepositoryId(repositoryId);
1460
- return getEntity(this.conn, repositoryId, entityId);
1556
+ return this.track(
1557
+ "getEntity",
1558
+ repositoryId,
1559
+ () => getEntity(this.conn, repositoryId, entityId)
1560
+ );
1461
1561
  }
1462
1562
  async getEntityBySlug(repositoryId, slug) {
1463
1563
  this.assertValidRepositoryId(repositoryId);
1464
- return getEntityBySlug(this.conn, repositoryId, slug);
1564
+ return this.track(
1565
+ "getEntityBySlug",
1566
+ repositoryId,
1567
+ () => getEntityBySlug(this.conn, repositoryId, slug)
1568
+ );
1465
1569
  }
1466
1570
  async getEntities(repositoryId, entityIds) {
1467
1571
  this.assertValidRepositoryId(repositoryId);
1468
- return getEntities(this.conn, repositoryId, entityIds);
1572
+ return this.track(
1573
+ "getEntities",
1574
+ repositoryId,
1575
+ () => getEntities(this.conn, repositoryId, entityIds)
1576
+ );
1469
1577
  }
1470
1578
  async updateEntity(repositoryId, entityId, updates) {
1471
1579
  this.assertValidRepositoryId(repositoryId);
1472
- return updateEntity(this.conn, repositoryId, entityId, updates);
1580
+ return this.track(
1581
+ "updateEntity",
1582
+ repositoryId,
1583
+ () => updateEntity(this.conn, repositoryId, entityId, updates)
1584
+ );
1473
1585
  }
1474
1586
  async deleteEntity(repositoryId, entityId) {
1475
1587
  this.assertValidRepositoryId(repositoryId);
1476
- return deleteEntity(this.conn, repositoryId, entityId);
1588
+ return this.track(
1589
+ "deleteEntity",
1590
+ repositoryId,
1591
+ () => deleteEntity(this.conn, repositoryId, entityId)
1592
+ );
1477
1593
  }
1478
1594
  async deleteEntities(repositoryId, ids) {
1479
1595
  if (ids.length === 0) return { deleted: [], notFound: [] };
1480
1596
  this.assertValidRepositoryId(repositoryId);
1597
+ return this.track("deleteEntities", repositoryId, () => this.deleteEntitiesImpl(repositoryId, ids));
1598
+ }
1599
+ async deleteEntitiesImpl(repositoryId, ids) {
1481
1600
  const deleted = [];
1482
1601
  const CHUNK = 100;
1483
1602
  for (let i = 0; i < ids.length; i += CHUNK) {
@@ -1516,32 +1635,59 @@ var CosmosDbProvider = class {
1516
1635
  }
1517
1636
  async deleteEntitiesByType(repositoryId, entityType) {
1518
1637
  this.assertValidRepositoryId(repositoryId);
1519
- return deleteEntitiesByType(this.conn, repositoryId, entityType);
1638
+ return this.track(
1639
+ "deleteEntitiesByType",
1640
+ repositoryId,
1641
+ () => deleteEntitiesByType(this.conn, repositoryId, entityType)
1642
+ );
1520
1643
  }
1521
1644
  async findEntities(repositoryId, query) {
1522
1645
  this.assertValidRepositoryId(repositoryId);
1523
- return findEntities(this.conn, repositoryId, query);
1646
+ return this.track(
1647
+ "findEntities",
1648
+ repositoryId,
1649
+ () => findEntities(this.conn, repositoryId, query)
1650
+ );
1524
1651
  }
1525
1652
  // ─── Relationships ─────────────────────────────────────────────────
1526
1653
  async createRelationship(repositoryId, relationship) {
1527
1654
  this.assertValidRepositoryId(repositoryId);
1528
- return createRelationship(this.conn, repositoryId, relationship);
1655
+ return this.track(
1656
+ "createRelationship",
1657
+ repositoryId,
1658
+ () => createRelationship(this.conn, repositoryId, relationship)
1659
+ );
1529
1660
  }
1530
1661
  async getRelationship(repositoryId, relationshipId) {
1531
1662
  this.assertValidRepositoryId(repositoryId);
1532
- return getRelationship(this.conn, repositoryId, relationshipId);
1663
+ return this.track(
1664
+ "getRelationship",
1665
+ repositoryId,
1666
+ () => getRelationship(this.conn, repositoryId, relationshipId)
1667
+ );
1533
1668
  }
1534
1669
  async getEntityRelationships(repositoryId, entityId, options) {
1535
1670
  this.assertValidRepositoryId(repositoryId);
1536
- return getEntityRelationships(this.conn, repositoryId, entityId, options);
1671
+ return this.track(
1672
+ "getEntityRelationships",
1673
+ repositoryId,
1674
+ () => getEntityRelationships(this.conn, repositoryId, entityId, options)
1675
+ );
1537
1676
  }
1538
1677
  async deleteRelationship(repositoryId, relationshipId) {
1539
1678
  this.assertValidRepositoryId(repositoryId);
1540
- return deleteRelationship(this.conn, repositoryId, relationshipId);
1679
+ return this.track(
1680
+ "deleteRelationship",
1681
+ repositoryId,
1682
+ () => deleteRelationship(this.conn, repositoryId, relationshipId)
1683
+ );
1541
1684
  }
1542
1685
  async deleteRelationships(repositoryId, ids) {
1543
1686
  if (ids.length === 0) return { deleted: [], notFound: [] };
1544
1687
  this.assertValidRepositoryId(repositoryId);
1688
+ return this.track("deleteRelationships", repositoryId, () => this.deleteRelationshipsImpl(repositoryId, ids));
1689
+ }
1690
+ async deleteRelationshipsImpl(repositoryId, ids) {
1545
1691
  const deleted = [];
1546
1692
  const CHUNK = 100;
1547
1693
  for (let i = 0; i < ids.length; i += CHUNK) {
@@ -1577,30 +1723,99 @@ var CosmosDbProvider = class {
1577
1723
  }
1578
1724
  async deleteRelationshipsByType(repositoryId, relationshipType) {
1579
1725
  this.assertValidRepositoryId(repositoryId);
1580
- return deleteRelationshipsByType(this.conn, repositoryId, relationshipType);
1726
+ return this.track(
1727
+ "deleteRelationshipsByType",
1728
+ repositoryId,
1729
+ () => deleteRelationshipsByType(this.conn, repositoryId, relationshipType)
1730
+ );
1581
1731
  }
1582
1732
  // ─── Graph Traversal (StorageProvider) ─────────────────────────────
1583
1733
  async exploreNeighborhood(repositoryId, entityId, options) {
1584
1734
  this.assertValidRepositoryId(repositoryId);
1585
- return exploreNeighborhood(this.conn, repositoryId, entityId, options);
1735
+ return this.track(
1736
+ "exploreNeighborhood",
1737
+ repositoryId,
1738
+ () => exploreNeighborhood(this.conn, repositoryId, entityId, options)
1739
+ );
1586
1740
  }
1587
1741
  async findPaths(repositoryId, sourceId, targetId, options) {
1588
1742
  this.assertValidRepositoryId(repositoryId);
1589
- return findPaths(this.conn, repositoryId, sourceId, targetId, options);
1743
+ return this.track(
1744
+ "findPaths",
1745
+ repositoryId,
1746
+ () => findPaths(this.conn, repositoryId, sourceId, targetId, options)
1747
+ );
1590
1748
  }
1591
1749
  // ─── Timeline ──────────────────────────────────────────────────────
1592
1750
  async getTimeline(repositoryId, entityId, options) {
1593
1751
  this.assertValidRepositoryId(repositoryId);
1594
- return getTimeline(this.conn, repositoryId, entityId, options);
1752
+ return this.track(
1753
+ "getTimeline",
1754
+ repositoryId,
1755
+ () => getTimeline(this.conn, repositoryId, entityId, options)
1756
+ );
1595
1757
  }
1596
1758
  // ─── Bulk Operations ───────────────────────────────────────────────
1597
1759
  exportAll(repositoryId) {
1598
1760
  this.assertValidRepositoryId(repositoryId);
1599
- return exportAll(this.conn, repositoryId);
1761
+ return this.trackIterable("exportAll", repositoryId, exportAll(this.conn, repositoryId));
1600
1762
  }
1601
1763
  async importBulk(repositoryId, data, options) {
1602
1764
  this.assertValidRepositoryId(repositoryId);
1603
- return importBulk(this.conn, repositoryId, data, options);
1765
+ return this.track(
1766
+ "importBulk",
1767
+ repositoryId,
1768
+ () => importBulk(this.conn, repositoryId, data, options)
1769
+ );
1770
+ }
1771
+ /**
1772
+ * Wrap an AsyncIterable so every emitted chunk is generated inside the
1773
+ * usage scope. On each iteration step, the scope aggregates charges from
1774
+ * the next chunk's submits; when the iterator completes (or is closed), a
1775
+ * single usage record is emitted for the whole stream.
1776
+ */
1777
+ trackIterable(operation, repositoryId, source) {
1778
+ if (!this.reportUsage) return source;
1779
+ const sink = this.reportUsage;
1780
+ return {
1781
+ [Symbol.asyncIterator]: () => {
1782
+ const iter = source[Symbol.asyncIterator]();
1783
+ const acc = { ru: 0, calls: 0, retries: 0 };
1784
+ let emitted = false;
1785
+ const emit = () => {
1786
+ if (emitted) return;
1787
+ emitted = true;
1788
+ if (acc.calls > 0) {
1789
+ sink({
1790
+ provider: PROVIDER_NAME,
1791
+ operation,
1792
+ unit: "RU",
1793
+ value: acc.ru,
1794
+ repositoryId,
1795
+ timestamp: /* @__PURE__ */ new Date(),
1796
+ details: { calls: acc.calls, retries: acc.retries }
1797
+ });
1798
+ }
1799
+ };
1800
+ return {
1801
+ async next() {
1802
+ const step = await usageScope.run(acc, () => iter.next());
1803
+ if (step.done) emit();
1804
+ return step;
1805
+ },
1806
+ async return(value) {
1807
+ emit();
1808
+ if (iter.return) return iter.return(value);
1809
+ return { done: true, value };
1810
+ },
1811
+ async throw(err) {
1812
+ emit();
1813
+ if (iter.throw) return iter.throw(err);
1814
+ throw err;
1815
+ }
1816
+ };
1817
+ }
1818
+ };
1604
1819
  }
1605
1820
  // ─── GraphTraversalProvider ────────────────────────────────────────
1606
1821
  getCapabilities() {
@@ -1618,6 +1833,9 @@ var CosmosDbProvider = class {
1618
1833
  }
1619
1834
  async traverse(repositoryId, spec) {
1620
1835
  this.assertValidRepositoryId(repositoryId);
1836
+ return this.track("traverse", repositoryId, () => this.traverseImpl(repositoryId, spec));
1837
+ }
1838
+ async traverseImpl(repositoryId, spec) {
1621
1839
  const startTime = Date.now();
1622
1840
  const vocabulary = await this.getVocabulary(repositoryId);
1623
1841
  const compiled = this.compiler.compile(spec, vocabulary);
@@ -1711,6 +1929,9 @@ var CosmosDbProvider = class {
1711
1929
  * repositoryId partition predicate.
1712
1930
  */
1713
1931
  async executeNativeQuery(_repositoryId, query, params) {
1932
+ return this.track("executeNativeQuery", void 0, () => this.executeNativeQueryImpl(query, params));
1933
+ }
1934
+ async executeNativeQueryImpl(query, params) {
1714
1935
  const startTime = Date.now();
1715
1936
  try {
1716
1937
  const result = await this.conn.submit(query, params);