diff --git a/src/Robotico.Nexus.Client/Document/NexusDocumentClient.cs b/src/Robotico.Nexus.Client/Document/NexusDocumentClient.cs
index 8d0a969..8a79577 100644
--- a/src/Robotico.Nexus.Client/Document/NexusDocumentClient.cs
+++ b/src/Robotico.Nexus.Client/Document/NexusDocumentClient.cs
@@ -53,6 +53,29 @@ public sealed class NexusDocumentClient
return NexusWireStatus.ToResult(response.Status, body);
});
+ ///
+ /// Reads many documents in one RPC. Missing ids are omitted from the list.
+ ///
+ public Robotico.Result.Result> GetDocuments(
+ string collectionName,
+ IReadOnlyList documentIds) => InvokeGeneric(() =>
+ {
+ ArgumentNullException.ThrowIfNull(documentIds);
+ DocumentCollectionGetManyRequest request = new() { CollectionName = collectionName };
+ request.DocumentIds.AddRange(documentIds);
+ DocumentCollectionGetManyResponse response = _client.CollectionGetMany(request, Metadata());
+ List found = new(response.Documents.Count);
+ foreach (DocumentCollectionDocumentWire doc in response.Documents)
+ {
+ string body = doc.JsonUtf8.Length > 0
+ ? System.Text.Encoding.UTF8.GetString(doc.JsonUtf8.Span)
+ : doc.JsonBody;
+ found.Add(new NexusRetrievedDocument(doc.DocumentId, body));
+ }
+
+ return NexusWireStatus.ToResult(response.Status, (IReadOnlyList)found);
+ });
+
/// Deletes a document by id when present (idempotent).
public Robotico.Result.Result DeleteDocument(string collectionName, string documentId) => Invoke(() =>
{
diff --git a/src/Robotico.Nexus.Client/Document/NexusRetrievedDocument.cs b/src/Robotico.Nexus.Client/Document/NexusRetrievedDocument.cs
new file mode 100644
index 0000000..66f7329
--- /dev/null
+++ b/src/Robotico.Nexus.Client/Document/NexusRetrievedDocument.cs
@@ -0,0 +1,4 @@
+namespace Robotico.Nexus.Client.Document;
+
+/// One document returned by .
+public readonly record struct NexusRetrievedDocument(string DocumentId, string JsonBody);
diff --git a/tests/Robotico.Nexus.Client.Tests/Document/NexusDocumentClientTests.cs b/tests/Robotico.Nexus.Client.Tests/Document/NexusDocumentClientTests.cs
index d93d74d..941b865 100644
--- a/tests/Robotico.Nexus.Client.Tests/Document/NexusDocumentClientTests.cs
+++ b/tests/Robotico.Nexus.Client.Tests/Document/NexusDocumentClientTests.cs
@@ -48,6 +48,22 @@ public class NexusDocumentClientTests : IAsyncLifetime
Assert.Equal("""{"name":"Ada"}""", body);
}
+ [Fact]
+ public void GetDocuments_ReturnsFound_AndOmitsMissing()
+ {
+ _documentClient.UpsertDocument("users", "u1", """{"name":"Ada"}""");
+ _documentClient.UpsertDocument("users", "u3", """{"name":"Grace"}""");
+
+ Robotico.Result.Result> fetched =
+ _documentClient.GetDocuments("users", ["u3", "missing", "u1"]);
+
+ Assert.True(fetched.IsSuccess(out IReadOnlyList? docs, out _));
+ Assert.Equal(2, docs!.Count);
+ Assert.Equal("u3", docs[0].DocumentId);
+ Assert.Equal("""{"name":"Grace"}""", docs[0].JsonBody);
+ Assert.Equal("u1", docs[1].DocumentId);
+ }
+
[Fact]
public void GetDocument_ReturnsNull_WhenNotFound()
{
diff --git a/tests/Robotico.Nexus.Client.Tests/TestSupport/FakeDocumentEngineService.cs b/tests/Robotico.Nexus.Client.Tests/TestSupport/FakeDocumentEngineService.cs
index 6e9cf90..90078f4 100644
--- a/tests/Robotico.Nexus.Client.Tests/TestSupport/FakeDocumentEngineService.cs
+++ b/tests/Robotico.Nexus.Client.Tests/TestSupport/FakeDocumentEngineService.cs
@@ -4,8 +4,8 @@ using Mishima.Nexus.Wire.V1;
namespace Robotico.Nexus.Client.Tests.TestSupport;
///
-/// In-memory fake standing in for a real Nexus document engine host — only the RPCs used by
-/// (Ping + single-document CRUD).
+/// In-memory fake standing in for a real Nexus document engine host — the RPCs used by
+/// (Ping, CRUD, GetMany).
///
public sealed class FakeDocumentEngineService : NexusDocumentEngineService.NexusDocumentEngineServiceBase
{
@@ -36,6 +36,29 @@ public sealed class FakeDocumentEngineService : NexusDocumentEngineService.Nexus
return Task.FromResult(new DocumentGetResponse { Status = NexusStatuses.Ok, Found = false });
}
+ ///
+ public override Task CollectionGetMany(
+ DocumentCollectionGetManyRequest request,
+ ServerCallContext context)
+ {
+ DocumentCollectionGetManyResponse response = new() { Status = NexusStatuses.Ok };
+ foreach (string id in request.DocumentIds)
+ {
+ if (!_documents.TryGetValue((request.CollectionName, id), out string? body))
+ {
+ continue;
+ }
+
+ response.Documents.Add(new DocumentCollectionDocumentWire
+ {
+ DocumentId = id,
+ JsonBody = body,
+ });
+ }
+
+ return Task.FromResult(response);
+ }
+
///
public override Task DeleteDocument(DocumentDeleteRequest request, ServerCallContext context)
{