@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.cjs +76 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.js +77 -8
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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
|
|
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
|
|
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
|
|
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 =
|
|
1521
|
+
const compiled = this.compiler.compile(spec, vocabulary);
|
|
1477
1522
|
const scopedQuery = compiled.query.replace(
|
|
1478
1523
|
"g.V()",
|
|
1479
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
1510
|
-
entities.push(
|
|
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 {
|