CodeLogic 5.0.31-preview

This is a prerelease version of CodeLogic.
This package has a SemVer 2.0.0 package version: 5.0.31-preview+b7b80f0.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package CodeLogic --version 5.0.31-preview
                    
NuGet\Install-Package CodeLogic -Version 5.0.31-preview
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CodeLogic" Version="5.0.31-preview" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CodeLogic" Version="5.0.31-preview" />
                    
Directory.Packages.props
<PackageReference Include="CodeLogic" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CodeLogic --version 5.0.31-preview
                    
#r "nuget: CodeLogic, 5.0.31-preview"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CodeLogic@5.0.31-preview
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CodeLogic&version=5.0.31-preview&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=CodeLogic&version=5.0.31-preview&prerelease
                    
Install as a Cake Tool

CodeLogic 5

NuGet License: MIT

A modular .NET 11 framework for building structured, lifecycle-managed applications with clean separation between core infrastructure, framework orchestration, and library integrations.

Why CodeLogic?

Most .NET apps start with Program.cs and grow organically — services get registered in random order, configuration is scattered, and adding a plugin system means building one from scratch.

CodeLogic gives you a structured lifecycle out of the box:

  • Libraries (reusable integrations) and Applications (your business logic) follow the same 4-phase lifecycle: Configure → Initialize → Start → Stop. Dependencies are always resolved in the right order.
  • Configuration and Localization are built in — not bolted on. Each library and application gets its own config file, auto-generated on first run with defaults and validation. Config can be changed and reloaded while running — libraries can apply a change to just the instance that changed (e.g. one database out of three) without a restart.
  • Plugins are first-class citizens with hot-reload, isolated assembly contexts, and the same lifecycle as libraries.
  • Zero external dependencies. CodeLogic itself has no NuGet dependencies — it's pure .NET 11. External integrations (MySQL, S3, SMTP, etc.) are optional library packages in the CodeLogic.* family.

Install

dotnet add package CodeLogic

What's new in 5.0

  • .NET 11 and C# 15. CodeLogic 5 targets net11.0; the 4.9.x line stays available for .NET 10.
  • Result and Result<T> are unions (Ok | Error and T | Error). A switch over a result is checked for exhaustiveness by the compiler, and results compare by value.
  • Error is a closed record with one subtype per kind (NotFoundError, ValidationError, ConflictError, TimeoutError, ...), so switching on the kind is exhaustive too.
  • Framework events share a closed base (FrameworkEvent): subscribe once to receive all of them.
  • Faster logging and events: a background log writer, near-free disabled log levels, and lock-free event publishing.
  • Result.ThrowIfFailure() and ResultFailedException (which carries the Error) for code where a failure is a bug: tests, scripts, startup.

All 4.x Result members still compile. See Migrating to 5.0.

Results

Expected failures are values, not exceptions:

Result<User> found = await users.FindAsync(id);

var message = found switch
{
    User user           => $"Hello {user.Name}",
    NotFoundError       => "No such user",
    Error error         => error.Message,
    null                => "Empty result",
};

// Where a failure is a bug, throw instead:
User admin = (await users.FindAsync(adminId)).ValueOrThrow();
(await users.DeleteAsync(oldId)).ThrowIfFailure();

Quick Start

var result = await CodeLogic.InitializeAsync(opts =>
{
    opts.FrameworkRootPath = "data/codelogic";
    opts.AppVersion = "1.0.0";
});
if (result.ShouldExit) return;

// Load optional libraries (each is a separate NuGet package)
await Libraries.LoadAsync<MailLibrary>();
await Libraries.LoadAsync<StorageLibrary>();

// Register your application
CodeLogic.RegisterApplication(new MyApplication());

// Boot: Configure → Initialize → Start (in dependency order)
await CodeLogic.ConfigureAsync();
await CodeLogic.StartAsync();

// ... your app runs ...

await CodeLogic.StopAsync();

