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
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,40 @@ The `NeoReports.Abstractions` contract follows SemVer strictly.

## [Unreleased]

### Removed (breaking, Abstractions ABI — next major)
- **Removed the never-thrown exception types `BatchFailedException`, `SourceFailedException` and
`ThresholdExceededException` from `NeoReports.Abstractions`.** They described a batch/source/
threshold failure but were never thrown anywhere: the pipeline reports those failures through
`ReportRunResult.Status` + its error string and the `IFailureStrategy` decision, not by throwing.
As dead surface in a frozen ABI (rule 7) they were a liability. `NeoReportsException` (the base)
and `ConfigurationException` are unchanged and still used. This is source-breaking for any consumer
that referenced those three types (nothing ever threw them, so no `catch` for them could have
fired) and is therefore slated for the next **major** release.

### Added
- **Streaming XLSX output at constant memory (resolves D14).** Both the MIT single-sheet XLSX writer
and the Pro multi-sheet workbook writer are rebuilt on `DocumentFormat.OpenXml`'s SAX writer and a
hand-assembled `ZipArchive`, streaming each worksheet to a temp file and deflating straight to the
output — bypassing `System.IO.Packaging`'s in-memory buffer. Measured live memory is flat writing
100k→2.4M rows. ClosedXML is removed from both writer packages. The only behavioural change is the
dropped column auto-fit. (`AdjustToContents` can't stream.)
- **Opt-in `AddNeoReportsStartupValidation()`** compiles config-driven reports at host startup so a
malformed document fails fast at boot rather than on the first request.

### Changed
- **Startup warning when the API is mapped without authentication.** `MapNeoReports()` logs a warning
when neither host authentication nor `RequireAuthorization` is configured (auth still inherits from
the host — D20 — this is a nudge, not a behaviour change).

### Fixed
- Keyset cursor now encodes `DateTime`/`byte[]` keys type-faithfully (was corrupting/duplicating rows).
- Local destination blocks path traversal via run-time parameters.
- A failed upload now fails the run instead of reporting success.
- Run-time parameters override same-named static parameters; `@name` matched on an identifier boundary.
- Health/sync-run error responses are scrubbed of connection details (logged server-side instead).
- Multi-artifact zip downloads stream at constant memory (were buffered in a `MemoryStream`).
- Run failures are surfaced through `ILogger` (scoped with job id + report name), not only the event store.

### Changed (breaking, commercial packages only)
- **The Pro packages now require a license key at run time (D70/Epic Q).** `NeoReports.Xlsx.Pro`,
`NeoReports.Sources.Join.Pro` and `NeoReports.QueryBuilder.Pro` previously had **no runtime
Expand Down
45 changes: 4 additions & 41 deletions src/NeoReports.Abstractions/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,47 +14,10 @@ public NeoReportsException(string code, string message, Exception? inner = null)
public string Code { get; }
}

/// <summary>A batch failed after exhausting its retries.</summary>
public sealed class BatchFailedException : NeoReportsException
{
/// <summary>Creates an exception describing a batch that failed after all retries.</summary>
/// <param name="pageNumber">Index of the page that failed.</param>
/// <param name="attemptsExhausted">Number of attempts made before giving up.</param>
/// <param name="message">Human-readable error message.</param>
/// <param name="inner">Optional underlying exception.</param>
public BatchFailedException(int pageNumber, int attemptsExhausted, string message, Exception? inner = null)
: base("NR-BATCH-001", message, inner)
{
PageNumber = pageNumber;
AttemptsExhausted = attemptsExhausted;
}

/// <summary>Index of the page that failed.</summary>
public int PageNumber { get; }

/// <summary>Number of attempts made before giving up.</summary>
public int AttemptsExhausted { get; }
}

/// <summary>The source could not be initialized or connected to.</summary>
public sealed class SourceFailedException : NeoReportsException
{
/// <summary>Creates an exception describing a source that could not be used.</summary>
/// <param name="message">Human-readable error message.</param>
/// <param name="inner">Optional underlying exception.</param>
public SourceFailedException(string message, Exception? inner = null)
: base("NR-SOURCE-001", message, inner) { }
}

/// <summary>A failure threshold (consecutive/total/ratio) was exceeded; the report was aborted.</summary>
public sealed class ThresholdExceededException : NeoReportsException
{
/// <summary>Creates an exception describing an exceeded failure threshold.</summary>
/// <param name="message">Human-readable error message.</param>
/// <param name="inner">Optional underlying exception.</param>
public ThresholdExceededException(string message, Exception? inner = null)
: base("NR-THRESHOLD-001", message, inner) { }
}
// NOTE: BatchFailedException / SourceFailedException / ThresholdExceededException were removed
// (2026-07-30, next major — see CHANGELOG). They were never thrown: the pipeline reports a
// batch/source/threshold failure through ReportRunResult.Status + its error string and the
// IFailureStrategy decision, not by throwing — so they were dead surface in a frozen ABI (rule 7).

/// <summary>A report was registered or configured incorrectly.</summary>
public sealed class ConfigurationException : NeoReportsException
Expand Down