@utaba/deep-memory-storage-cosmosdb 0.15.0 → 0.17.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/LICENSE +15 -4
- package/README.md +262 -0
- package/dist/index.cjs +1269 -671
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +136 -7
- package/dist/index.d.ts +136 -7
- package/dist/index.js +1256 -645
- package/dist/index.js.map +1 -1
- package/package.json +4 -5
package/LICENSE
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"Contribution" shall mean any work of authorship, including
|
|
49
49
|
the original version of the Work and any modifications or additions
|
|
50
50
|
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
-
submitted to
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
52
|
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
53
|
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
54
|
means any form of electronic, verbal, or written communication sent
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
61
|
|
|
62
62
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
-
on behalf of whom a Contribution has been received by
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
64
|
subsequently incorporated within the Work.
|
|
65
65
|
|
|
66
66
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
107
|
distribution, then any Derivative Works that You distribute must
|
|
108
108
|
include a readable copy of the attribution notices contained
|
|
109
|
-
within such NOTICE file, excluding
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
110
|
pertain to any part of the Derivative Works, in at least one
|
|
111
111
|
of the following places: within a NOTICE text file distributed
|
|
112
112
|
as part of the Derivative Works; within the Source form or
|
|
@@ -175,7 +175,18 @@
|
|
|
175
175
|
|
|
176
176
|
END OF TERMS AND CONDITIONS
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2024 Tim Wheeler
|
|
179
190
|
|
|
180
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
181
192
|
you may not use this file except in compliance with the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# @utaba/deep-memory-storage-cosmosdb
|
|
2
|
+
|
|
3
|
+
CosmosDB Gremlin storage provider for [`@utaba/deep-memory`](https://www.npmjs.com/package/@utaba/deep-memory). Implements both `StorageProvider` and `GraphTraversalProvider` — a single instance gives deep-memory persistent storage *and* native graph query capabilities backed by Azure CosmosDB.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @utaba/deep-memory @utaba/deep-memory-storage-cosmosdb
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Runtime dependency:** [`gremlin`](https://www.npmjs.com/package/gremlin) (Apache TinkerPop JavaScript driver).
|
|
12
|
+
|
|
13
|
+
## Quick Start (production / Azure)
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { DeepMemory } from '@utaba/deep-memory';
|
|
17
|
+
import { CosmosDbProvider } from '@utaba/deep-memory-storage-cosmosdb';
|
|
18
|
+
|
|
19
|
+
const provider = new CosmosDbProvider({
|
|
20
|
+
endpoint: 'wss://your-account.gremlin.cosmos.azure.com:443/',
|
|
21
|
+
key: process.env.COSMOSDB_KEY!,
|
|
22
|
+
database: 'deep-memory',
|
|
23
|
+
container: 'graph',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
await provider.ensureSchema(); // creates db + container if needed
|
|
27
|
+
await provider.initialise(); // opens Gremlin WebSocket
|
|
28
|
+
|
|
29
|
+
const dm = new DeepMemory({
|
|
30
|
+
storage: provider,
|
|
31
|
+
graphTraversal: provider, // same instance — implements both interfaces
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For local development with the CosmosDB emulator, see [Local emulator setup](#local-emulator-setup) below.
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
### `CosmosDbProviderConfig`
|
|
40
|
+
|
|
41
|
+
| Option | Type | Default | Description |
|
|
42
|
+
|--------|------|---------|-------------|
|
|
43
|
+
| `endpoint` | `string` | *required* | Gremlin WebSocket endpoint (e.g. `wss://your-account.gremlin.cosmos.azure.com:443/`) |
|
|
44
|
+
| `restEndpoint` | `string` | derived from `endpoint` | CosmosDB REST endpoint for database/container provisioning. Defaults to the Gremlin hostname on port 8081. |
|
|
45
|
+
| `key` | `string` | *required* | CosmosDB primary key |
|
|
46
|
+
| `database` | `string` | *required* | Database name |
|
|
47
|
+
| `container` | `string` | *required* | Container (graph) name |
|
|
48
|
+
| `partitionKey` | `string` | `/repositoryId` | Partition key path |
|
|
49
|
+
| `maxRetries` | `number` | `3` | Retries for transient errors (429 throttling, 503 unavailable) |
|
|
50
|
+
| `defaultTimeoutMs` | `number` | `30000` | Default query timeout |
|
|
51
|
+
| `rejectUnauthorized` | `boolean` | `true` | Set `false` for the local emulator (self-signed certs) |
|
|
52
|
+
|
|
53
|
+
CosmosDB Gremlin API does **not** support managed identity for data plane operations — authentication always uses an account key.
|
|
54
|
+
|
|
55
|
+
## Lifecycle
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const provider = new CosmosDbProvider({ ... });
|
|
59
|
+
|
|
60
|
+
await provider.ensureSchema(); // creates db + container, writes _meta vertex
|
|
61
|
+
await provider.initialise(); // opens Gremlin WebSocket
|
|
62
|
+
|
|
63
|
+
const dm = new DeepMemory({ storage: provider, graphTraversal: provider });
|
|
64
|
+
// ... use ...
|
|
65
|
+
|
|
66
|
+
await provider.dispose(); // closes WebSocket
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`ensureSchema()` uses the CosmosDB REST API to create the database and container if they don't exist, then writes a `_meta` schema version vertex. Subsequent calls detect the existing schema and return early.
|
|
70
|
+
|
|
71
|
+
## Data Model
|
|
72
|
+
|
|
73
|
+
All data is partitioned by `repositoryId` — every vertex and edge stores it. This enables:
|
|
74
|
+
|
|
75
|
+
- Single-partition queries for all operations within a repository
|
|
76
|
+
- Efficient cascade deletes (drop all documents in a partition)
|
|
77
|
+
- Cross-partition queries only for `listRepositories()`
|
|
78
|
+
|
|
79
|
+
### Vertex Types
|
|
80
|
+
|
|
81
|
+
| Label | Purpose | ID Format |
|
|
82
|
+
|-------|---------|-----------|
|
|
83
|
+
| `_meta` | Schema version tracking | `_meta:schema` |
|
|
84
|
+
| `_repository` | Repository definitions and governance config | `repo:{repositoryId}` |
|
|
85
|
+
| `_vocabulary` | One vocabulary JSON document per repository | `vocab:{repositoryId}` |
|
|
86
|
+
| `_vocabularyChangeLog` | Audit trail for vocabulary changes | `vocablog:{changeId}` |
|
|
87
|
+
| `{entityType}` | Graph nodes — vertex label is the entity type | Entity GUID |
|
|
88
|
+
|
|
89
|
+
### Edge Types
|
|
90
|
+
|
|
91
|
+
Edge labels are relationship types. Each edge stores `sourceEntityId`, `targetEntityId`, `bidirectional`, properties (as JSON), and provenance.
|
|
92
|
+
|
|
93
|
+
### Property Storage
|
|
94
|
+
|
|
95
|
+
| Data | Storage | Notes |
|
|
96
|
+
|------|---------|-------|
|
|
97
|
+
| Entity properties | JSON string in `properties` vertex property | Parsed on read |
|
|
98
|
+
| Embeddings | JSON string in `embedding` vertex property | Stored for export/import fidelity; not searchable via Gremlin |
|
|
99
|
+
| Governance config | JSON string in `governanceConfig` vertex property | On `_repository` vertices |
|
|
100
|
+
| Vocabulary | JSON string in `vocabulary` vertex property | On `_vocabulary` vertices |
|
|
101
|
+
|
|
102
|
+
## Capabilities
|
|
103
|
+
|
|
104
|
+
`findEntities()` supports:
|
|
105
|
+
|
|
106
|
+
- **Type filter** — restrict to specific entity types
|
|
107
|
+
- **Text search** — case-insensitive slug-based matching via `TextP.containing()`
|
|
108
|
+
- **Pagination** — Gremlin `range()` with total count
|
|
109
|
+
|
|
110
|
+
`GraphTraversalProvider` capabilities:
|
|
111
|
+
|
|
112
|
+
| Capability | Supported |
|
|
113
|
+
|-----------|-----------|
|
|
114
|
+
| Native Gremlin queries | Yes |
|
|
115
|
+
| Relationship property filters | Yes |
|
|
116
|
+
| Entity property filters | Yes |
|
|
117
|
+
| Repeat/loop traversals | Yes |
|
|
118
|
+
| Dedup | Yes |
|
|
119
|
+
| Aggregation | Limited (CosmosDB Gremlin limitation) |
|
|
120
|
+
| Relationship summaries | No |
|
|
121
|
+
|
|
122
|
+
RU cost is reported in `QueryMetadata.resourceCost` for graph traversal operations.
|
|
123
|
+
|
|
124
|
+
## Limitations
|
|
125
|
+
|
|
126
|
+
| Limitation | Impact |
|
|
127
|
+
|-----------|--------|
|
|
128
|
+
| No full-text search | `findEntities()` text matching is slug-based only — pair with a separate `SearchProvider` for richer search |
|
|
129
|
+
| No vector similarity | Embeddings stored for portability but not searchable |
|
|
130
|
+
| Limited aggregation | `count()`, `sum()`, `mean()` have limited support |
|
|
131
|
+
| No lambda steps | Cannot use closures in Gremlin queries |
|
|
132
|
+
| Arrays stored as JSON strings | Cannot filter on array elements server-side |
|
|
133
|
+
|
|
134
|
+
## Bulk Operations
|
|
135
|
+
|
|
136
|
+
`exportAll()` returns an async iterable of chunks (batches of 100), entities first then relationships. `importBulk()` uses upsert semantics and adapts concurrency to RU-constrained tiers — see [Adaptive import deep-dive](https://github.com/TjWheeler/deep-memory/blob/main/docs/storage-cosmosdb-adaptive-import.md) for the control loop, throttle detection, and circuit breaker behavior.
|
|
137
|
+
|
|
138
|
+
## Error Handling
|
|
139
|
+
|
|
140
|
+
All errors use the `@utaba/deep-memory` error hierarchy (`ProviderError`, `RepositoryNotFoundError`, `DuplicateEntityError`, etc.). Transient errors (429 throttling, 503 unavailable) are automatically retried with exponential backoff up to `maxRetries`.
|
|
141
|
+
|
|
142
|
+
## Local emulator setup
|
|
143
|
+
|
|
144
|
+
The **Windows desktop** CosmosDB emulator supports the Gremlin API. The Docker emulator does **not**.
|
|
145
|
+
|
|
146
|
+
### Installation
|
|
147
|
+
|
|
148
|
+
1. Install the [Azure CosmosDB Emulator](https://learn.microsoft.com/en-us/azure/cosmos-db/local-emulator) on Windows.
|
|
149
|
+
2. The default emulator key is: `C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==`
|
|
150
|
+
|
|
151
|
+
### Starting the emulator with Gremlin
|
|
152
|
+
|
|
153
|
+
From an **admin** PowerShell:
|
|
154
|
+
|
|
155
|
+
```powershell
|
|
156
|
+
& "C:\Program Files\Azure Cosmos DB Emulator\Microsoft.Azure.Cosmos.Emulator.exe" /EnableGremlinEndpoint
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This starts the emulator with:
|
|
160
|
+
- REST API on port **8081** (HTTPS)
|
|
161
|
+
- Gremlin endpoint on port **8901** (WebSocket)
|
|
162
|
+
|
|
163
|
+
### Starting with network access (required for WSL2)
|
|
164
|
+
|
|
165
|
+
If you're connecting from WSL2, the emulator must listen on all interfaces. `/AllowNetworkAccess` requires the `/Key` parameter:
|
|
166
|
+
|
|
167
|
+
```powershell
|
|
168
|
+
& "C:\Program Files\Azure Cosmos DB Emulator\Microsoft.Azure.Cosmos.Emulator.exe" `
|
|
169
|
+
/AllowNetworkAccess `
|
|
170
|
+
/Key=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw== `
|
|
171
|
+
/EnableGremlinEndpoint
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
You may also need Windows Firewall rules:
|
|
175
|
+
|
|
176
|
+
```powershell
|
|
177
|
+
netsh advfirewall firewall add rule name="CosmosDB REST" dir=in action=allow protocol=TCP localport=8081
|
|
178
|
+
netsh advfirewall firewall add rule name="CosmosDB Gremlin" dir=in action=allow protocol=TCP localport=8901
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Connecting from WSL2
|
|
182
|
+
|
|
183
|
+
From WSL2, connect using `host.docker.internal` which resolves to the Windows host:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const provider = new CosmosDbProvider({
|
|
187
|
+
endpoint: 'ws://host.docker.internal:8901/',
|
|
188
|
+
restEndpoint: 'https://host.docker.internal:8081',
|
|
189
|
+
key: 'C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==',
|
|
190
|
+
database: 'deep-memory-test',
|
|
191
|
+
container: 'graph-test',
|
|
192
|
+
rejectUnauthorized: false,
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Note:** The Gremlin endpoint uses `ws://` (plain WebSocket), not `wss://`. The emulator's self-signed certificate causes TLS errors with the Gremlin client when using `wss://`.
|
|
197
|
+
|
|
198
|
+
### Troubleshooting
|
|
199
|
+
|
|
200
|
+
| Problem | Solution |
|
|
201
|
+
|---------|----------|
|
|
202
|
+
| "Multiple attempts to restart" error | Full reset: shut down, delete `%LOCALAPPDATA%\CosmosDBEmulator`, reinstall if needed |
|
|
203
|
+
| Port 8081/8901 not reachable from WSL2 | Start with `/AllowNetworkAccess` and add firewall rules |
|
|
204
|
+
| Gremlin endpoint not starting | Ensure `/EnableGremlinEndpoint` flag is present at startup |
|
|
205
|
+
| Emulator crash loop | Delete data: `Remove-Item -Recurse -Force "$env:LOCALAPPDATA\CosmosDBEmulator"` then restart |
|
|
206
|
+
| TLS errors on Gremlin connection | Use `ws://` not `wss://`, and set `rejectUnauthorized: false` |
|
|
207
|
+
|
|
208
|
+
## Azure production deployment
|
|
209
|
+
|
|
210
|
+
1. Create a CosmosDB account with **Apache Gremlin** API in the Azure portal.
|
|
211
|
+
2. Note the Gremlin endpoint (e.g. `wss://your-account.gremlin.cosmos.azure.com:443/`).
|
|
212
|
+
3. Get the primary key from the Keys blade.
|
|
213
|
+
4. Call `ensureSchema()` once on first deployment — it creates the database and container.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const provider = new CosmosDbProvider({
|
|
217
|
+
endpoint: 'wss://your-account.gremlin.cosmos.azure.com:443/',
|
|
218
|
+
key: process.env.COSMOSDB_KEY!,
|
|
219
|
+
database: 'deep-memory',
|
|
220
|
+
container: 'graph',
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Request Unit (RU) cost considerations
|
|
225
|
+
|
|
226
|
+
CosmosDB charges per Request Unit. The provider reports RU costs in `QueryMetadata.resourceCost` for `GraphTraversalProvider` operations. Key cost drivers:
|
|
227
|
+
|
|
228
|
+
- **Write operations** — entity/relationship creation costs ~5-10 RU per document
|
|
229
|
+
- **Cross-partition queries** — `listRepositories()` is the only cross-partition query
|
|
230
|
+
- **Graph traversals** — cost scales with depth and fan-out; use filters to constrain
|
|
231
|
+
- **Bulk imports** — `importBulk()` uses an adaptive concurrency controller that ramps down on 429s and back up when the cluster keeps up. See the [adaptive import deep-dive](https://github.com/TjWheeler/deep-memory/blob/main/docs/storage-cosmosdb-adaptive-import.md).
|
|
232
|
+
|
|
233
|
+
## Testing
|
|
234
|
+
|
|
235
|
+
The conformance test suite requires a running CosmosDB emulator with Gremlin enabled:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
COSMOSDB_GREMLIN_ENDPOINT=ws://host.docker.internal:8901/ \
|
|
239
|
+
COSMOSDB_REST_ENDPOINT=https://host.docker.internal:8081 \
|
|
240
|
+
COSMOSDB_KEY=<emulator-key> \
|
|
241
|
+
pnpm --filter @utaba/deep-memory-storage-cosmosdb test
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Without `COSMOSDB_GREMLIN_ENDPOINT`, tests are skipped.
|
|
245
|
+
|
|
246
|
+
## Exports
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Provider class (implements StorageProvider + GraphTraversalProvider)
|
|
250
|
+
import { CosmosDbProvider } from '@utaba/deep-memory-storage-cosmosdb';
|
|
251
|
+
|
|
252
|
+
// Config type
|
|
253
|
+
import type { CosmosDbProviderConfig } from '@utaba/deep-memory-storage-cosmosdb';
|
|
254
|
+
|
|
255
|
+
// Low-level connection (for advanced usage)
|
|
256
|
+
import { CosmosDbConnection } from '@utaba/deep-memory-storage-cosmosdb';
|
|
257
|
+
import type { CosmosDbConnectionConfig, GremlinResult } from '@utaba/deep-memory-storage-cosmosdb';
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## See also
|
|
261
|
+
|
|
262
|
+
- [Adaptive import deep-dive](https://github.com/TjWheeler/deep-memory/blob/main/docs/storage-cosmosdb-adaptive-import.md) — how `importBulk` adapts concurrency to RU-constrained tiers
|