Skip to content

Commit 7c025d7

Browse files
authored
Merge branch 'master' into dependabot/github_actions/actions/checkout-7
2 parents 5c744db + e8f0a4c commit 7c025d7

26 files changed

Lines changed: 1535 additions & 415 deletions

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
[Oo]ut/
99
[Ll]og/
1010
[Ll]ogs/
11-
artifacts/
11+
# Only the .NET build output dir at the solution root — anchored so it does not
12+
# match source folders named Artifacts/ (Windows git matches case-insensitively).
13+
/artifacts/
1214

1315
# Visual Studio / Rider / VS Code
1416
.vs/
@@ -66,3 +68,5 @@ secrets.json
6668
_*.log
6769
_*.txt
6870
*.flag
71+
_*/
72+
*.trx

CLAUDE.md

Lines changed: 115 additions & 55 deletions
Large diffs are not rendered by default.

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ propose changes.
88
NeoReports v1 is a tightly scoped MVP. The scope, the eight non-negotiable
99
architecture rules, and the list of out-of-scope features are documented in
1010
[`CLAUDE.md`](CLAUDE.md), [`docs/MVP-Spec.md`](docs/MVP-Spec.md), and the
11-
decision log [`NeoReports-Decisoes.md`](NeoReports-Decisoes.md). Please skim
11+
decision log [`DECISIONS.md`](DECISIONS.md). Please skim
1212
them first — a change that conflicts with a recorded decision will need that
1313
decision revisited before it can be merged.
1414

@@ -33,7 +33,7 @@ dotnet format
3333
- [ ] Tests pass: `dotnet test` — and new behavior is covered by tests.
3434
- [ ] Style applied: `dotnet format --verify-no-changes` passes.
3535
- [ ] Public API has XML doc comments (the library ships docs).
36-
- [ ] If you changed a recorded decision, update `NeoReports-Decisoes.md` in the
36+
- [ ] If you changed a recorded decision, update `DECISIONS.md` in the
3737
same PR.
3838

3939
CI (build · test · format on .NET 8 and 9) must be green before a PR is merged.

DECISIONS.md

Lines changed: 227 additions & 0 deletions
Large diffs are not rendered by default.

NeoReports-Decisoes.md

Lines changed: 0 additions & 221 deletions
This file was deleted.

NeoReports.sln

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NeoReports.Jobs.Hangfire",
4949
EndProject
5050
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NeoReports.Jobs.UnitTests", "tests\NeoReports.Jobs.UnitTests\NeoReports.Jobs.UnitTests.csproj", "{98F4D154-BE1B-4CC5-9F75-51419E6F75BA}"
5151
EndProject
52+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NeoReports.AspNetCore", "src\Integrations\NeoReports.AspNetCore\NeoReports.AspNetCore.csproj", "{DC55DB23-7998-47DC-9088-FA1B7B3821D1}"
53+
EndProject
54+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NeoReports.AspNetCore.IntegrationTests", "tests\NeoReports.AspNetCore.IntegrationTests\NeoReports.AspNetCore.IntegrationTests.csproj", "{9477CA22-21CE-453F-9DC9-B99D874B2CD0}"
55+
EndProject
56+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "03-async-job-hangfire", "samples\03-async-job-hangfire\03-async-job-hangfire.csproj", "{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}"
57+
EndProject
5258
Global
5359
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5460
Debug|Any CPU = Debug|Any CPU
@@ -287,6 +293,42 @@ Global
287293
{98F4D154-BE1B-4CC5-9F75-51419E6F75BA}.Release|x64.Build.0 = Release|Any CPU
288294
{98F4D154-BE1B-4CC5-9F75-51419E6F75BA}.Release|x86.ActiveCfg = Release|Any CPU
289295
{98F4D154-BE1B-4CC5-9F75-51419E6F75BA}.Release|x86.Build.0 = Release|Any CPU
296+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
297+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
298+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Debug|x64.ActiveCfg = Debug|Any CPU
299+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Debug|x64.Build.0 = Debug|Any CPU
300+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Debug|x86.ActiveCfg = Debug|Any CPU
301+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Debug|x86.Build.0 = Debug|Any CPU
302+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
303+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Release|Any CPU.Build.0 = Release|Any CPU
304+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Release|x64.ActiveCfg = Release|Any CPU
305+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Release|x64.Build.0 = Release|Any CPU
306+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Release|x86.ActiveCfg = Release|Any CPU
307+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1}.Release|x86.Build.0 = Release|Any CPU
308+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
309+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
310+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Debug|x64.ActiveCfg = Debug|Any CPU
311+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Debug|x64.Build.0 = Debug|Any CPU
312+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Debug|x86.ActiveCfg = Debug|Any CPU
313+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Debug|x86.Build.0 = Debug|Any CPU
314+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
315+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Release|Any CPU.Build.0 = Release|Any CPU
316+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Release|x64.ActiveCfg = Release|Any CPU
317+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Release|x64.Build.0 = Release|Any CPU
318+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Release|x86.ActiveCfg = Release|Any CPU
319+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0}.Release|x86.Build.0 = Release|Any CPU
320+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
321+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU
322+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Debug|x64.ActiveCfg = Debug|Any CPU
323+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Debug|x64.Build.0 = Debug|Any CPU
324+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Debug|x86.ActiveCfg = Debug|Any CPU
325+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Debug|x86.Build.0 = Debug|Any CPU
326+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU
327+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Release|Any CPU.Build.0 = Release|Any CPU
328+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Release|x64.ActiveCfg = Release|Any CPU
329+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Release|x64.Build.0 = Release|Any CPU
330+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Release|x86.ActiveCfg = Release|Any CPU
331+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD}.Release|x86.Build.0 = Release|Any CPU
290332
EndGlobalSection
291333
GlobalSection(SolutionProperties) = preSolution
292334
HideSolutionNode = FALSE
@@ -311,5 +353,8 @@ Global
311353
{55D991E5-0FB3-4989-8A1F-D9071F82280C} = {11111111-1111-1111-1111-111111111111}
312354
{CDE09705-9C9A-4D5B-B8EF-A86BCD81CF28} = {11111111-1111-1111-1111-111111111111}
313355
{98F4D154-BE1B-4CC5-9F75-51419E6F75BA} = {22222222-2222-2222-2222-222222222222}
356+
{DC55DB23-7998-47DC-9088-FA1B7B3821D1} = {11111111-1111-1111-1111-111111111111}
357+
{9477CA22-21CE-453F-9DC9-B99D874B2CD0} = {22222222-2222-2222-2222-222222222222}
358+
{21CDAB18-7D2A-4926-B27C-B29F82C01ECD} = {44444444-4444-4444-4444-444444444444}
314359
EndGlobalSection
315360
EndGlobal

