Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ static BadRequestObjectResult MapValidationExceptionAsValidationProblemDetails(R
var groupFailures = exception.Errors.GroupBy(v => v.PropertyName);

foreach (var groupFailure in groupFailures) {
problemDetails.Errors.Add(groupFailure.Key, groupFailure.Select(s => s.ErrorMessage).ToArray());
problemDetails.Errors.Add(groupFailure.Key, [.. groupFailure.Select(s => s.ErrorMessage)]);
}

return new(problemDetails);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public async Task CorrelationId() {

[Test]
public async Task ScheduledEnqueueTime() =>
await Assert.That(_message.ScheduledEnqueueTime).IsEqualTo(new DateTimeOffset(2026, 3, 23, 16, 31, 0, TimeSpan.Zero));
await Assert.That(_message.ScheduledEnqueueTime).IsEqualTo(new(2026, 3, 23, 16, 31, 0, TimeSpan.Zero));

[Test]
[Arguments("MessageId")]
Expand Down
5 changes: 3 additions & 2 deletions src/Benchmarks/Benchmarks/ChannelBatchingBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using BenchmarkDotNet.Attributes;
using System.Buffers;
using System.Runtime.InteropServices;

namespace Benchmarks;

Expand All @@ -26,12 +27,12 @@ public void Setup() {

[Benchmark(Baseline = true, Description = "Current: List.ToArray()")]
public int[] CurrentApproach_ToArray() {
return _buffer.ToArray();
return [.. _buffer];
}

[Benchmark(Description = "Alternative 1: CollectionsMarshal.AsSpan()")]
public ReadOnlySpan<int> Alternative1_CollectionsMarshalAsSpan() {
return System.Runtime.InteropServices.CollectionsMarshal.AsSpan(_buffer);
return CollectionsMarshal.AsSpan(_buffer);
}

[Benchmark(Description = "Alternative 2: ArrayPool rent/copy")]
Expand Down
10 changes: 1 addition & 9 deletions src/Core/gen/Eventuous.Shared.Generators/EventUsageAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,7 @@ static void AnalyzeInvocation(OperationAnalysisContext ctx, KnownTypeSymbols kno
return;
}
// Case 1c: State<T>.On<TEvent>(...) handler registrations
case { Name: "On", TypeArguments.Length: 1 } when IsState(method.ContainingType, knownTypes): {
var eventType = method.TypeArguments[0];

if (IsConcreteEvent(eventType) && !HasEventTypeAttribute(eventType, knownTypes) && !IsExplicitlyRegistered(eventType, ctx, knownTypes)) {
ctx.ReportDiagnostic(Diagnostic.Create(MissingEventTypeAttribute, inv.Syntax.GetLocation(), eventType.ToDisplayString()));
}

return;
}
case { Name: "On", TypeArguments.Length: 1 } when IsState(method.ContainingType, knownTypes):
// Case 1d: EventHandler.On<T>(...) handler registrations
case { Name: "On", TypeArguments.Length: 1 } when IsEventHandler(method.ContainingType, knownTypes): {
var eventType = method.TypeArguments[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ void ProcessNamespace(INamespaceSymbol ns, bool isReferenced) {
}

static bool IsPublicType(INamedTypeSymbol type) {
for (var t = (ITypeSymbol)type; t != null; t = t.ContainingType) {
for (ITypeSymbol? t = type; t != null; t = t.ContainingType) {
if (t.DeclaredAccessibility != Accessibility.Public) return false;
}
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public async Task<Result<TState>> Handle<TCommand>(TCommand command, Cancellatio
// Zero in the global position would mean nothing, so the receiver needs to check the Changes.Length
if (result.Changes.Count == 0) return Result<TState>.FromSuccess(result.State, [], 0);

var proposed = new ProposedAppend(stream, new(result.OriginalVersion), result.Changes.Select(x => new ProposedEvent(x, new())).ToArray());
var proposed = new ProposedAppend(stream, new(result.OriginalVersion), [.. result.Changes.Select(x => new ProposedEvent(x, new()))]);
var final = registeredHandler.AmendAppend?.Invoke(proposed, command) ?? proposed;
var writer = registeredHandler.ResolveWriter(command);
var storeResult = await writer.Store(final, Amend, cancellationToken).NoContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void Dispose() {
_meter.Dispose();
}

public void SetCustomTags(TagList customTags) => _customTags = customTags.ToArray();
public void SetCustomTags(TagList customTags) => _customTags = [.. customTags];
}

record CommandServiceMetricsContext(string ServiceName, string CommandName);
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public async Task<AppendEventsResult> Store(ProposedAppend append, AmendEvent? a
return await writer.AppendEvents(
append.StreamName,
append.ExpectedVersion,
append.Events.Select(ToStreamEvent).ToArray(),
[.. append.Events.Select(ToStreamEvent)],
cancellationToken
)
.NoContext();
Expand Down Expand Up @@ -44,7 +44,7 @@ CancellationToken cancellationToken
return new NewStreamAppend(
a.StreamName,
a.ExpectedVersion,
a.Events.Select(evt => ToStreamEvent(evt, amendEvent)).ToArray()
[.. a.Events.Select(evt => ToStreamEvent(evt, amendEvent))]
);
}
)
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Eventuous.Diagnostics/EventuousDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static class EventuousDiagnostics {

public static void AddDefaultTag(string key, object? value) {
var tags = new List<KeyValuePair<string, object?>>(Tags) { new(key, value) };
Tags = tags.ToArray();
Tags = [.. tags];
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Eventuous.Domain/Aggregate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ protected void EnsureExists(Func<Exception>? getException = null) {
}

public void Load(long version, IEnumerable<object?> events) {
Original = events.Where(x => x != null).ToArray()!;
Original = [.. events.Where(x => x != null)!];
OriginalVersion = version;
Comment on lines 81 to 83
State = Original.Aggregate(State, Fold);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void Dispose() {
_meter.Dispose();
}

public void SetCustomTags(TagList customTags) => _customTags = customTags.ToArray();
public void SetCustomTags(TagList customTags) => _customTags = [.. customTags];
}

record PersistenceMetricsContext(string Component, string Operation);
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ protected async Task Trace(StreamName stream, string operation, Func<Task> task)
}

protected async IAsyncEnumerable<T> TraceEnumerable<T>(
StreamName stream,
string operation,
IAsyncEnumerable<T> source,
StreamName stream,
string operation,
IAsyncEnumerable<T> source,
[EnumeratorCancellation] CancellationToken cancellationToken = default
) {
using var activity = StartActivity(stream, operation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public class TracedEventReader(IEventReader reader) : BaseTracer, IEventReader {
IEventReader Inner { get; } = reader;

public IAsyncEnumerable<StreamEvent> ReadEvents(StreamName stream, StreamReadPosition start, int count, CancellationToken cancellationToken)
=> TraceEnumerable(stream, Operations.ReadEvents, Inner.ReadEvents(stream, start, count, cancellationToken));
=> TraceEnumerable(stream, Operations.ReadEvents, Inner.ReadEvents(stream, start, count, cancellationToken), cancellationToken);

public IAsyncEnumerable<StreamEvent> ReadEventsBackwards(StreamName stream, StreamReadPosition start, int count, CancellationToken cancellationToken)
=> TraceEnumerable(stream, Operations.ReadEvents, Inner.ReadEventsBackwards(stream, start, count, cancellationToken));
=> TraceEnumerable(stream, Operations.ReadEvents, Inner.ReadEventsBackwards(stream, start, count, cancellationToken), cancellationToken);

// ReSharper disable once ConvertToAutoProperty
protected override string ComponentName => _componentName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,7 @@ public async Task<AppendEventsResult[]> AppendEvents(IReadOnlyCollection<NewStre

using var measure = Measure.Start(MetricsSource, new PersistenceMetricsContext(ComponentName, Operations.AppendEvents));

var tracedAppends = appends.Select(a => new NewStreamAppend(
a.StreamName,
a.ExpectedVersion,
a.Events.Select(x => x with { Metadata = x.Metadata.AddActivityTags(activity) }).ToArray()
)
)
.ToArray();
var tracedAppends = appends.Select(a => a with { Events = [.. a.Events.Select(x => x with { Metadata = x.Metadata.AddActivityTags(activity) })] }).ToArray();

try {
var results = await writer.AppendEvents(tracedAppends, cancellationToken).NoContext();
Expand Down
185 changes: 91 additions & 94 deletions src/Core/src/Eventuous.Persistence/EventStore/StoreFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public async Task<AppendEventsResult> Store(
var result = await eventWriter.AppendEvents(
streamName,
expectedStreamVersion,
changes.Select(ToStreamEvent).ToArray(),
[.. changes.Select(ToStreamEvent)],
cancellationToken
)
.NoContext();
Expand Down Expand Up @@ -64,7 +64,7 @@ public async Task<AppendEventsResult[]> Store(
return new NewStreamAppend(
s.StreamName,
s.ExpectedVersion,
s.Changes.Select(evt => ToStreamEvent(evt, amendEvent)).ToArray()
[.. s.Changes.Select(evt => ToStreamEvent(evt, amendEvent))]
);
}
)
Expand All @@ -86,106 +86,103 @@ static NewStreamEvent ToStreamEvent(object evt, AmendEvent? amendEvent) {
}
}

/// <summary>
/// Read a fixed number of events from an existing stream to an array.
/// Returns an empty array when the stream is not found and <paramref name="failIfNotFound"/> is false.
/// </summary>
/// <param name="eventReader">Event reader or event store</param>
/// <param name="stream">Stream name</param>
/// <param name="start">Where to start reading events</param>
/// <param name="count">How many events to read</param>
/// <param name="failIfNotFound">Throw an exception if the stream is not found</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>An array with events retrieved from the stream</returns>
public static async Task<StreamEvent[]> ReadEvents(
this IEventReader eventReader,
StreamName stream,
StreamReadPosition start,
int count,
bool failIfNotFound,
CancellationToken cancellationToken
) {
try {
var result = new List<StreamEvent>();

await foreach (var evt in eventReader.ReadEvents(stream, start, count, cancellationToken).ConfigureAwait(false)) {
result.Add(evt);
}
extension(IEventReader eventReader) {
/// <summary>
/// Read a fixed number of events from an existing stream to an array.
/// Returns an empty array when the stream is not found and <paramref name="failIfNotFound"/> is false.
/// </summary>
/// <param name="stream">Stream name</param>
/// <param name="start">Where to start reading events</param>
/// <param name="count">How many events to read</param>
/// <param name="failIfNotFound">Throw an exception if the stream is not found</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>An array with events retrieved from the stream</returns>
public async Task<StreamEvent[]> ReadEvents(
StreamName stream,
StreamReadPosition start,
int count,
bool failIfNotFound,
CancellationToken cancellationToken
) {
try {
var result = new List<StreamEvent>();

return result.ToArray();
} catch (StreamNotFound) when (!failIfNotFound) {
return [];
}
}
await foreach (var evt in eventReader.ReadEvents(stream, start, count, cancellationToken).NoContext(cancellationToken)) {
result.Add(evt);
}

/// <summary>
/// Read a number of events from a given stream, backwards (from the stream end), to an array.
/// Returns an empty array when the stream is not found and <paramref name="failIfNotFound"/> is false.
/// </summary>
/// <param name="eventReader">Event reader or event store</param>
/// <param name="stream">Stream name</param>
/// <param name="start">Where to start reading events</param>
/// <param name="count">How many events to read</param>
/// <param name="failIfNotFound">Throw an exception if the stream is not found</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>An array with events retrieved from the stream</returns>
public static async Task<StreamEvent[]> ReadEventsBackwards(
this IEventReader eventReader,
StreamName stream,
StreamReadPosition start,
int count,
bool failIfNotFound,
CancellationToken cancellationToken
) {
try {
var result = new List<StreamEvent>();

await foreach (var evt in eventReader.ReadEventsBackwards(stream, start, count, cancellationToken).ConfigureAwait(false)) {
result.Add(evt);
return [.. result];
} catch (StreamNotFound) when (!failIfNotFound) {
return [];
}

return result.ToArray();
} catch (StreamNotFound) when (!failIfNotFound) {
return [];
}
}

/// <summary>
/// Reads a stream from the event store to a collection of <seealso cref="StreamEvent"/>
/// </summary>
/// <param name="eventReader">Event reader or event store</param>
/// <param name="streamName">Name of the stream to read from</param>
/// <param name="start">Stream version to start reading from</param>
/// <param name="failIfNotFound">Set to true if the function needs to throw when the stream isn't found. Default is false, and if there's no
/// stream with the given name found in the store, the function will return an empty collection.</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Collection of events wrapped in <seealso cref="StreamEvent"/></returns>
public static async Task<StreamEvent[]> ReadStream(
this IEventReader eventReader,
StreamName streamName,
StreamReadPosition start,
bool failIfNotFound = true,
CancellationToken cancellationToken = default
) {
const int pageSize = 500;

var streamEvents = new List<StreamEvent>();

var position = start;

try {
while (true) {
var events = await eventReader.ReadEvents(streamName, position, pageSize, failIfNotFound, cancellationToken).NoContext();
streamEvents.AddRange(events);

if (events.Length < pageSize) break;

position = new(position.Value + events.Length);
/// <summary>
/// Read a number of events from a given stream, backwards (from the stream end), to an array.
/// Returns an empty array when the stream is not found and <paramref name="failIfNotFound"/> is false.
/// </summary>
/// <param name="stream">Stream name</param>
/// <param name="start">Where to start reading events</param>
/// <param name="count">How many events to read</param>
/// <param name="failIfNotFound">Throw an exception if the stream is not found</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>An array with events retrieved from the stream</returns>
public async Task<StreamEvent[]> ReadEventsBackwards(
StreamName stream,
StreamReadPosition start,
int count,
bool failIfNotFound,
CancellationToken cancellationToken
) {
try {
var result = new List<StreamEvent>();

await foreach (var evt in eventReader.ReadEventsBackwards(stream, start, count, cancellationToken).ConfigureAwait(false)) {
result.Add(evt);
}

return [.. result];
} catch (StreamNotFound) when (!failIfNotFound) {
return [];
}
} catch (StreamNotFound) when (!failIfNotFound) {
return [];
}

return streamEvents.ToArray();
/// <summary>
/// Reads a stream from the event store to a collection of <seealso cref="StreamEvent"/>
/// </summary>
/// <param name="streamName">Name of the stream to read from</param>
/// <param name="start">Stream version to start reading from</param>
/// <param name="failIfNotFound">Set to true if the function needs to throw when the stream isn't found. Default is false, and if there's no
/// stream with the given name found in the store, the function will return an empty collection.</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Collection of events wrapped in <seealso cref="StreamEvent"/></returns>
public async Task<StreamEvent[]> ReadStream(
StreamName streamName,
StreamReadPosition start,
bool failIfNotFound = true,
CancellationToken cancellationToken = default
) {
const int pageSize = 500;

var streamEvents = new List<StreamEvent>();

var position = start;

try {
while (true) {
var events = await eventReader.ReadEvents(streamName, position, pageSize, failIfNotFound, cancellationToken).NoContext();
streamEvents.AddRange(events);

if (events.Length < pageSize) break;

position = new(position.Value + events.Length);
}
} catch (StreamNotFound) when (!failIfNotFound) {
return [];
}

return [.. streamEvents];
}
}
}
2 changes: 1 addition & 1 deletion src/Core/src/Eventuous.Producers/BaseProducer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public abstract class BaseProducer<TProduceOptions> : IProducer<TProduceOptions>
/// <param name="tracingOptions">Tracing options for the producer</param>
protected BaseProducer(ProducerTracingOptions? tracingOptions = null) {
var options = tracingOptions ?? new ProducerTracingOptions();
DefaultTags = options.AllTags.Concat(EventuousDiagnostics.Tags).ToArray();
DefaultTags = [.. options.AllTags, .. EventuousDiagnostics.Tags];
}

/// <summary>
Expand Down
Loading
Loading