Native AOT and trimming
The SDK is trimming and AOT compatible: its own types use source-generated System.Text.Json metadata and it
does not rely on reflection. Your types are the only thing left to cover. Everything the SDK serializes for you,
event data, step results, invoke payloads and function output, goes through InngestOptions.SerializerOptions,
and in a trimmed or AOT app that needs a source-generated JsonSerializerContext that knows your types.
Set it up
Section titled “Set it up”Turn on Native AOT in the project:
<PropertyGroup> <PublishAot>true</PublishAot> <InvariantGlobalization>true</InvariantGlobalization></PropertyGroup>Declare a context with every type that crosses the wire, and put it first in the resolver chain:
using System.Text.Json.Serialization;using Inngest;
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services .AddInngest(options => { options.AppId = "shop"; options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJson.Default); }) .AddFunction<Greeting>(new FunctionOptions { Id = "greet", Triggers = [Trigger.Event("demo/greet")] }, async (context, cancellationToken) => { var reply = await context.Step.RunAsync("compose", () => new Reply($"Hello, {context.Event.Data.Name}!")); await context.Step.SleepAsync("pause", TimeSpan.FromSeconds(1)); return reply; });
var app = builder.Build();app.MapInngest();app.Run();
public sealed record Greeting(string Name);
public sealed record Reply(string Text);
[JsonSerializable(typeof(Greeting))][JsonSerializable(typeof(Reply))]internal sealed partial class AppJson : JsonSerializerContext;samples/Inngest.AotSample in the repository is this app. It publishes with no trimming or AOT warnings and runs
against the dev server as a native binary:
dotnet publish -c Release -r linux-x64Which types to list
Section titled “Which types to list”List every type that the SDK serializes or deserializes for your code:
- the event data type of every function,
TEventData; - the result type of every
RunAsync, theTinTask<T>; - the type each function returns as its output;
- the
TDataofWaitForEventAsync<TData>, and theTResultanddataofInvokeAsync; - the runtime types of
DataandUserin the events you send; - for each function that runs the chunks of a fan-out,
FanOutChunk<TItem>andFanOutOutcome<TResult>[]. The methods return arrays, and the engine writes a function’s output by its runtime type, so a function that returns whatMapAsyncgave listsTResult[].
The SDK covers its own public types, such as FunctionFailure, SendEventsResult, JsonElement and object, so a
step without a result or an untyped event needs nothing extra.
Anonymous types cannot be source generated, so use records where the other guides use new { ... }. A type the
context does not know fails at run time with a NotSupportedException from System.Text.Json, in the step or call
that serializes it; the publish warnings will not catch it, so test the paths that matter.
Let the build find what is missing
Section titled “Let the build find what is missing”A forgotten type is the one thing the publish warnings do not catch. The Inngest package therefore includes an
analyzer that runs in a project which turns reflection-based JSON off: PublishAot or PublishTrimmed set to true,
or JsonSerializerIsReflectionEnabledByDefault set to false. It warns about every type of the list above that no
context lists, for the event data and the result of your function classes, the event data of a delegate function and the
types the steps save, such as the result of each RunAsync<T>:
warning INNGEST010: 'Receipt' is the result of function 'OnRefund', but no JsonSerializerContext lists it with[JsonSerializable], and reflection-based JSON is turned offIn the IDE, a code fix adds the [JsonSerializable] attribute to your context. It is an analyzer and not a generator
that fills the context, because the generator of System.Text.Json does not see code that another generator writes.
Source generator and analyzers says what
the analyzer checks and what it leaves to you, and covers the generator that registers the function classes.
Webhooks
Section titled “Webhooks”MapInngestWebhook needs nothing added to the context. The payload of a webhook is a
JsonElement, which the SDK covers, so the event goes out as the provider sent it, and the verifiers use
System.Security.Cryptography without reflection. What stays yours is the data type of the function that receives the
event, as for any event, and the type a Transform puts in Data instead of the payload, such as a JsonObject with a
header added. An app that maps endpoints for Stripe, GitHub, Shopify, Clerk and an HMAC provider publishes as a Native AOT
binary without warnings, and verifies and sends their webhooks. samples/Inngest.AotSample maps a Stripe endpoint, so
the publish in CI covers the package.
Class functions and middleware
Section titled “Class functions and middleware”AddFunction<TFunction> and AddMiddleware<TMiddleware> keep the constructors they need through
DynamicallyAccessedMembers, so class-based functions and middleware work in AOT apps without extra configuration.
AddInngestFunctions(), which the source generator writes once a project sets
InngestGenerateFunctionRegistration to true, calls AddFunction<TFunction> for each class that has [InngestFunction],
so the app lists no class and nothing scans for them. The AOT sample does this.
Attribute classes work too: AddFunction<TFunction>() reads the attributes of the class it is given when the app
starts, the generic ones such as [EventTrigger<T>] included, which needs no reflection beyond what trimming keeps.
Typed events need nothing either, since their names are static members. The
AOT sample registers an attribute
class, sends it a typed event and invokes another function by its class, and publishes without warnings. Settings
from a configuration section, AddInngest(builder.Configuration.GetSection("Inngest")), are read key by key, not
through the configuration binder, so they work in AOT apps as well.
Packages that are not compatible
Section titled “Packages that are not compatible”Inngest.EntityFrameworkCore is the exception among the packages. Entity Framework Core is not trimming or Native AOT
compatible, and the outbox runs on it, so the package does not declare itself compatible. Its code has no trim or AOT
warning, and the events it stores are serialized from the same InngestOptions.SerializerOptions metadata as every
other event, but an app that publishes with Native AOT cannot use it. See Transactional outbox.
Microsoft’s Native AOT deployment and source generation docs cover the general rules.
inngest-dotnet is a community Inngest SDK for .NET. It is not affiliated with or endorsed by Inngest Inc.