mishima-nexus-client-csharp/docs/adr/0001-nexus-client-monolith....

69 lines
4.6 KiB
Plaintext

= ADR 0001 — Nexus client monolith, no Node.* references, ports-only for Graph/Time
:revdate: 2026-07-25
== Status
Accepted.
== Context
Robotico apps that talk to a remote Mishima Nexus server need a single, small SDK: parse a
`nexus+*://` connection URI, dial the right gRPC plane, and expose the same `Robotico.Graph` /
`Robotico.Time` ports application code already codes against for local/embedded engines. Nexus
exposes four wire planes (Graph, Time, Document, Relational) plus a SQL-compatibility plane that is
explicitly out of scope for this client. `Mishima.Nexus.Abstractions` (the server-side config/URI
library) pulls in `Mishima.Nexus.Config.FSharp` and `Mishima.Nexus.Domain`, which are not published
to the NuGet feed and are not appropriate dependencies for a thin remote client.
== Decision
* Publish a single package, `Robotico.Nexus.Client` (YAGNI — no `Robotico.Nexus.Client.Graph.dll`
split etc.), with internal namespaces `Robotico.Nexus.Client`, `.Graph`, `.Time`, `.Document`,
`.Relational`.
* `NexusClient.Connect(connectionUri, options)` is the single entry point: parses the URI, dials a
gRPC channel (TLS/mTLS or plaintext h2c), and exposes `Graph` (`IGraphStore`), `Time`
(`ITimeStore`), `CheckHealth()`, and `CreateDocumentClient()` / `CreateRelationalClient()`.
* **Do not reference `Mishima.Nexus.Abstractions` or any `Mishima.Nexus.Node.*` assembly.** The URI
parsing logic (`NexusConnectionUri`) and TLS material handling (`NexusTlsMaterial`) are
intentionally duplicated (not shared) from the server-side equivalents, using the same URI shape
and the same environment variable names (POLA) so operators don't need to learn two conventions.
This keeps the client's dependency graph to `Mishima.Nexus.Wire.V1` (generated gRPC stubs only),
`Robotico.Graph`, `Robotico.Time`, and `Robotico.Result` — no F# assemblies, no server config, no
host process wiring.
* `nexus+sql://` is recognized and rejected with a dedicated error message pointing at
`Mishima.Nexus.Sql` — this client never speaks the SQL-compatibility wire.
* Graph and Time are **ports-only**: `NexusGraphStore`/`NexusGraphSession` implement `IGraphStore`/
`IGraphSession` and `NexusTimeStore`/`NexusTimeSession` implement `ITimeStore`/`ITimeSession`
exactly — no Cypher/Gremlin/SPARQL/Vacuum surface on `IGraphSession`, and `Doctor()` is always
available on `ITimeSession` (synthesized from `NexusTimeEngineService.Doctor`, since Nexus does
not expose server-side filesystem paths to remote clients — `PrimaryPath`/`WalPath` report the
logical `nexus://` identity instead, and `MaxActiveSeries` is always `0`).
* Document and Relational get **thin connect helpers**, not a full `IMishimaDatabase` façade:
`NexusDocumentClient` (Ping + single-document upsert/get/delete) and `NexusRelationalClient`
(Ping only). `CreateDocumentWireClient()` / `CreateRelationalWireClient()` return the generated
Wire.V1 clients directly for callers who need the full RPC surface.
* `Create`/`Open`/`OpenOrCreate` on `IGraphStore`/`ITimeStore` are logical-database-name operations
(the `databaseFilePath` parameter name is the port's, not this client's — see README). Nexus
provisions logical databases lazily on first use, and `Ping` is engine-level (not per-database),
so:
- `Open` == ping + bind session (works even before the host has materialized any state for that
name).
- `OpenOrCreate` == `Open` (no separate provisioning RPC to race).
- `Create` fails with `NXS_ALREADY_EXISTS` once `Ping` succeeds, since a successful `Ping` cannot
distinguish "engine reachable, database still empty" from "database already has data".
* All failures — wire status envelopes (`NexusStatus`) and gRPC (`RpcException`) — map to
`Robotico.Result.Result` with stable `NXS_*` codes (`NexusErrors`), so callers see one consistent
error surface regardless of failure layer.
== Consequences
* Apps depend on `Robotico.Nexus.Client` alongside `Robotico.Graph`/`Robotico.Time` and code against
the same ports whether the backing store is embedded (`MishimaGraph`/`MishimaTime`) or remote
(Nexus over gRPC) — swapping is a `NexusClient.Connect(...)` call, not a port change.
* If `Mishima.Nexus.Abstractions` is later published cleanly (without Node.* transitive
dependencies), this client can migrate to referencing it instead of duplicating URI/TLS parsing —
tracked as a follow-up, not required for the 1.0.0 MVP.
* Document/Relational callers who need more than Ping/basic CRUD must drop to the generated
Wire.V1 client via `CreateDocumentWireClient()`/`CreateRelationalWireClient()` until (if ever) a
richer façade ships.