@utaba/deep-memory-storage-cosmosdb 0.17.0 → 0.19.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/README.md CHANGED
@@ -88,35 +88,42 @@ All data is partitioned by `repositoryId` — every vertex and edge stores it. T
88
88
 
89
89
  ### Edge Types
90
90
 
91
- Edge labels are relationship types. Each edge stores `sourceEntityId`, `targetEntityId`, `bidirectional`, properties (as JSON), and provenance.
91
+ Edge labels are relationship types. Each edge stores `sourceEntityId`, `targetEntityId`, `bidirectional`, user properties (dual-written — see [Property Storage](#property-storage)), and provenance.
92
92
 
93
93
  ### Property Storage
94
94
 
95
+ User-supplied properties on entities and relationships are **dual-written**: the full payload is JSON-stringified into the `properties` slot (round-trip authoritative — every shape JS can serialise survives), and every key whose value is natively Cosmos-storable (`string`, finite `number`, `boolean`, homogeneous arrays of those) is mirrored as a per-key native vertex/edge scalar so it can be reached by server-side Gremlin predicates (`has('orgType', 'company')`, `values('orgType')`, `group().by(...)`) and the exact-path `findEntities` SQL prefilter. Nested objects, `null`, mixed arrays, and arrays of objects live only in the blob and are not predicate-queryable. Schema-slot collisions on user keys (`entityType`, `id`, `'label'` on edges, …) throw `ProviderError` synchronously on every write path AND on `findEntities` property filters. See the [Properties model section in the Gremlin compatibility doc](https://github.com/TjWheeler/deep-memory/blob/main/docs/cosmosdb-gremlin-compatibility.md#properties-model) for the full contract.
96
+
95
97
  | Data | Storage | Notes |
96
98
  |------|---------|-------|
97
- | Entity properties | JSON string in `properties` vertex property | Parsed on read |
99
+ | Entity / relationship user properties | JSON blob in `properties` + per-key native vertex/edge scalars for native-storable values | Blob is authoritative on read. The scalars are a write-only mirror that powers server-side predicates and aggregation; the read path never consumes them. |
98
100
  | Embeddings | JSON string in `embedding` vertex property | Stored for export/import fidelity; not searchable via Gremlin |
99
101
  | Governance config | JSON string in `governanceConfig` vertex property | On `_repository` vertices |
100
102
  | Vocabulary | JSON string in `vocabulary` vertex property | On `_vocabulary` vertices |
101
103
 
102
104
  ## Capabilities
103
105
 
104
- `findEntities()` supports:
106
+ `findEntities()` runs against the Cosmos NoSQL (Document) endpoint over the same backing container the Gremlin reads use — a separate code path because the Gremlin subset cannot express case-insensitive substring search server-side (`TextP.containing()` silently returns zero rows in the Cosmos Gremlin subset). The two paths share the container; `CosmosDocumentClient` issues raw HTTPS + HMAC requests with no SDK dependency. Supports:
105
107
 
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
108
+ - **Type filter** — `c.entityType[0]._value IN (@etype0, …)`
109
+ - **Text search** — case-insensitive substring via `CONTAINS(<field>, @term, true)` across `entityLabel` / `slug` / `summary`
110
+ - **Property filter** — three modes set by the filter values:
111
+ - no filter → `PaginatedResult.total` is an exact `number`
112
+ - every value native-storable → exact prefilter (`c.<key>[0]._value = @val` per clause against the dual-written scalar column); `total` is an exact `number`
113
+ - any value non-storable → fallback `CONTAINS` substring on the JSON blob; `total: undefined` (the count branch is skipped because the substring prefilter over-reports)
114
+ - Reserved-key collisions in `query.properties` throw `ProviderError` synchronously
115
+ - **Pagination** — SQL `ORDER BY c.id OFFSET @off LIMIT @lim`; data and count queries share one `WHERE` clause so `total` is consistent with the data page by construction
109
116
 
110
117
  `GraphTraversalProvider` capabilities:
111
118
 
112
119
  | Capability | Supported |
113
120
  |-----------|-----------|
114
121
  | Native Gremlin queries | Yes |
115
- | Relationship property filters | Yes |
116
- | Entity property filters | Yes |
122
+ | Relationship property filters | Yes (server-side `has('<userKey>', value)` against the dual-written edge scalar) |
123
+ | Entity property filters | Yes (server-side `has('<userKey>', value)` against the dual-written vertex scalar) |
117
124
  | Repeat/loop traversals | Yes |
118
125
  | Dedup | Yes |
119
- | Aggregation | Limited (CosmosDB Gremlin limitation) |
126
+ | Server-side aggregation over user properties | Yes — `group().by(values('<userKey>')).by(count())` and `dedup().by(values('<userKey>'))` resolve through the dual-written scalars |
120
127
  | Relationship summaries | No |
121
128
 
122
129
  RU cost is reported in `QueryMetadata.resourceCost` for graph traversal operations.
@@ -125,11 +132,11 @@ RU cost is reported in `QueryMetadata.resourceCost` for graph traversal operatio
125
132
 
126
133
  | Limitation | Impact |
127
134
  |-----------|--------|
128
- | No full-text search | `findEntities()` text matching is slug-based only — pair with a separate `SearchProvider` for richer search |
135
+ | No ranked / fuzzy / phrase text search | `findEntities()` text matching is substring `CONTAINS` over `entityLabel` / `slug` / `summary` only — pair with a separate `SearchProvider` for ranked, fuzzy, or multi-token search |
129
136
  | No vector similarity | Embeddings stored for portability but not searchable |
130
- | Limited aggregation | `count()`, `sum()`, `mean()` have limited support |
137
+ | `group().by(values('<key>'))` cost scales with partition size | Server-side aggregation over user-property scalars works but touches every matching vertex in the partition — costly at millions of vertices. Mitigation options (write-side counter vertex, scheduled stats refresh) are deferred |
131
138
  | No lambda steps | Cannot use closures in Gremlin queries |
132
- | Arrays stored as JSON strings | Cannot filter on array elements server-side |
139
+ | Nested-shape user properties are blob-only | Nested objects, `null`, mixed-type arrays, and arrays of objects survive via the JSON blob but get no native scalar mirror — so they are not server-side predicate-queryable. Homogeneous arrays of native-storable values *are* dual-written as multi-cardinality native properties. |
133
140
 
134
141
  ## Bulk Operations
135
142