From 3841a84b21087cefd1c1fd5fd15067ae085f39c3 Mon Sep 17 00:00:00 2001 From: Garfield Lee Date: Fri, 1 May 2026 23:22:54 +0800 Subject: [PATCH 1/2] docs: add README and MIT license --- LICENSE | 21 ++++++ README.md | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..69cd62c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Moeru AI & Garfield550 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..10bc0d0 --- /dev/null +++ b/README.md @@ -0,0 +1,216 @@ +# Eventa for C# + +[![Build and Test][build-test-src]][build-test-href] +[![Run Example][run-example-src]][run-example-href] +[![License][license-src]][license-href] + +Transport-agnostic, type-safe events for .NET 10, with ergonomic request/response +and streaming invoke flows built on top of event primitives. + +Eventa for C# is a source-first preview of the Eventa protocol ideas in idiomatic +.NET. It uses `EventDefinition` for strongly typed events, `EventContext` for +local dispatch and adapter hooks, `Task` for unary invokes, `IAsyncEnumerable` +for streams, `CancellationToken` for cancellation, and `IDisposable` for listener +lifetimes. + +## Overview + +Eventa treats events as the shared protocol boundary. You define stable event +identities once, subscribe to them through a context, and optionally compose the +same event primitives into RPC-like invoke contracts. + +The C# implementation currently focuses on the core protocol layer: + +- publish/subscribe event dispatch +- one-shot listeners and explicit unsubscribe support +- match-expression listeners +- unary request/response invokes +- request-stream to unary-response invokes +- server-streaming and bidirectional streaming invokes +- adapter observation hooks for send/receive activity +- fatal event and fatal match-expression hooks that abort pending invokes + +## Features + +- Type-safe event definitions with stable or generated ids. +- `EventContext` dispatch with direct listeners, one-shot listeners, and match + expressions. +- .NET-native resource cleanup through `IDisposable` subscriptions. +- Unary invoke clients and handlers using `Task`. +- Streaming invoke clients and handlers using `IAsyncEnumerable`. +- Request-stream support for unary and streaming invoke flows. +- Cancellation through `CancellationToken`. +- AOT-oriented API shape with explicit generic payload types. +- Minimal adapter surface through `IEventaAdapter`. + +## Requirements + +- .NET 10 SDK +- A shell that can run `dotnet` + +This repository is source-first right now. There is no NuGet installation step +documented here; clone the repository and work from the solution under this +directory. + +## Getting Started + +From this directory: + +```sh +dotnet restore Eventa.slnx --locked-mode +dotnet build Eventa.slnx --configuration Release --no-restore +dotnet test --project tests/Eventa.Tests/Eventa.Tests.csproj --configuration Release --no-build +dotnet run --project examples/Eventa.Example/Eventa.Example.csproj --configuration Release --no-restore +``` + +The console example walks through basic events, cross-file event contracts, +dependency injection, unary invokes, streaming invokes, request streams, +match expressions, adapter hooks, cancellation, and fatal abort hooks. + +## Event Example + +```csharp +using Eventa; + +using var context = new EventContext(); +var moved = new EventDefinition("demo:move"); + +using var subscription = context.Subscribe( + moved, + envelope => Console.WriteLine($"{envelope.Body.X},{envelope.Body.Y}")); + +context.Emit(moved, new MovePayload(10, 20)); + +public sealed record MovePayload(int X, int Y); +``` + +`EventDefinition` gives an event a stable protocol identity and a payload +shape. `EventContext.Subscribe` returns an `IDisposable`; disposing it removes +the listener. + +## Unary Invoke Example + +```csharp +using Eventa; + +using var context = new EventContext(); +var echo = new InvokeEventDefinition("demo:rpc:echo"); + +using var handler = context.RegisterInvokeHandler( + echo, + static (EchoRequest request, CancellationToken _) => + Task.FromResult(new EchoResponse(request.Input.ToUpperInvariant()))); + +var client = context.CreateInvokeClient(echo); +var result = await client.InvokeAsync(new EchoRequest("eventa")); + +Console.WriteLine(result.Output); // EVENTA + +public sealed record EchoRequest(string Input); + +public sealed record EchoResponse(string Output); +``` + +Invoke definitions derive the protocol event ids used for request payloads, +responses, errors, stream completion, and aborts. Concurrent invokes are +correlated internally so each call receives its own response or failure. + +## Streaming Invoke Example + +```csharp +using System.Runtime.CompilerServices; + +using Eventa; + +using var context = new EventContext(); +var sync = new InvokeEventDefinition("demo:rpc:sync"); + +using var handler = context.RegisterStreamHandler( + sync, + SyncJob); + +var client = context.CreateInvokeStreamClient(sync); + +await foreach (var update in client.InvokeAsync(new SyncRequest("import", 3))) +{ + Console.WriteLine(update); +} + +static async IAsyncEnumerable SyncJob( + SyncRequest request, + [EnumeratorCancellation] CancellationToken cancellationToken) +{ + for (var step = 1; step <= request.Steps; step++) + { + cancellationToken.ThrowIfCancellationRequested(); + await Task.Yield(); + yield return new SyncProgress(step * 100 / request.Steps); + } + + yield return new SyncCompleted(request.JobId); +} + +public sealed record SyncRequest(string JobId, int Steps); + +public abstract record SyncUpdate; + +public sealed record SyncProgress(int Percent) : SyncUpdate; + +public sealed record SyncCompleted(string JobId) : SyncUpdate; +``` + +Streaming invokes return `IAsyncEnumerable`. Handlers can be written +as async iterators or adapted from callback-style code with +`EventStream.ToStreamHandler`. + +## Project Layout + +```text +. ++-- Eventa.slnx ++-- src/Eventa/ # Core library ++-- tests/Eventa.Tests/ # xUnit v3 tests on Microsoft.Testing.Platform ++-- examples/Eventa.Example/ # Console examples ++-- docs/ # Design and compatibility notes +``` + +## Development + +Useful commands: + +```sh +dotnet restore Eventa.slnx --locked-mode +dotnet build Eventa.slnx --configuration Release --no-restore +dotnet test --project tests/Eventa.Tests/Eventa.Tests.csproj --configuration Release --no-build +dotnet run --project examples/Eventa.Example/Eventa.Example.csproj --configuration Release --no-restore +``` + +The test project uses xUnit v3 with Microsoft.Testing.Platform. Package lock +files are checked in for restore reproducibility. + +## TypeScript Relationship + +Eventa started in TypeScript as an event-first way to express local events, +RPC, and streaming RPC across swappable transports. This C# project carries the +same core protocol idea into .NET conventions instead of mirroring the +TypeScript API shape directly. + +For example, C# uses `Task`, `IAsyncEnumerable`, `CancellationToken`, and +`IDisposable` where the TypeScript package uses promises, readable streams, +abort signals, and unsubscribe callbacks. + +## Security Note + +Eventa forwards the payloads you emit. Validate data at process, network, and +trust boundaries before sending it to or accepting it from untrusted peers. + +## License + +MIT + +[build-test-src]: https://github.com/moeru-ai/eventa.net/actions/workflows/build-test.yml/badge.svg +[build-test-href]: https://github.com/moeru-ai/eventa.net/actions/workflows/build-test.yml +[run-example-src]: https://github.com/moeru-ai/eventa.net/actions/workflows/run-example.yml/badge.svg +[run-example-href]: https://github.com/moeru-ai/eventa.net/actions/workflows/run-example.yml +[license-src]: https://img.shields.io/github/license/moeru-ai/eventa.svg?style=flat +[license-href]: https://github.com/moeru-ai/eventa/blob/main/LICENSE From e494461fa10750e0b707abd45758a1ec4b2c6d14 Mon Sep 17 00:00:00 2001 From: Garfield Lee Date: Fri, 1 May 2026 23:30:17 +0800 Subject: [PATCH 2/2] docs: update links Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 10bc0d0..ba00397 100644 --- a/README.md +++ b/README.md @@ -212,5 +212,5 @@ MIT [build-test-href]: https://github.com/moeru-ai/eventa.net/actions/workflows/build-test.yml [run-example-src]: https://github.com/moeru-ai/eventa.net/actions/workflows/run-example.yml/badge.svg [run-example-href]: https://github.com/moeru-ai/eventa.net/actions/workflows/run-example.yml -[license-src]: https://img.shields.io/github/license/moeru-ai/eventa.svg?style=flat -[license-href]: https://github.com/moeru-ai/eventa/blob/main/LICENSE +[license-src]: https://img.shields.io/github/license/moeru-ai/eventa.net.svg?style=flat +[license-href]: LICENSE