diff --git a/document-parser/domain/value_objects.py b/document-parser/domain/value_objects.py
index cf9b258..af973b5 100644
--- a/document-parser/domain/value_objects.py
+++ b/document-parser/domain/value_objects.py
@@ -40,11 +40,11 @@ class DocumentLifecycleState(StrEnum):
class StoreKind(StrEnum):
- """Backing technology of a Store. Today only OpenSearch is implemented;
- the enum is here so future backends (Pinecone, Qdrant, pgvector) can be
- added without touching the persistence schema."""
+ """Backing technology of a Store. New backends plug in here without
+ touching the persistence schema."""
OPENSEARCH = "opensearch"
+ NEO4J = "neo4j"
class DocumentStoreLinkState(StrEnum):
diff --git a/document-parser/services/store_service.py b/document-parser/services/store_service.py
index bee40a5..13e2000 100644
--- a/document-parser/services/store_service.py
+++ b/document-parser/services/store_service.py
@@ -87,13 +87,17 @@ def _validate_slug(slug: str) -> None:
def _validate_config_for_kind(kind: StoreKind, config: dict) -> None:
- """Per-kind config schema. Today only OpenSearch is wired; new kinds plug
- in here without touching the rest of the pipeline."""
+ """Per-kind config schema. New kinds plug in here without touching the
+ rest of the pipeline. Authentication for remote stores comes from the
+ deployment env (see `infra/settings.py`)."""
if kind is StoreKind.OPENSEARCH:
index_name = config.get("index_name") or config.get("indexName")
if not isinstance(index_name, str) or not index_name.strip():
raise StoreValidationError("OpenSearch config requires a non-empty 'index_name'")
- # Future: add LlamaIndex / LangChain / pgvector branches here.
+ elif kind is StoreKind.NEO4J:
+ index_name = config.get("index_name") or config.get("indexName")
+ if not isinstance(index_name, str) or not index_name.strip():
+ raise StoreValidationError("Neo4j config requires a non-empty 'index_name'")
class StoreService:
diff --git a/document-parser/tests/test_store_service.py b/document-parser/tests/test_store_service.py
index 31cab80..19b3996 100644
--- a/document-parser/tests/test_store_service.py
+++ b/document-parser/tests/test_store_service.py
@@ -110,6 +110,27 @@ class TestCreate:
config={},
)
+ async def test_create_neo4j_ok(self, service):
+ store = await service.create_store(
+ name="kg",
+ slug="kg",
+ kind=StoreKind.NEO4J,
+ embedder="bge-m3",
+ config={"index_name": "chunks-vec", "database": "neo4j"},
+ )
+ assert store.kind is StoreKind.NEO4J
+ assert store.config["index_name"] == "chunks-vec"
+
+ async def test_create_neo4j_rejects_missing_index_name(self, service):
+ with pytest.raises(StoreValidationError):
+ await service.create_store(
+ name="kg",
+ slug="kg",
+ kind=StoreKind.NEO4J,
+ embedder="bge-m3",
+ config={},
+ )
+
async def test_create_default_clears_others(self, service):
# The seeded 'default' store starts as is_default=True.
new_default = await service.create_store(
diff --git a/frontend/src/features/store/ui/Neo4jConfigForm.vue b/frontend/src/features/store/ui/Neo4jConfigForm.vue
new file mode 100644
index 0000000..c710250
--- /dev/null
+++ b/frontend/src/features/store/ui/Neo4jConfigForm.vue
@@ -0,0 +1,109 @@
+
+