mishima-nexus-client-csharp/README.md

153 lines
7.4 KiB
Markdown
Raw Normal View History

[![Woodpecker CI](https://dvalin.robotico.dev/api/badges/robotico/robotico-nexus-client-csharp/status.svg)](https://dvalin.robotico.dev/robotico/robotico-nexus-client-csharp)
[![.NET 10](https://img.shields.io/badge/.NET-10.0-512BD4?logo=dotnet)](https://dotnet.microsoft.com/download/dotnet/10.0)
[![C#](https://img.shields.io/badge/C%23-latest-239120?logo=csharp)](https://learn.microsoft.com/dotnet/csharp/)
[![gRPC](https://img.shields.io/badge/gRPC-Nexus%20Wire-5C4EE5?logo=grpc)](https://grpc.io/)
[![NuGet](https://img.shields.io/badge/NuGet-Robotico.Nexus.Client-blue?logo=nuget)](https://download.robotico.dev/nuget/)
[![Robotico](https://img.shields.io/badge/Robotico-Nexus%20client-1f883d?logo=github)](https://brokkr.robotico.dev/robotico/robotico-nexus-client-csharp)
# Robotico.Nexus.Client
Unified client SDK for a remote [Mishima Nexus](https://brokkr.robotico.dev/robotico/mishima-suite)
server: connect via a `nexus+*://` URI and get `Robotico.Graph` / `Robotico.Time` ports bound to a
logical database over gRPC, plus thin Document/Relational connect helpers.
- NuGet: `Robotico.Nexus.Client`
- Feed: `https://download.robotico.dev/nuget/v3/index.json`
- Brokkr: `https://brokkr.robotico.dev/robotico/robotico-nexus-client-csharp`
This package does **not** reference `Mishima.Nexus.Node.*` (the server host assemblies) — it only
depends on the generated `Mishima.Nexus.Wire.V1` gRPC stubs, `Robotico.Graph`, and `Robotico.Time`.
<!-- repo-uses:start -->
## Uses
This repository uses:
```mermaid
flowchart LR
robotico_nexus_client_csharp["robotico-nexus-client-csharp"]
mishima_nexus["mishima-nexus"]
robotico_nexus_client_csharp --> mishima_nexus
```
<!-- repo-uses:end -->
<!-- repo-used-by:start -->
## Used by
This repository is used by:
```mermaid
flowchart LR
robotico_nexus_client_csharp["robotico-nexus-client-csharp"]
mishima_nexus["mishima-nexus"]
mishima_nexus_wpf["mishima-nexus-wpf"]
tanukiarmy_bot_ng["tanukiarmy-bot-ng"]
robotico_nexus_client_csharp --> mishima_nexus
robotico_nexus_client_csharp --> mishima_nexus_wpf
robotico_nexus_client_csharp --> tanukiarmy_bot_ng
```
<!-- repo-used-by:end -->
## Quick start
```csharp
using Robotico.Nexus.Client;
Result<NexusClient> connected = NexusClient.Connect("nexus+graph://localhost:50051/pathways");
using NexusClient client = connected.ExpectSuccess();
client.CheckHealth().ExpectSuccess();
Result<IGraphSession> opened = client.Graph.Open("pathways");
using IGraphSession session = opened.ExpectSuccess();
session.RegisterLabel("Person").ExpectSuccess();
session.UpsertVertex(1, ["Person"]).ExpectSuccess();
```
## Connection URIs
| Scheme | Plane | Default database (empty path) |
|-----------------------|------------|--------------------------------|
| `nexus+graph://` | Graph | `pathways` |
| `nexus+time://` | Time | `telemetry` |
| `nexus+document://` | Document | `catalog` |
| `nexus+relational://` | Relational | `ledger` |
| `nexus+sql://` | — | rejected — use `Mishima.Nexus.Sql` directly |
- The URI **host:port** is the gRPC endpoint; the **port is always explicit** (no default port is
ever inferred, so a typo can't silently connect to the wrong service).
- The URI **path** (e.g. `/pathways`) is the **logical database name**, sent as the
`x-nexus-database` gRPC metadata header on every call — it is not a filesystem path, even though
the underlying `IGraphStore`/`ITimeStore` port parameter is historically named
`databaseFilePath`.
- One `NexusClient` is bound to a single scheme + logical database from the connect URI. Open a
second client to talk to another plane or database.
## TLS / mTLS / bearer auth
`NexusClientOptions` mirrors `Mishima.Nexus.Wire.Clients.WireTlsMaterial`'s environment variables
(same names, no shared assembly dependency):
| Variable | Purpose |
|---------------------------------|-------------------------------------------|
| `NEXUS_TLS_CA_FILE` | CA certificate PEM (trust root) |
| `NEXUS_TLS_CLIENT_CERT_FILE` | Client (machine credential) certificate |
| `NEXUS_TLS_CLIENT_KEY_FILE` | Client private key PEM |
| `NEXUS_TLS_MODE` | `https` (default) or `insecure` |
| `NEXUS_WIRE_BEARER` | Bearer token attached as `Authorization` |
| `NEXUS_WIRE_DEFAULT_BEARER` | Fallback bearer token |
Pass an explicit `NexusTlsMaterial` / `BearerToken` via `NexusClientOptions` to override the
environment. When no TLS material is present and `UseHttps` is unset, the client dials plaintext
h2c (loopback/dev only).
## Create / Open / OpenOrCreate semantics (POLA)
Nexus provisions logical databases lazily — the host creates a database's on-disk state the first
time an RPC actually touches it, not via a separate "create database" call on the Graph/Time wire
services. This client documents that behavior explicitly rather than pretending otherwise:
- **`Open(name)`** — pings the engine with `x-nexus-database: name` and returns a session bound to
that logical name. Succeeds even for a database the host hasn't materialized yet (the host
creates it lazily on first write).
- **`OpenOrCreate(name)`** — identical to `Open(name)`. There is no separate provisioning step to
race against, so "open if exists, else create" collapses to "open" once the engine is reachable.
- **`Create(name)`** — fails with `NXS_ALREADY_EXISTS` when `Ping` against `name` succeeds, because
Nexus's `Ping` is an **engine-liveness** probe, not a **per-database existence** check — once the
Graph/Time engine is reachable at all, `Create` cannot distinguish "database already has data"
from "database is merely reachable". Use `OpenOrCreate` for greenfield databases instead.
## Document / Relational
`NexusClient.CreateDocumentClient()` / `CreateRelationalClient()` return small façades (`Ping` plus,
for Document, single-document upsert/get/delete) — enough for MVP smoke usage. They are **not** a
full `IMishimaDatabase` façade. For the complete generated RPC surface, use
`CreateDocumentWireClient()` / `CreateRelationalWireClient()`, which return the raw
`Mishima.Nexus.Wire.V1` gRPC clients bound to the same channel and `x-nexus-database` metadata.
## Error codes
All failures map to `Robotico.Result.Result` with `NXS_*` codes (`NexusErrors`): `NXS_URI_INVALID`,
`NXS_URI_UNSUPPORTED_SCHEME`, `NXS_CONNECT_FAILED`, `NXS_TLS_REJECTED`, `NXS_ALREADY_EXISTS`,
`NXS_NOT_FOUND`, `NXS_INVALID_ARGUMENT`, `NXS_DISPOSED`, `NXS_UNAVAILABLE`, `NXS_WIRE_FAILED`.
`Robotico.Graph` (`GR_*`) and `Robotico.Time` (`TM_*`) codes surface unchanged from the ports.
## Quality gate
Same Library 10 bar as other Robotico C# packages:
- Analyzers on (`TreatWarningsAsErrors`, Meziantou MA0004/MA0042/MA0155, IDE0008/IDE0090/IDE0301/IDE0330)
- `dotnet test` with coverlet **≥ 90%** line coverage on `Robotico.Nexus.Client`, exercised against
an in-process fake gRPC server (Kestrel + in-memory fake `NexusGraphEngineService` /
`NexusTimeEngineService` / `NexusDocumentEngineService` / `NexusRelationalEngineService` /
`NexusHealthService`) — no live Nexus process required.
- Woodpecker / GitHub CI run `bash scripts/woodpecker-verify.sh`
## Build
```bash
bash scripts/woodpecker-verify.sh
bash scripts/pack-release.sh
```