Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Shell scripts must keep LF endings regardless of the contributor's platform.
# CI runs them on Linux; a CRLF checkout puts a stray \r on the shebang line and
# bash fails with "bad interpreter: /usr/bin/env bash^M" — an error that reads like
# a missing interpreter rather than a line-ending problem.
*.sh text eol=lf
25 changes: 15 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,21 @@ jobs:

PUBLISH_DIR="src/Tests/RemoteFactory.TrimmingTests/bin/Release/net9.0/linux-x64/publish"

# Server-only IMPLEMENTATION types must be trimmed out of the published
# assembly. The IServerOnlyRepository interface name is expected to remain
# (referenced from guarded-dead LocalCreate bodies the trimmer keeps —
# tracked as TRIM-005); the implementations must not.
if grep -aq "ServerOnlyDirect" "$PUBLISH_DIR/RemoteFactory.TrimmingTests.dll" \
|| grep -aqP '(?<!I)ServerOnlyRepository' "$PUBLISH_DIR/RemoteFactory.TrimmingTests.dll"; then
echo "::error::Server-only implementation types found in trimmed assembly — trimming did not remove server-only code."
exit 1
fi
echo "Server-only implementation types absent from trimmed assembly."
# Absence gate. Lives in a script so it can be run against an archived
# known-bad artifact and observed failing — see the header of
# verify-trimmed.sh. It checks per factory shape and names the leg on
# failure, and it carries positive controls so a missing or unreadable
# artifact fails loudly instead of passing silently.
#
# The former `(?<!I)ServerOnlyRepository` exemption is gone. It existed
# because the interface name used to survive, and was justified by a
# TRIM-005 diagnosis this repo later disproved. The real cause was the
# static-factory registrar attribute naming the consumer's class, which
# made [DynamicallyAccessedMembers] retain every method on it; with that
# fixed (TRIM-008), IServerOnlyRepository is measured absent and is now
# asserted absent like any other marker.
bash src/Tests/RemoteFactory.TrimmingTests/verify-trimmed.sh \
"$PUBLISH_DIR/RemoteFactory.TrimmingTests.dll"

# Run the trimmed harness; it exits non-zero if any check fails
"$PUBLISH_DIR/RemoteFactory.TrimmingTests"
Expand Down
6 changes: 6 additions & 0 deletions docs/attributes-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ public static partial class PromoteCommand
<sup><a href='/src/docs/reference-app/EmployeeManagement.Domain/Samples/Attributes/MinimalAttributesSamples.cs#L88-L96' title='Snippet source file'>snippet source</a> | <a href='#snippet-attributes-execute' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

**Trimming:** the generated local registration is guarded by `NeatooRuntime.IsServerRuntime`, so a client published with the feature switch set to `false` drops the method body, its `[Service]` dependencies, and their transitive references. See [IL Trimming](trimming.md).

Note that `[Remote]` is **decorative** on `[Execute]` methods — static factories are exempt from the NF0105 `[Remote] public` check, and the generator emits both remote and local registrations regardless, guarding only the local one. What makes the body trimmable is the guard, not `[Remote]`. Keeping `[Remote]` on the method is still worthwhile as intent, and matches how the same marker behaves on class factories.

### [FactoryEventHandler\<T\>]

Class-level attribute that marks a class as a **server-side** static handler for factory events of type `T` (where `T : FactoryEventBase`). The source generator finds one matching `static` method by signature and registers it with `FactoryEventHandlerRegistry`. See [Factory Events](factory-events.md) for the full pattern.
Expand All @@ -209,6 +213,8 @@ public static partial class OrderAuditHandler
}
```

**Trimming:** handler registrations are wrapped in `NeatooRuntime.IsServerRuntime`, so a client published with the feature switch set to `false` drops the handler bodies and their `[Service]` dependencies. Because those registrations are entirely server-guarded, there is nothing left on a trimmed client to resolve — handler registration cannot be verified from a client-side test, only from server-side or untrimmed ones. See [IL Trimming](trimming.md).

Runs in the caller's DI scope via `FactoryEventHandlerRegistry`, triggered by `IFactoryEvents.Raise` during a factory method. All handlers for the event type run sequentially, awaited, sharing the caller's `DbContext` and transaction. A throwing handler aborts the chain and propagates to the caller. For fire-and-forget work that should not participate in the caller's transaction, compose a manual `Task.Run` + `IServiceScopeFactory.CreateScope()` pattern inside the factory method (see the [v1.5.0 release notes](release-notes/v1.5.0.md)).

> **Instance-method handlers are not supported.** Declaring a non-`static` matching method inside a `[FactoryEventHandler<T>]` class emits **NF0503 (Warning)** and is silently skipped at runtime. Client-side reception is handled by implementing `IFactoryEventRelay` on your own class and registering it in DI — see [Factory Events — Client-Side Relay](factory-events.md#client-side-relay-consumer-implements-ifactoryeventrelay) and the [`IFactoryEventRelay`](interfaces-reference.md#ifactoryeventrelay) interface reference.
Expand Down
Loading
Loading