On first run, CodeLogic creates a data/codelogic/ directory with auto-generated config files for each library and your application. Edit them to configure — no code changes needed.

Lifecycle

Every library and application follows the same 4-phase lifecycle:

InitializeAsync()       Load CodeLogic.json, validate, create LibraryManager
RegisterApplication()   Declare the consuming app
ConfigureAsync()        Run OnConfigureAsync on app + libraries
                        (generates/loads config and localization files)
StartAsync()            Libraries: OnInitializeAsync → OnStartAsync
                        Application: OnInitializeAsync → OnStartAsync

--- application runs ---

StopAsync()             Application OnStopAsync, unload plugins, then stop libraries

Libraries start before the application and stop after it — so your app can always rely on its libraries being available.

Architecture

CodeLogic/
├── src/
│   ├── Core/               # Pure infrastructure — no framework coupling
│   │   ├── Logging/        # ILogger with scoped file-based output
│   │   ├── Configuration/  # Auto-generated JSON config with validation
│   │   ├── Localization/   # Culture-aware string management
│   │   └── Utilities/      # SemanticVersion, StartupValidator, FirstRunManager
│   │
│   ├── Framework/          # Lifecycle orchestration — uses Core
│   │   ├── Libraries/      # ILibrary, LibraryContext, LibraryManager
│   │   ├── Application/    # IApplication, ApplicationContext
│   │   └── Plugins/        # IPlugin, PluginContext, PluginManager (hot-reload)
│   │
│   └── CodeLogic.cs        # Static facade + ICodeLogicRuntime (injectable)
│
├── docs/                   # Documentation (getting started, articles, API reference)
└── samples/                # Example applications

Key Design Principles

Core is standalone. Logging, Configuration, and Localization have zero dependencies on the framework lifecycle. You can use them in any .NET app without the rest of CodeLogic.

Framework wires Core into lifecycle. LibraryContext and ApplicationContext bundle Core engines and hand them to libraries/apps at the right phase — nothing gets constructed until it's needed.

Static facade, injectable runtime. The CodeLogic static class is a convenience facade. For testing or advanced scenarios, inject ICodeLogicRuntime via DI.

Plugins are first-class. Same lifecycle, same access to config/logging/localization, hot-loadable at runtime, each in its own collectible AssemblyLoadContext.

Building a Library

Libraries are reusable integrations that plug into the CodeLogic lifecycle:

public class MyLibrary : ILibrary
{
    public LibraryManifest Manifest { get; } = new()
    {
        Id = "my.library", Name = "My Library", Version = "1.0.0"
    };

    public Task OnConfigureAsync(LibraryContext context)
    {
        context.Configuration.Register<MyLibConfig>();
        return Task.CompletedTask;
    }

    public Task OnInitializeAsync(LibraryContext context)
    {
        var config = context.Configuration.Get<MyLibConfig>();
        // Initialize services using validated config
        return Task.CompletedTask;
    }

    public Task OnStartAsync(LibraryContext context) => Task.CompletedTask;
    public Task OnStopAsync() => Task.CompletedTask;
    public Task<HealthStatus> HealthCheckAsync() => Task.FromResult(HealthStatus.Healthy("OK"));
    public void Dispose() { }
}

Building an Application

public class MyApplication : IApplication
{
    public ApplicationManifest Manifest { get; } = new()
    {
        Id = "myapp", Name = "My App", Version = "1.0.0"
    };

    public Task OnConfigureAsync(ApplicationContext context)
    {
        context.Configuration.Register<AppSettings>();
        return Task.CompletedTask;
    }

    public Task OnInitializeAsync(ApplicationContext context)
    {
        var settings = context.Configuration.Get<AppSettings>();
        return Task.CompletedTask;
    }

    public Task OnStartAsync(ApplicationContext context) => Task.CompletedTask;
    public Task OnStopAsync() => Task.CompletedTask;
}

