@utaba/deep-memory-storage-cosmosdb 0.5.0 → 0.6.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
@@ -32,6 +32,16 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
32
32
  private readonly config;
33
33
  private readonly compiler;
34
34
  constructor(config: CosmosDbProviderConfig);
35
+ /**
36
+ * Reject any repositoryId that is not a valid v4 UUID.
37
+ *
38
+ * Every partition-scoped query filters on repositoryId. Since repositoryId is
39
+ * the CosmosDB partition key, a predicate with a different value would target
40
+ * a different partition. Strict format validation at the provider boundary
41
+ * prevents injection (e.g. strings containing quotes or Gremlin syntax) from
42
+ * ever reaching query construction.
43
+ */
44
+ private assertValidRepositoryId;
35
45
  initialize(): Promise<void>;
36
46
  dispose(): Promise<void>;
37
47
  ensureSchema(): Promise<EnsureSchemaResult>;
@@ -74,7 +84,28 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
74
84
  exportAll(repositoryId: string): AsyncIterable<ExportChunk>;
75
85
  importBulk(repositoryId: string, data: ImportChunk[], options?: BulkImportOptions): Promise<BulkImportResult>;
76
86
  getCapabilities(): GraphTraversalCapabilities;
77
- traverse(repositoryId: string, spec: TraversalSpec, compiledQuery?: string): Promise<TraversalResult>;
87
+ traverse(repositoryId: string, spec: TraversalSpec): Promise<TraversalResult>;
88
+ /**
89
+ * Execute a raw Gremlin query with caller-supplied bindings.
90
+ *
91
+ * ⚠️ ELEVATED PRIVILEGE — SYSTEM-LEVEL OPERATION ⚠️
92
+ *
93
+ * This method is an unscoped pass-through: it does not filter by repository,
94
+ * does not inject the partition key, and performs no validation on the query
95
+ * string. A single call can read or mutate any vertex or edge in the
96
+ * container regardless of which repository (partition) it belongs to.
97
+ *
98
+ * DO NOT expose this method to AI agents, end users, or any untrusted caller.
99
+ * It is intended for:
100
+ * - administrative tooling (migrations, diagnostics, repairs)
101
+ * - internal library operations that need cross-partition reach
102
+ *
103
+ * `repositoryId` is accepted for interface symmetry but is intentionally
104
+ * ignored here — the caller is trusted to scope the query themselves.
105
+ *
106
+ * For agent-facing graph queries use {@link traverse}, which enforces the
107
+ * repositoryId partition predicate.
108
+ */
78
109
  executeNativeQuery(_repositoryId: string, query: string, params?: Record<string, unknown>): Promise<TraversalResult>;
79
110
  }
80
111
 
package/dist/index.d.ts CHANGED
@@ -32,6 +32,16 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
32
32
  private readonly config;
33
33
  private readonly compiler;
34
34
  constructor(config: CosmosDbProviderConfig);
35
+ /**
36
+ * Reject any repositoryId that is not a valid v4 UUID.
37
+ *
38
+ * Every partition-scoped query filters on repositoryId. Since repositoryId is
39
+ * the CosmosDB partition key, a predicate with a different value would target
40
+ * a different partition. Strict format validation at the provider boundary
41
+ * prevents injection (e.g. strings containing quotes or Gremlin syntax) from
42
+ * ever reaching query construction.
43
+ */
44
+ private assertValidRepositoryId;
35
45
  initialize(): Promise<void>;
36
46
  dispose(): Promise<void>;
37
47
  ensureSchema(): Promise<EnsureSchemaResult>;
@@ -74,7 +84,28 @@ declare class CosmosDbProvider implements StorageProvider, GraphTraversalProvide
74
84
  exportAll(repositoryId: string): AsyncIterable<ExportChunk>;
75
85
  importBulk(repositoryId: string, data: ImportChunk[], options?: BulkImportOptions): Promise<BulkImportResult>;
76
86
  getCapabilities(): GraphTraversalCapabilities;
