69 lines
4.6 KiB
Plaintext
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.
|