PLAN.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# NeoReports — Implementation Plan (v1)
2+
3+
Small, independent PRs, in order. Each one closes with green tests and closes one acceptance criterion (AC-n) from `docs/MVP-Spec.md`. Check the box when done.
4+
5+
## PR 0 — Repository bootstrap
6+
- [x] `global.json`, `build/Directory.Build.props`, `build/Directory.Packages.props`, `.editorconfig`, `.gitignore`.
7+
- [x] `NeoReports.sln` with solution folders mirroring `src/ tests/ benchmarks/ samples/`.
8+
- [x] Minimal CI (`dotnet build` + `dotnet test` + `dotnet format --verify-no-changes`).
9+
- **Acceptance:** `dotnet build` and `dotnet test` pass on an empty repo.
10+
11+
## PR 1 — NeoReports.Abstractions
12+
- [x] Typed-only types and interfaces per D9 (already skeletoned in `src/NeoReports.Abstractions/`).
13+
- [x] English XML docs on everything public.
14+
- **Acceptance:** compiles multi-target (net8/net9), no dependencies beyond `Logging.Abstractions`.
15+
- **Depends on:** PR 0.
16+
17+
## PR 2 — NeoReports.Core: builder + batch pipeline
18+
- [x] Generic fluent builder `ReportBuilder<TRow>` (`From`/`Filter`/`Columns`/`Column`/`To`/`UploadTo`/`Retry`/`OnFailure`; mapping via `From(source, map)` — see D12).
19+
- [x] `IReportRegistry` + `AddReport<TRow>(...)` (DI).
20+
- [x] `ReportRunner`/pipeline: batch loop, `TypedBatchReader` (adapts streaming → batches), `T → object?[]` projection at the writer edge.
21+
- [x] Polly v8 integration (`ResiliencePipeline`) on the batch read.
22+
- [x] `IFailureStrategy`: `AbortReport`, `SkipBatchAndLog`; threshold (consecutive/total/ratio) via `AbortIf` (see D11).
23+
- **Acceptance:** AC-1, AC-11, AC-12, AC-13, AC-14. Pipeline tested with an in-memory fake source. ✅ 13 green tests.
24+
- **Depends on:** PR 1.
25+
26+
## PR 3 — Sources.Sql + Formats.Csv + Destinations.Local (first end-to-end)
27+
- [x] `Source.Sql(...).Keyset(key, pageSize)``IBatchSource<T>`, connection per page, `string?` cursor, parameterized parameters (auto-binds only what the query references).
28+
- [x] `Format.Csv(...)` — non-generic writer (delimiter, encoding, header via `DisplayName`, culture/format formatting, RFC 4180 escaping, CRLF, UTF-8 without BOM).
29+
- [x] `Destination.Local(pathTemplate)``{name}/{date[:fmt]}/{ext}` tokens + parameters; atomic publish (temp + move).
30+
- [x] Sample `01-sql-to-csv-local`.
31+
- **Acceptance:** AC-2, AC-4, AC-7. Reference report runs end-to-end to CSV+Local. SQL tested with Testcontainers. ✅ 26 green tests (13 Core + 4 CSV + 6 Local + 3 SQL/E2E).
32+
- **Depends on:** PR 2.
33+
34+
## PR 4 — Formats.Xlsx + Destinations.S3
35+
- [x] `Format.Xlsx(...)` with ClosedXML (sheet, auto-filter, native types; per-column format/date). Memory grows with rows — see D14.
36+
- [x] Multi-output in a single pass (CSV + XLSX reading the source once) — proven in the E2E `Csv_and_xlsx_are_generated_reading_the_source_once`.
37+
- [x] `Destination.S3(bucket, keyTemplate)` — all-or-nothing upload via `PutObject` (no partial object on failure) — see D15.
38+
- [x] Sample `02-sql-to-xlsx-s3`.
39+
- **Acceptance:** AC-5, AC-6, AC-8. ✅ Cumulative green tests: 34 (13 Core + 4 CSV + 6 Local + 4 Xlsx + 3 S3 + 4 SQL/E2E).
40+
- **Depends on:** PR 3.
41+
42+
## PR 5 — Constant memory (validation)
43+
- [x] `NeoReports.Benchmarks` with `MemoryDiagnoser`: synthetic source (lazy, page by page) of 100k and 1M rows → CSV/XLSX.
44+
- [x] No buffering tweak needed: per-row allocation already constant (~446 B/row @100k vs ~461 B/row @1M — linear, not super-linear).
45+
- **Acceptance:** AC-3 (~constant allocation). ✅ proven. CSV is streaming; XLSX grows with volume by ClosedXML design (D14).
46+
- **Depends on:** PR 4.
47+
48+
## PR 6 — Jobs: single worker
49+
- [x] `IJobStore` (InMemory) + `ICheckpointStore` (no-op) + `InMemoryJobScheduler` in the base package `NeoReports.Jobs` (see D18).
50+
- [x] `Jobs.Hangfire` single-server: `HangfireJobScheduler` + invoker reusing `ReportJobWorker`; params via JSON; DI. SQL storage configured by the host (see D19).
51+
- [x] Cooperative cancellation (per-job CTS / Hangfire `CancellationToken`); idempotent restart (per-job temp + upload only at the end, inherited from the pipeline).
52+
- **Acceptance:** AC-15, AC-16; status `queued→running→completed`. ✅ 16 green tests.
53+
- **Depends on:** PR 2.
54+
55+
## PR 7 — Integrations.AspNetCore: trigger endpoints
56+
- [x] `MapNeoReports("/api")` (Minimal API): `run` (async 202+jobId / `?mode=sync` stream), `GET /reports`, `GET /jobs/{id}`, `POST /jobs/{id}/cancel`, `GET /jobs/{id}/download` (multi-output → zip).
57+
- [x] Validation: sync rejects multi-output (`400`); auth inherited from the host (optional `RequireAuthorization`). Artifact store in Core; status as string — see D20.
58+
- [x] Sample `03-async-job-hangfire` (Hangfire single-server, in-memory storage).
59+
- **Acceptance:** AC-9, AC-10. **Demonstrable MVP.** ✅ 8 integration tests via TestServer.
60+
- **Depends on:** PR 6, PR 4.
61+
62+
## PR 8 — OSS release polish
63+
- [ ] README, LICENSE (MIT), CHANGELOG, NuGet packaging (symbols/snupkg), per-package README.
64+
- **Acceptance:** `dotnet pack` produces all packages; samples documented.
65+
- **Depends on:** PR 7.
66+
67+
---
68+
69+
## Post-MVP (do not start before validating with users)
70+
71+
Likely order once there's traction: dynamic path (config + JsonLogic) → **Blazor UI from the Claude Design handoff** → variants/pipeline → multi-worker + mid-job resume → remaining sources/formats/destinations. Reminder: any UI work always starts from the Claude Design deliverables, never from invented design.

build/Directory.Packages.props

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
<ItemGroup>
88
<!-- Core -->
99
<PackageVersion Include="Polly.Core" Version="8.6.6" />
10-
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
11-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
12-
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
13-
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.8" />
14-
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.8" />
10+
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
11+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.9" />
12+
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
13+
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.9" />
14+
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.9" />
1515
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
1616

1717
<!-- Sources / Formats / Destinations -->
@@ -22,6 +22,7 @@
2222
<!-- Jobs -->
2323
<PackageVersion Include="Hangfire.Core" Version="1.8.14" />
2424
<PackageVersion Include="Hangfire.SqlServer" Version="1.8.14" />
25+
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.14" />
2526
<PackageVersion Include="Hangfire.InMemory" Version="1.0.0" />
2627

2728
<!-- Tests -->
@@ -32,6 +33,7 @@
3233
<PackageVersion Include="NSubstitute" Version="5.3.0" />
3334
<PackageVersion Include="Testcontainers.MsSql" Version="4.12.0" />
3435
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.61" />
36+
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="8.0.11" />
3537

3638
<!-- Benchmarks -->
3739
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />

0 commit comments

Comments
 (0)