77
- traverse(repositoryId: string, spec: TraversalSpec, compiledQuery?: string): Promise<TraversalResult>;
87
+ traverse(repositoryId: string, spec: TraversalSpec): Promise<TraversalResult>;
88
+ /**
89
+ * Execute a raw Gremlin query with caller-supplied bindings.
90
+ *
91
+ * ⚠️ ELEVATED PRIVILEGE — SYSTEM-LEVEL OPERATION ⚠️
92
+ *
93
+ * This method is an unscoped pass-through: it does not filter by repository,
94
+ * does not inject the partition key, and performs no validation on the query
95
+ * string. A single call can read or mutate any vertex or edge in the
96
+ * container regardless of which repository (partition) it belongs to.
97
+ *
98
+ * DO NOT expose this method to AI agents, end users, or any untrusted caller.
99
+ * It is intended for:
100
+ * - administrative tooling (migrations, diagnostics, repairs)
101
+ * - internal library operations that need cross-partition reach
102
+ *
103
+ * `repositoryId` is accepted for interface symmetry but is intentionally
104
+ * ignored here — the caller is trusted to scope the query themselves.
105
+ *
106
+ * For agent-facing graph queries use {@link traverse}, which enforces the
107
+ * repositoryId partition predicate.
108
+ */
78
109
  executeNativeQuery(_repositoryId: string, query: string, params?: Record<string, unknown>): Promise<TraversalResult>;
79
110
  }
80
111
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/CosmosDbProvider.ts
2
2
  import crypto from "crypto";
3
- import { GremlinCompiler, ProviderError } from "@utaba/deep-memory";
3
+ import { GremlinCompiler, ProviderError, InvalidInputError, isValidUuid, projectEntity } from "@utaba/deep-memory";
4
4
 
5
5
  // src/CosmosDbConnection.ts
6
6
  import gremlin from "gremlin";
@@ -1282,6 +1282,23 @@ var CosmosDbProvider = class {
1282
1282
  rejectUnauthorized: config.rejectUnauthorized
1283
1283
  });
1284
1284
  }
1285
+ /**
1286
+ * Reject any repositoryId that is not a valid v4 UUID.
1287
+ *
1288
+ * Every partition-scoped query filters on repositoryId. Since repositoryId is
1289
+ * the CosmosDB partition key, a predicate with a different value would target
1290
+ * a different partition. Strict format validation at the provider boundary
1291
+ * prevents injection (e.g. strings containing quotes or Gremlin syntax) from
1292
+ * ever reaching query construction.
1293
+ */
1294
+ assertValidRepositoryId(repositoryId) {
1295
+ if (!isValidUuid(repositoryId)) {
1296
+ throw new InvalidInputError(
1297
+ "repositoryId",
1298
+ `repositoryId must be a valid v4 UUID; got '${repositoryId}'`
1299
+ );
1300
+ }
1301
+ }
1285
1302
  // ─── Lifecycle ─────────────────────────────────────────────────────