Building a Plugin

Plugins are hot-loadable at runtime — no restart required:

public sealed class MyPlugin : IPlugin
{
    public PluginManifest Manifest { get; } = new()
    {
        Id = "my.plugin", Name = "My Plugin", Version = "1.0.0",
        Description = "Does something cool", Author = "You"
    };
    public PluginState State { get; private set; } = PluginState.Loaded;

    public Task OnConfigureAsync(PluginContext context)
    {
        context.Configuration.Register<MyPluginConfig>();
        return Task.CompletedTask;
    }

    public Task OnInitializeAsync(PluginContext context)
    {
        var config = context.Configuration.Get<MyPluginConfig>();
        return Task.CompletedTask;
    }

    public Task OnStartAsync(PluginContext context) { State = PluginState.Started; return Task.CompletedTask; }
    public Task OnUnloadAsync() { State = PluginState.Stopped; return Task.CompletedTask; }
    public Task<HealthStatus> HealthCheckAsync() => Task.FromResult(HealthStatus.Healthy("OK"));
    public void Dispose() { }
}

Load it with pluginManager.LoadPluginAsync(pathToDll); see Plugins.

Official Library Packages

The CodeLogic.* library family provides production-ready integrations:

Package Description
CodeLogic.Common Shared utilities: hashing, caching, scheduling, resilience, compression
CodeLogic.Imaging Image editing: resize, crop, rotate, filters, watermarks, format conversion
CodeLogic.Data Unified database framework: LINQ queries, repositories, joins, transactions, schema sync, migrations, backups
CodeLogic.Data.MySql / .PostgreSql / .SqlServer / .Sqlite The database providers for CodeLogic.Data
CodeLogic.Mail SMTP/IMAP email with template engine
CodeLogic.Storage Provider-neutral mounted storage for local filesystems, S3/R2/MinIO, FTP/FTPS, SFTP, WebDAV, Azure Blob, Google Cloud Storage, and OpenStack Swift
CodeLogic.SocialConnect Discord webhooks + Steam Web API
CodeLogic.NetUtils DNS, DNSBL, IP geolocation
CodeLogic.GameNetQuery Game server queries (Valve RCON, Source UDP, Minecraft)
CodeLogic.SystemStats Cross-platform CPU/memory/process monitoring
CodeLogic.GitHelper Git repository management
CodeLogic.TwoFactorAuth TOTP 2FA + QR code generation

In 5.x, CodeLogic.Data replaces CodeLogic.MySQL2, CodeLogic.MSSQL, CodeLogic.PostgreSQL and CodeLogic.SQLite, which stay on the 4.9.x line.

Each library follows the same lifecycle pattern — load it with Libraries.LoadAsync<T>(), configure it via its auto-generated JSON config file, and use it.

For storage, see the complete CL.Storage guide for named multi-provider connections, configuration, transfers, metadata, tags, versions, signed URLs, health checks, and native-provider access. Existing CodeLogic.StorageS3 users can follow the migration guide.

Requirements

  • .NET 11 SDK or later. CodeLogic 5 targets net11.0 only; on .NET 10, stay on CodeLogic 4.9.x (migrating to 5.0)
  • No external NuGet dependencies (the core framework is self-contained)

Documentation

License

MIT — see LICENSE

Product Compatible and additional computed target framework versions.
.NET net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net11.0

    • No dependencies.

NuGet packages (21)

Showing the top 5 NuGet packages that depend on CodeLogic:

Package Downloads
CodeLogic.MySQL2

MySQL with typed LINQ, SQL aggregation, projection pushdown, compiled materializers, covering indexes, retention, and a working cache.

CodeLogic.NetUtils

DNSBL blacklist checking and IP geolocation services for CodeLogic 5 applications

CodeLogic.GitHelper

Git repository management library for CodeLogic 5 applications

CodeLogic.Mail

SMTP/IMAP email library with template engine for CodeLogic 5 applications