1286
1303
  async initialize() {
1287
1304
  await this.conn.connect();
@@ -1367,93 +1384,120 @@ var CosmosDbProvider = class {
1367
1384
  }
1368
1385
  // ─── Repository ────────────────────────────────────────────────────
1369
1386
  async createRepository(config) {
1387
+ this.assertValidRepositoryId(config.repositoryId);
1370
1388
  return createRepository(this.conn, config);
1371
1389
  }
1372
1390
  async getRepository(repositoryId) {
1391
+ this.assertValidRepositoryId(repositoryId);
1373
1392
  return getRepository(this.conn, repositoryId);
1374
1393
  }
1375
1394
  async listRepositories(filter) {
1376
1395
  return listRepositories(this.conn, filter);
1377
1396
  }
1378
1397
  async updateRepository(repositoryId, updates) {
1398
+ this.assertValidRepositoryId(repositoryId);
1379
1399
  return updateRepository(this.conn, repositoryId, updates);
1380
1400
  }
1381
1401
  async deleteRepository(repositoryId, onProgress) {
1402
+ this.assertValidRepositoryId(repositoryId);
1382
1403
  return deleteRepository(this.conn, repositoryId, onProgress);
1383
1404
  }
1384
1405
  async deleteAllContents(repositoryId, onProgress) {
1406
+ this.assertValidRepositoryId(repositoryId);
1385
1407
  return deleteAllContents(this.conn, repositoryId, onProgress);
1386
1408
  }
1387
1409
  async getRepositoryStats(repositoryId) {
1410
+ this.assertValidRepositoryId(repositoryId);
1388
1411
  return getRepositoryStats(this.conn, repositoryId);
1389
1412
  }
1390
1413
  // ─── Vocabulary ────────────────────────────────────────────────────
1391
1414
  async getVocabulary(repositoryId) {
1415
+ this.assertValidRepositoryId(repositoryId);
1392
1416
  return getVocabulary(this.conn, repositoryId);
1393
1417
  }
1394
1418
  async saveVocabulary(repositoryId, vocabulary) {
1419
+ this.assertValidRepositoryId(repositoryId);
1395
1420
  return saveVocabulary(this.conn, repositoryId, vocabulary);
1396
1421
  }
1397
1422
  async getVocabularyChangeLog(repositoryId, options) {
1423
+ this.assertValidRepositoryId(repositoryId);
1398
1424
  return getVocabularyChangeLog(this.conn, repositoryId, options);
1399
1425
  }
1400
1426
  // ─── Entities ──────────────────────────────────────────────────────
1401
1427
  async createEntity(repositoryId, entity) {
1428
+ this.assertValidRepositoryId(repositoryId);
1402
1429
  return createEntity(this.conn, repositoryId, entity);
1403
1430
  }
1404
1431
  async getEntity(repositoryId, entityId) {
1432
+ this.assertValidRepositoryId(repositoryId);
1405
1433
  return getEntity(this.conn, repositoryId, entityId);
1406
1434
  }
1407
1435
  async getEntityBySlug(repositoryId, slug) {
1436
+ this.assertValidRepositoryId(repositoryId);
1408
1437
  return getEntityBySlug(this.conn, repositoryId, slug);
1409
1438
  }
1410
1439
  async getEntities(repositoryId, entityIds) {
1440
+ this.assertValidRepositoryId(repositoryId);
1411
1441
  return getEntities(this.conn, repositoryId, entityIds);
1412
1442
  }
1413
1443
  async updateEntity(repositoryId, entityId, updates) {
1444
+ this.assertValidRepositoryId(repositoryId);
1414
1445
  return updateEntity(this.conn, repositoryId, entityId, updates);
1415
1446
  }
1416
1447
  async deleteEntity(repositoryId, entityId) {
1448
+ this.assertValidRepositoryId(repositoryId);
1417
1449
  return deleteEntity(this.conn, repositoryId, entityId);
1418
1450
  }
1419
1451
  async deleteEntitiesByType(repositoryId, entityType) {
1452
+ this.assertValidRepositoryId(repositoryId);
1420
1453
  return deleteEntitiesByType(this.conn, repositoryId, entityType);
1421
1454
  }
1422
1455
  async findEntities(repositoryId, query) {
1456
+ this.assertValidRepositoryId(repositoryId);
1423
1457
  return findEntities(this.conn, repositoryId, query);
1424
1458
  }
1425
1459
  // ─── Relationships ─────────────────────────────────────────────────
1426
1460
  async createRelationship(repositoryId, relationship) {
1461
+ this.assertValidRepositoryId(repositoryId);
1427
1462
  return createRelationship(this.conn, repositoryId, relationship);
1428
1463
  }
1429
1464
  async getRelationship(repositoryId, relationshipId) {
1465
+ this.assertValidRepositoryId(repositoryId);
1430
1466
  return getRelationship(this.conn, repositoryId, relationshipId);
1431
1467
  }
1432
1468
  async getEntityRelationships(repositoryId, entityId, options) {
1469
+ this.assertValidRepositoryId(repositoryId);
1433
1470
  return getEntityRelationships(this.conn, repositoryId, entityId, options);
1434
1471
  }
1435
1472
  async deleteRelationship(repositoryId, relationshipId) {
1473
+ this.assertValidRepositoryId(repositoryId);
1436
1474
  return deleteRelationship(this.conn, repositoryId, relationshipId);
1437
1475
  }
1438
1476
  async deleteRelationshipsByType(repositoryId, relationshipType) {
1477
+ this.assertValidRepositoryId(repositoryId);
1439
1478
  return deleteRelationshipsByType(this.conn, repositoryId, relationshipType);
1440
1479
  }
1441
1480
  // ─── Graph Traversal (StorageProvider) ─────────────────────────────
1442
1481
  async exploreNeighborhood(repositoryId, entityId, options) {
1482
+ this.assertValidRepositoryId(repositoryId);
1443
1483
  return exploreNeighborhood(this.conn, repositoryId, entityId, options);
1444
1484
  }
1445
1485
  async findPaths(repositoryId, sourceId, targetId, options) {
1486
+ this.assertValidRepositoryId(repositoryId);
1446
1487
  return findPaths(this.conn, repositoryId, sourceId, targetId, options);
1447
1488
  }
1448
1489
  // ─── Timeline ──────────────────────────────────────────────────────
1449
1490
  async getTimeline(repositoryId, entityId, options) {
1491
+ this.assertValidRepositoryId(repositoryId);
1450
1492
  return getTimeline(this.conn, repositoryId, entityId, options);
1451
1493
  }
1452
1494
  // ─── Bulk Operations ───────────────────────────────────────────────
1453
1495
  exportAll(repositoryId) {
1496
+ this.assertValidRepositoryId(repositoryId);
1454
1497
  return exportAll(this.conn, repositoryId);
1455
1498
  }
1456
1499
  async importBulk(repositoryId, data, options) {
1500
+ this.assertValidRepositoryId(repositoryId);
1457
1501
  return importBulk(this.conn, repositoryId, data, options);
1458
1502
  }
1459
1503
  // ─── GraphTraversalProvider ────────────────────────────────────────
@@ -1470,17 +1514,20 @@ var CosmosDbProvider = class {
1470
1514
  supportsRelationshipSummary: false
1471
1515
  };
1472
1516
  }
1473
- async traverse(repositoryId, spec, compiledQuery) {
1517
+ async traverse(repositoryId, spec) {
1518
+ this.assertValidRepositoryId(repositoryId);
1474
1519
  const startTime = Date.now();
1475
1520
  const vocabulary = await this.getVocabulary(repositoryId);
1476
- const compiled = compiledQuery ? { query: compiledQuery, params: {}, estimatedFanOut: 100 } : this.compiler.compile(spec, vocabulary);
1521
+ const compiled = this.compiler.compile(spec, vocabulary);
1477
1522
  const scopedQuery = compiled.query.replace(
1478
1523
  "g.V()",
1479
- `g.V().has('repositoryId', '${repositoryId}')`
1524
+ "g.V().has('repositoryId', pRid)"
1480
1525
  );
1526
+ const scopedParams = { ...compiled.params, pRid: repositoryId };
1481
1527
  try {
1482
- const result = await this.conn.submit(scopedQuery, compiled.params);
1528
+ const result = await this.conn.submit(scopedQuery, scopedParams);
1483
1529
  const executionTimeMs = Date.now() - startTime;
1530
+ const detailLevel = spec.detailLevel ?? "summary";
1484
1531
  const entities = [];
1485
1532
  const relationships = [];
1486
1533
  const paths = [];
@@ -1492,7 +1539,8 @@ var CosmosDbProvider = class {
1492
1539
  for (const obj of pathData.objects) {
1493
1540
  const props = obj;
1494
1541
  if (props["entityType"]) {
1495
- pathEntities.push(entityFromGremlin(props));
1542
+ const stored = entityFromGremlin(props);
1543
+ pathEntities.push(projectEntity(stored, detailLevel));
1496
1544
  }
1497
1545
  }
1498
1546
  if (pathEntities.length > 0) {
@@ -1506,8 +1554,8 @@ var CosmosDbProvider = class {
1506
1554
  }
1507
1555
  } else {
1508
1556
  for (const item of result.items) {
1509
- const entity = entityFromGremlin(item);
1510
- entities.push(entity);
1557
+ const stored = entityFromGremlin(item);
1558
+ entities.push(projectEntity(stored, detailLevel));
1511
1559
  }
1512
1560
  }
1513
1561
  const limit = spec.limit ?? 50;
@@ -1539,6 +1587,27 @@ var CosmosDbProvider = class {
1539
1587
  );
1540
1588
  }
1541
1589
  }
1590
+ /**
1591
+ * Execute a raw Gremlin query with caller-supplied bindings.
1592
+ *
1593
+ * ⚠️ ELEVATED PRIVILEGE — SYSTEM-LEVEL OPERATION ⚠️
1594
+ *
1595
+ * This method is an unscoped pass-through: it does not filter by repository,
1596
+ * does not inject the partition key, and performs no validation on the query
1597
+ * string. A single call can read or mutate any vertex or edge in the
1598
+ * container regardless of which repository (partition) it belongs to.
1599
+ *
1600
+ * DO NOT expose this method to AI agents, end users, or any untrusted caller.
1601
+ * It is intended for:
1602
+ * - administrative tooling (migrations, diagnostics, repairs)
1603
+ * - internal library operations that need cross-partition reach
1604
+ *
1605
+ * `repositoryId` is accepted for interface symmetry but is intentionally
1606
+ * ignored here — the caller is trusted to scope the query themselves.
1607
+ *
1608
+ * For agent-facing graph queries use {@link traverse}, which enforces the
1609
+ * repositoryId partition predicate.
1610
+ */
1542
1611
  async executeNativeQuery(_repositoryId, query, params) {
1543
1612
  const startTime = Date.now();
1544
1613
  try {