CodeLogic.TwoFactorAuth

TOTP 2FA with QR code generation for CodeLogic 5 applications

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
5.0.34-preview 1 11/1/2026
5.0.33-preview 136 10/31/2026
5.0.32-preview 27 10/31/2026
5.0.31-preview 28 10/31/2026
5.0.30-preview 182 10/31/2026
5.0.29-preview 42 10/31/2026
5.0.28-preview 137 10/31/2026
4.9.24 583 10/25/2026
4.8.20 246 9/23/2026
4.8.19 288 9/12/2026
4.1.1 173 8/2/2026
4.1.0 215 8/1/2026
4.0.2 493 5/13/2026
4.0.1 106 5/9/2026
4.0.0 1,304 4/18/2026
3.4.0 349 4/18/2026
3.2.5 279 4/18/2026
3.2.3 2,718 4/17/2026
Loading failed

# Changelog

## 5.0.x

5.0 targets .NET 11 and uses C# 15 in the public API. Read
[Migrating to 5.0](docs/articles/migrating-to-5.md) before upgrading. The 4.x line (.NET 10)
continues on the `support/4.x` branch (published from `release/4.x`).

### Breaking

- **Strong-named** with the CodeLogic key (public key token `53a89305eedc5d98`), shared with CodeLogic.Libs. Assemblies that reference CodeLogic now bind to a strong-named identity.

- **Targets `net11.0` only** (was `net10.0`).
- **`Error` is a `closed record`** with one subtype per kind: `NotFoundError`,
 `ValidationError`, `InternalError`, `UnauthorizedError`, `ConflictError`, `TimeoutError`,
 `UnavailableError`. The factories are unchanged and now return those subtypes. Errors
 compare by value.
- **`Result<T>` is a union of `T | Error`, and `Result` is a union of `Ok | Error`.** A
 `switch` over them is checked for exhaustiveness. All 4.x members remain. A
 `default(Result<T>)` is empty: it matches the `null` pattern and reports `Error.Empty`.
- **The 13 framework events derive from `closed record FrameworkEvent`.** Subscribing to
 `FrameworkEvent` receives all of them.
- Log timestamps, date folders and upper-cased library/plugin IDs use the invariant culture.
 This fixes Buddhist-calendar log paths on th-TH and Turkish-I casing.
- **Plugin dependency resolution is isolated.** `PluginLoadContext` no longer hands a plugin any
 already-loaded assembly with a matching simple name (which could be another plugin's copy).
 `CodeLogic` always resolves to the host; other dependencies resolve from the plugin's own
 `.deps.json`/folder first, then from the host. Assemblies whose types cross the host/plugin
 boundary must now be listed in the new `PluginOptions.SharedAssemblies` if the plugin ships its own
 copy. `PluginLoadContext`'s constructor takes an optional `sharedAssemblies` argument (recompile).
- **Loading a plugin whose ID is already loaded throws** `InvalidOperationException` instead of
 silently replacing the running instance. Use `ReloadPluginAsync`.
- **`ResetAsync` disposes the plugin manager registered with `SetPluginManager`** and clears the
 registration. Create a new manager after a reset. Using a disposed manager throws
 `ObjectDisposedException`.
- `PluginManager.RegisterInProcessPluginAsync` is now truly asynchronous, and `GetAllPlugins` /
 `GetLoadedPlugins` return snapshots instead of live views.

### Added

- `Result<T>.Bind`, `Match`, `MapAsync`, `BindAsync`; `Result.Bind`, `Match`; `Ok`;
 `Error.Empty`; value equality for `Result` and `Result<T>`.
- `Result.ThrowIfFailure()`, the non-generic counterpart of `ValueOrThrow`.
- `ResultFailedException`, which carries the `Error`. `ValueOrThrow` and `ThrowIfFailure` throw it. It
 derives from `InvalidOperationException`, so existing `catch` blocks keep working; checks for the exact
 type (`Assert.Throws<InvalidOperationException>`) need `ThrowsAny` or the new type.

### Changed

- Builds with C# 15. Comparer and capacity collections use collection expression arguments
 (`[with(StringComparer.OrdinalIgnoreCase), ...]`).
- Async methods compile to .NET 11 runtime-native async (shorter stack traces, lower overhead).
- `object` locks are replaced by `System.Threading.Lock`, and `JsonSerializerOptions`
 instances are cached.
- `src` builds with `AnalysisLevel=latest-recommended`.
- `global.json` pins the .NET 11 SDK, and CI installs it from there.

### Fixed

- **Plugin lifecycle is serialized.** Load, register, unload, reload, dispose and hot reload run one at
 a time; readers and health checks work on snapshots, so hot reload can no longer corrupt the plugin
 table or throw "collection was modified". Calling lifecycle methods from inside a plugin's own
 callback throws instead of deadlocking.
- **Plugin failures clean up.** A failed load disposes the instance (after `OnUnloadAsync` when
 `OnStartAsync` was entered) and unloads its load context. A throwing `OnUnloadAsync` no longer skips
 `Dispose`, context unload and removal. Exceptions from `OnPluginLoaded`/`OnPluginUnloaded` handlers
 are no longer reported as plugin failures.
- **Default config generation is serialized with saves and loads**, and atomic writes use a unique
 temp file, so concurrent generate/load/save can no longer collide on `config.json.tmp` or overwrite
 a competing save with defaults.
- **Log directories recover.** A failed directory creation is no longer cached, and a log folder
 deleted while the process runs is recreated on the next line.
- **Log paths compare case-sensitively on Linux** (case-insensitively on Windows and macOS), so
 `App.log` and `app.log` no longer share one writer there.
- **Idle log files are closed even when nothing else is logged**; previously the idle check only ran
 after new entries arrived.
- **Secret masking covers defaults and string lists.** `GetSchema` masks the default value of secret
 string fields and every entry of secret `List<string>` fields. Echoed masks in such lists are
 restored by position; an update that changes the list length while entries are still masked is
 rejected instead of saving the mask.
- `release.yml` checks out the commit that triggered the run (`github.sha`) instead of the branch
 head, so the packed commit is always the verified, tagged one.

### Documentation

- Plugin docs and README examples use the real API (`LoadPluginAsync`/`UnloadPluginAsync`/
 `ReloadPluginAsync`, the actual `PluginManager` constructor, `PluginState` values and manifest
 members), and the isolation section describes what the load context does and does not isolate.
- The Core, Framework and Reference guides and the in-depth getting-started page are now part of the
 docs site.
- The web demo's config endpoints carry authentication/authorization guidance.

### Performance

Measured with `benchmarks/CodeLogic.Benchmarks` against 5.0.28-preview.

- **Log calls no longer touch the disk.** Lines go to one background writer per process, which keeps
 files open, writes them in order and flushes when the queue is empty. An `Info` line costs about
 0.6 µs instead of about 130 µs. `StopAsync`, process exit and an unhandled exception flush pending
 lines; `Logger.FlushAsync()` is available to hosts.
- **Disabled log levels are almost free.** New interpolated-string overloads skip evaluating the holes
 when the level is off (8 ns and 0 B instead of 460 ns and 1 KB). New `ILogger.IsEnabled(LogLevel)`.
 The format overloads take `params ReadOnlySpan<object?>` and don't format when the level is off.
- **`EventBus.Publish` takes no lock and allocates nothing.** Subscribers live in a copy-on-write
 snapshot; a publish sees the subscribers that existed when it started. 8 ns instead of 29 ns for
 one subscriber.

## 4.9.x

### Added

- **Live config changes for libraries.** A library can implement the new optional
 `IConfigReloadable` interface to apply a config change without a restart. It receives the
 old and new section values and applies only what changed, e.g. reconnecting one database
 out of three while the others stay connected. Called after `Libraries.UpdateConfigAsync`,
 `Libraries.ResetConfigAsync`, the new `Libraries.ReloadConfigAsync`, and the file watcher.
- `ConfigDiff`: `Keyed(old, new)` splits keyed instance sections into added / removed /
 changed / unchanged; `ChangedProperties` and `RequiresRestart` report changed fields.
- `ConfigChangedEventArgs` now carries `ConfigType`, `OldValue` / `NewValue` and
 `GetOld<T>()` / `GetNew<T>()`.
- Opt-in config file watcher: `libraries.configHotReload { enabled, debounceMs }` in
 `CodeLogic.json` reloads a library section when its `config*.json` file is edited. Invalid
 edits are logged and ignored; the framework's own writes don't re-trigger it.
- `ConfigChangedEvent` (every committed change) and `ConfigApplyFailedEvent` (a saved change a
 library could not apply). `ConfigReloadedEvent` is now actually published.
- `LoadedLibrary.ConfigApplyError`; a library that failed to apply a change reports Degraded
 health until a later change applies.
- `ConfigField.IsOverridden` in `GetSchema` marks fields set by runtime overrides.

### Changed

- Package author and copyright are now Zyntal.com. NuGet release notes come from this
 changelog, and the NuGet readme no longer shows the oversized logo.
- `LibraryManager` / `Libraries` `UpdateConfigAsync` and `ResetConfigAsync` return
 `ConfigApplyResult` (validation result plus apply error) instead of `ConfigValidationResult`.
 `IsValid` and `Errors` are still there; `Applied` is new. Recompile callers.
- Config files are written atomically (temp file + rename), and changes to config sections
 are serialized.

### Fixed

- Runtime config overrides (`OverrideConfig`) were lost on reload/update and were written to
 the config file when the section was saved. They are now re-applied after every change and
 never persisted.
- `UpdateSectionAsync` / `UpdateConfigAsync` and `GetSchema` threw `TargetParameterCountException`
 for any section with a `List<T>` of objects (they reflected over the list's indexer). List
 entries now appear in the schema as `{list}.{index}.{field}` with secrets masked, and masked
 secrets sent back are restored from the matching previous entry (matched by its `Id` / `Name` /
 `Key` / `…Id` property, else by position when the length is unchanged).

## 4.8.x

### Changed

- Unified the version line with `CodeLogic.Libs` on **4.8.x**. The framework and every
 official library now share one `major.minor`, so a given `4.8.<patch>` means the same
 generation across all packages.
- Moved `major.minor` into a hand-controlled repo-root `version.txt`. The patch component
 is the CI run number, composed at pack time — no version is written back to the
 repository during a release.
- `AssemblyVersion` and `FileVersion` are now derived as `Major.Minor.0.0` (`4.8.0.0`)
 instead of being hardcoded. Assembly binding stays fixed across the whole `4.8` line,
 so every patch loads interchangeably without a binding redirect.

### Removed

- Removed commit-message-driven version bumping from the release workflow. `feat:` and
 `!`/`BREAKING CHANGE` prefixes no longer move `major` or `minor`; the version line moves
 only when `version.txt` is edited.

### Added

- Added a `prerelease` branch trigger to the release workflow, matching `CodeLogic.Libs`.
 Pushing to `prerelease` publishes `4.8.<run>-preview+<sha>`; pushing to `release`
 publishes `4.8.<run>+<sha>`.
- Added [Reference/versioning.md](docs/Reference/versioning.md) documenting the scheme,
 the run-number patch, assembly binding identity, and how to move to the next minor.

### Notes

- The previous published line was `4.1.x` with `AssemblyVersion` pinned at `3.3.0.0`.
 Assembly identity now moves to `4.8.0.0`, which is a binding-breaking change for
 anything compiled against the old value; recompilation is required.