From 863c7a6349ce6f5f69cf790b880446bc1f9e9e2e Mon Sep 17 00:00:00 2001 From: Willem van den Ende Date: Thu, 17 Sep 2026 20:42:49 +0100 Subject: [PATCH] Exercise baseline --- .gitignore | 12 + AGENTS.md | 81 + CLAUDE.md | 1 + LICENSE.TXT | 21 + db-subclass-to-dto.sln | 131 ++ mise.toml | 2 + package-lock.json | 2521 +++++++++++++++++++++++ package.json | 5 + puppeteer.config.json | 15 + scripts/dotnet.sh | 29 + scripts/mermaid.sh | 31 + scripts/run-tests.sh | 5 + src/Db/ClientData.cs | 10 + src/Db/DataMappers.cs | 46 + src/Db/Db.csproj | 13 + src/Db/DbBase.cs | 13 + src/Db/DbContext.cs | 102 + src/Db/OrderData.cs | 16 + src/Domain/Client.cs | 40 + src/Domain/Domain.csproj | 9 + src/Domain/Order.cs | 17 + src/Web/ClientDto.cs | 16 + src/Web/DtoMappers.cs | 92 + src/Web/OrderDto.cs | 19 + src/Web/Web.csproj | 16 + src/Web/WebApp.cs | 16 + tests/Db.Tests/AfterPersistenceTests.cs | 186 ++ tests/Db.Tests/Db.Tests.csproj | 29 + tests/Db.Tests/DomainStaysPlainTests.cs | 30 + tests/Domain.Tests/Domain.Tests.csproj | 26 + tests/TestData/ClientBuilder.cs | 36 + tests/TestData/OrderBuilder.cs | 33 + tests/TestData/TestData.csproj | 25 + tests/Web.Tests/DtoMapperTests.cs | 163 ++ tests/Web.Tests/Web.Tests.csproj | 30 + tests/Web.Tests/WebAppTests.cs | 61 + 36 files changed, 3898 insertions(+) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 LICENSE.TXT create mode 100644 db-subclass-to-dto.sln create mode 100644 mise.toml create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 puppeteer.config.json create mode 100755 scripts/dotnet.sh create mode 100755 scripts/mermaid.sh create mode 100755 scripts/run-tests.sh create mode 100644 src/Db/ClientData.cs create mode 100644 src/Db/DataMappers.cs create mode 100644 src/Db/Db.csproj create mode 100644 src/Db/DbBase.cs create mode 100644 src/Db/DbContext.cs create mode 100644 src/Db/OrderData.cs create mode 100644 src/Domain/Client.cs create mode 100644 src/Domain/Domain.csproj create mode 100644 src/Domain/Order.cs create mode 100644 src/Web/ClientDto.cs create mode 100644 src/Web/DtoMappers.cs create mode 100644 src/Web/OrderDto.cs create mode 100644 src/Web/Web.csproj create mode 100644 src/Web/WebApp.cs create mode 100644 tests/Db.Tests/AfterPersistenceTests.cs create mode 100644 tests/Db.Tests/Db.Tests.csproj create mode 100644 tests/Db.Tests/DomainStaysPlainTests.cs create mode 100644 tests/Domain.Tests/Domain.Tests.csproj create mode 100644 tests/TestData/ClientBuilder.cs create mode 100644 tests/TestData/OrderBuilder.cs create mode 100644 tests/TestData/TestData.csproj create mode 100644 tests/Web.Tests/DtoMapperTests.cs create mode 100644 tests/Web.Tests/Web.Tests.csproj create mode 100644 tests/Web.Tests/WebAppTests.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61135fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.yaks +artifacts/ +bin/ +obj/ +.dotnet-cli/ +.npm-cache/ +node_modules/ +*.user +# NuGet restore scratch dir (DOTNET_CLI_HOME fallout) +.local/ +Library/ +.idea diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0195049 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,81 @@ +# AGENTS.md + +Exercise repo: illustrating the transition from DB-backed domain classes +(`DbBase` inheritance) to plain domain objects + DTOs + mappers. + +**Working Set (for now): the split projects — `src/Domain`, +`src/Db`, `src/Web`** — focus changes there; leave other +projects untouched unless a yak explicitly says otherwise. + +## Rendering diagrams (mermaid → svg) + +Mermaid diagrams are rendered to SVG with mermaid-cli (`mmdc`), pinned as a +devDependency in `package.json`. Install it once per clone: + +```sh +npm install # installs @mermaid-js/mermaid-cli into node_modules/ +``` + +Then render with `npx mmdc`, which resolves the local install — the same +command on Linux, macOS and **Windows** (PowerShell/cmd): + +```sh +npx mmdc -p puppeteer.config.json -i .mmd # -> .mmd.svg +``` + +`-p puppeteer.config.json` passes the headless-Chrome flags (`--no-sandbox` +et al.) — without it mmdc fails to launch the browser in agent/sandbox +shells. On desktop shells it is not needed: plain `npx mmdc -i ` +works. On first run puppeteer downloads its own Chrome. `node_modules/` is +gitignored. + +## Running dotnet + +dotnet SDK 10 is installed **via mise** (see `mise.toml`). It is NOT on PATH in +agent/sandbox shells, and SDK 10 additionally fails without +`DOTNET_CLI_HOME` in some shells. Always use the wrappers: + +```sh +scripts/dotnet.sh build db-subclass-to-dto.sln +scripts/dotnet.sh test db-subclass-to-dto.sln +scripts/run-tests.sh # full test suite, from repo root +``` + +The wrappers cd to the repo root (the test runner must run from the parent +directory of the test project), export `DOTNET_CLI_HOME`, and invoke +`mise exec -- dotnet ...`. Do not call bare `dotnet`. + +If the first-time-use setup fails with `The file ... +'.dotnet-cli/.dotnet' already exists` or `Operation not permitted` (some +agent/sandbox shells block `$HOME/.dotnet-cli`), point the wrapper at a +repo-local home: `DOTNET_CLI_HOME="$PWD/.dotnet-cli" scripts/dotnet.sh ...` +(add `.dotnet-cli/` to `.gitignore`). + +## Yaks (task management with yx) + +Tasks are managed with the `yx` CLI; yaks live in git (event store), snapshot +in `.yaks/` (gitignored). Work one yak at a time: + +```sh +yx list # all yaks and states +yx show "" # full yak context (the task instructions) +yx start "" # set state to wip +yx done "" # set state to done (only after acceptance + # criteria pass and changes are committed) +``` + +- The yak context in `yx show` is the authoritative task description; follow + its acceptance criteria before `yx done`. +- Children block their parent: do prerequisites (nested under a yak) first. +- Add new tasks: `yx add ""` (nest with `--under ""`). +- If yx complains `.yaks is not gitignored`, keep `.yaks` in `.gitignore`. + +## Conventions + +- Target framework: net10.0 (SDK pinned by mise). +- Tests: xUnit, mirroring the src split: `tests/Domain.Tests`, + `tests/Db.Tests`, `tests/Web.Tests`, with the shared test-data builders in + `tests/TestData`. +- No EF Core / SQLite / AutoMapper — the DB layer is a small hand-rolled fake + (pure managed code, cross-platform). +- Commit per yak with a message referencing the yak. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 0000000..fce4ddf --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Living Software LTD (UK) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/db-subclass-to-dto.sln b/db-subclass-to-dto.sln new file mode 100644 index 0000000..59da496 --- /dev/null +++ b/db-subclass-to-dto.sln @@ -0,0 +1,131 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "src\Domain\Domain.csproj", "{A103FFE5-4F20-4DE1-A086-38012E701F9E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Db", "src\Db\Db.csproj", "{3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "src\Web\Web.csproj", "{2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestData", "tests\TestData\TestData.csproj", "{A9EA20AD-2B2D-456B-858A-1575C4B99E11}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain.Tests", "tests\Domain.Tests\Domain.Tests.csproj", "{824867CB-A83B-4968-A29D-D39FC17BCB85}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Db.Tests", "tests\Db.Tests\Db.Tests.csproj", "{7DA9971D-306A-4997-A585-D956F41C7A08}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web.Tests", "tests\Web.Tests\Web.Tests.csproj", "{17001C6E-C47D-4DB2-AC4F-93DE654CEAED}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Debug|x64.ActiveCfg = Debug|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Debug|x64.Build.0 = Debug|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Debug|x86.ActiveCfg = Debug|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Debug|x86.Build.0 = Debug|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Release|Any CPU.Build.0 = Release|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Release|x64.ActiveCfg = Release|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Release|x64.Build.0 = Release|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Release|x86.ActiveCfg = Release|Any CPU + {A103FFE5-4F20-4DE1-A086-38012E701F9E}.Release|x86.Build.0 = Release|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Debug|x64.ActiveCfg = Debug|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Debug|x64.Build.0 = Debug|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Debug|x86.ActiveCfg = Debug|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Debug|x86.Build.0 = Debug|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Release|Any CPU.Build.0 = Release|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Release|x64.ActiveCfg = Release|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Release|x64.Build.0 = Release|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Release|x86.ActiveCfg = Release|Any CPU + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA}.Release|x86.Build.0 = Release|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Debug|x64.ActiveCfg = Debug|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Debug|x64.Build.0 = Debug|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Debug|x86.ActiveCfg = Debug|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Debug|x86.Build.0 = Debug|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Release|Any CPU.Build.0 = Release|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Release|x64.ActiveCfg = Release|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Release|x64.Build.0 = Release|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Release|x86.ActiveCfg = Release|Any CPU + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3}.Release|x86.Build.0 = Release|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Debug|x64.ActiveCfg = Debug|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Debug|x64.Build.0 = Debug|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Debug|x86.ActiveCfg = Debug|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Debug|x86.Build.0 = Debug|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Release|Any CPU.Build.0 = Release|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Release|x64.ActiveCfg = Release|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Release|x64.Build.0 = Release|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Release|x86.ActiveCfg = Release|Any CPU + {A9EA20AD-2B2D-456B-858A-1575C4B99E11}.Release|x86.Build.0 = Release|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Debug|x64.ActiveCfg = Debug|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Debug|x64.Build.0 = Debug|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Debug|x86.ActiveCfg = Debug|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Debug|x86.Build.0 = Debug|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Release|Any CPU.Build.0 = Release|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Release|x64.ActiveCfg = Release|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Release|x64.Build.0 = Release|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Release|x86.ActiveCfg = Release|Any CPU + {824867CB-A83B-4968-A29D-D39FC17BCB85}.Release|x86.Build.0 = Release|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Debug|x64.Build.0 = Debug|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Debug|x86.ActiveCfg = Debug|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Debug|x86.Build.0 = Debug|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Release|Any CPU.Build.0 = Release|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Release|x64.ActiveCfg = Release|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Release|x64.Build.0 = Release|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Release|x86.ActiveCfg = Release|Any CPU + {7DA9971D-306A-4997-A585-D956F41C7A08}.Release|x86.Build.0 = Release|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Debug|x64.ActiveCfg = Debug|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Debug|x64.Build.0 = Debug|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Debug|x86.ActiveCfg = Debug|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Debug|x86.Build.0 = Debug|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Release|Any CPU.Build.0 = Release|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Release|x64.ActiveCfg = Release|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Release|x64.Build.0 = Release|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Release|x86.ActiveCfg = Release|Any CPU + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A103FFE5-4F20-4DE1-A086-38012E701F9E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {3C19CC9F-A2F5-4A7C-81C0-20CCEDD3E1EA} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {2336C0CF-86CD-4DEC-B2AF-24D0AFF0D6F3} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {A9EA20AD-2B2D-456B-858A-1575C4B99E11} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {824867CB-A83B-4968-A29D-D39FC17BCB85} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {7DA9971D-306A-4997-A585-D956F41C7A08} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {17001C6E-C47D-4DB2-AC4F-93DE654CEAED} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + EndGlobalSection +EndGlobal diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..df386ef --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[tools] +dotnet = "10" diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..11bc9dc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2521 @@ +{ + "name": "db-subclass-to-dto", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@mermaid-js/mermaid-cli": "^11.17.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.1.0.tgz", + "integrity": "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.8.0", + "tinyexec": "^1.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.20", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.20.tgz", + "integrity": "sha512-CMqMy7OaXl9W0eq1Uy7L7i2Y/anPvHmFmESd2CEw0t5YvZhcVCeo4MBevAmswRllX7Y2dEidA4ozGPunLSTQpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.9", + "@floating-ui/utils": "^0.2.12", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.3.1.tgz", + "integrity": "sha512-wmglKKPDIkgV3aWlZzWECCPoGIkYCulzBwxG9+w7rc5BGapZ6cPMpoPOT8k36J0Ni7PPX6c/rsoMWfS4d1MUMg==", + "dev": true, + "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", + "engines": { + "node": ">=6" + } + }, + "node_modules/@headlessui/react": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.10.tgz", + "integrity": "sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.20.2", + "@react-aria/interactions": "^3.25.0", + "@tanstack/react-virtual": "^3.13.9", + "use-sync-external-store": "^1.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@headlessui/react/node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@headlessui/tailwindcss": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@headlessui/tailwindcss/-/tailwindcss-0.2.2.tgz", + "integrity": "sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "tailwindcss": "^3.0 || ^4.0" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.7.tgz", + "integrity": "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^2.0.1", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.4.tgz", + "integrity": "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.8", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.8.tgz", + "integrity": "sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.10.tgz", + "integrity": "sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@mermaid-js/layout-elk": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@mermaid-js/layout-elk/-/layout-elk-0.2.3.tgz", + "integrity": "sha512-m46RVGfvKK1PSMdAFGDz2nksI4jusEkFxvf0lb48bYWmqLQxZpjtpruiw29MbOy9rlWJtuMf9EzG9fAF8CVZbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "elkjs": "^0.9.3" + }, + "peerDependencies": { + "mermaid": "^11.0.2" + } + }, + "node_modules/@mermaid-js/layout-tidy-tree": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@mermaid-js/layout-tidy-tree/-/layout-tidy-tree-0.2.2.tgz", + "integrity": "sha512-8RmjDXjKJBxqTS1mICStm8zWRM45fSzs0SOrkp28+KsOGS2YEMFMVTwwRU8CsC6M1L+pDYZVjf1m9AC1c9Wndg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "d3": "^7.9.0" + }, + "peerDependencies": { + "mermaid": "^11.0.2" + } + }, + "node_modules/@mermaid-js/mermaid-cli": { + "version": "11.17.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/mermaid-cli/-/mermaid-cli-11.17.0.tgz", + "integrity": "sha512-pxF8rmheBb1gabIN4GFDCNWzIUsOzcEsSp7H5CQoL7gv7X8U8r6FFKtdMzEmZMeHmziNAq2B7J7LG+CDSfIg9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-free": "^6.0.0 || ^7.0.1", + "@mermaid-js/layout-elk": "^0.1.5 || ^0.2.0", + "@mermaid-js/mermaid-zenuml": "^0.2.0", + "chalk": "^5.0.1", + "commander": "^13.1.0", + "import-meta-resolve": "^4.1.0", + "katex": "^0.16.25", + "mermaid": "^11.14.0", + "p-limit": "^6.2.0" + }, + "bin": { + "mmdc": "src/cli.js" + }, + "engines": { + "node": "^18.19 || >=20.0" + }, + "optionalDependencies": { + "@mermaid-js/layout-tidy-tree": "^0.2.1" + }, + "peerDependencies": { + "puppeteer": "^23 || ^24 || ^25" + } + }, + "node_modules/@mermaid-js/mermaid-zenuml": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@mermaid-js/mermaid-zenuml/-/mermaid-zenuml-0.2.3.tgz", + "integrity": "sha512-RGBtgL6fc+5Y2Jm9odOH9HRJ80BP4l6atBYnAK5bBzEowF0PU3UtvZRRcbFxImPGPuLIzqZq31ur8lVO0AoF3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zenuml/core": "^3.47.0" + }, + "peerDependencies": { + "mermaid": "^10 || ^11" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.1.tgz", + "integrity": "sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.2" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.100.tgz", + "integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==", + "dev": true, + "license": "MIT", + "optional": true, + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.100", + "@napi-rs/canvas-darwin-arm64": "0.1.100", + "@napi-rs/canvas-darwin-x64": "0.1.100", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", + "@napi-rs/canvas-linux-arm64-musl": "0.1.100", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-musl": "0.1.100", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", + "@napi-rs/canvas-win32-x64-msvc": "0.1.100" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz", + "integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz", + "integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz", + "integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz", + "integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz", + "integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz", + "integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.100.tgz", + "integrity": "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz", + "integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz", + "integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-arm64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz", + "integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz", + "integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.2.2.tgz", + "integrity": "sha512-q2BU4YfO9h/Wt7IcWPcggpOOqLk2Tbs1hDwolvKZrweRjy751OJBKMN9zO5bfD0pzU7X/tvKw/exQds4pM/LOg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "modern-tar": "^0.8.4", + "yargs": "^18.0.0" + }, + "bin": { + "browsers": "lib/main-cli.js" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "proxy-agent": ">=8.0.1", + "yauzl": "^2.10.0 || ^3.4.0" + }, + "peerDependenciesMeta": { + "proxy-agent": { + "optional": true + }, + "yauzl": { + "optional": true + } + } + }, + "node_modules/@react-aria/focus": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.22.1.tgz", + "integrity": "sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.28.1.tgz", + "integrity": "sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.34.0", + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.1.tgz", + "integrity": "sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.14.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.13.tgz", + "integrity": "sha512-JbDTAwtzZ99aOeCrAfW5EsE5KSq5RWh6Af2dtFwyLIIk48Ja7vm6n4axu/43T3vfjFnEGJalxJ1wyYjSQD6bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.17.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.17.11", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.11.tgz", + "integrity": "sha512-+ILjvtHup6Y2hzQ6YzwMgX1Q+oQpxEGOXCEsCNaPoIP0VxMbizIBTmYTDtkerkIQS8/CbP1BRuyt8V/8BCsy1g==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.12.tgz", + "integrity": "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, + "node_modules/@zenuml/core": { + "version": "3.50.1", + "resolved": "https://registry.npmjs.org/@zenuml/core/-/core-3.50.1.tgz", + "integrity": "sha512-Q18BXvIfhRuGw0JfO4e+pltG04Phhv2E7n35EWTNSZKKH/HEkyE4Sh7KERb9dqaOfjOdRdKvLG0YekjK3fcDXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.16", + "@headlessui/react": "^2.2.9", + "@headlessui/tailwindcss": "^0.2.2", + "antlr4": "~4.11.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "color-string": "^2.1.4", + "dompurify": "^3.3.1", + "highlight.js": "^11.10.0", + "html-to-image": "^1.11.13", + "jotai": "^2.16.1", + "marked": "^4.3.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "tailwind-merge": "^3.4.0" + }, + "bin": { + "zenuml": "dist/cli/zenuml.mjs" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.97" + }, + "peerDependencies": { + "playwright-core": "1.57.0" + }, + "peerDependenciesMeta": { + "playwright-core": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antlr4": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.11.0.tgz", + "integrity": "sha512-GUGlpE2JUjAN+G8G5vY+nOoeyNhHsXoIJwP1XF1oRw89vifA1K46T6SEkwLwr7drihN7I/lf0DIjKc4OZvBX8w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=14" + } + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chromium-bidi": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-17.0.2.tgz", + "integrity": "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "engines": { + "node": ">=20.19.0 <22.0.0 || >=22.12.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-name": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.1.tgz", + "integrity": "sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dev": true, + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cytoscape": { + "version": "3.34.3", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.3.tgz", + "integrity": "sha512-yfYGhRcGAntq6YBD583j4n0Eg3jIxvWmZtz/5uz9UYkeIStSlMxuUja+ec5j3iBD8nv1rwaOAYMW09tBdkSeaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "dev": true, + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dev": true, + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "dev": true, + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1680125", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1680125.tgz", + "integrity": "sha512-rVARKAvOFJCFkvgX01VLvYqKIvt+cRCqvUxIhB2oAqVGPalxk3w3sb5TUGDagpow/EFblBpm/Zl/29QA2dd+jA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/dompurify": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.15.tgz", + "integrity": "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==", + "dev": true, + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/elkjs": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", + "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==", + "dev": true, + "license": "EPL-2.0" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fastdom": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.12.tgz", + "integrity": "sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "strictdom": "^1.0.1" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "dev": true, + "license": "MIT" + }, + "node_modules/highlight.js": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.12.0.tgz", + "integrity": "sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/html-to-image": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz", + "integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/jotai": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.20.3.tgz", + "integrity": "sha512-N8L9FUbeGDP+0qNJw1FebOxt+5hk+jTOwUA2LlRE4C081jUcY6F1T/FSETp4DT2/INMtiSRZyNm2D+yZdzrSgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0", + "@babel/template": ">=7.0.0", + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@babel/template": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==", + "dev": true + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mermaid": { + "version": "11.17.2", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.17.2.tgz", + "integrity": "sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@braintree/sanitize-url": "^7.1.2", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.2.1", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.34.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.21", + "dompurify": "^3.3.3", + "es-toolkit": "^1.45.1", + "fastdom": "1.0.12", + "katex": "^0.16.47", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/modern-tar": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.8.5.tgz", + "integrity": "sha512-snEhs+6G5Tjd4I7tLCDOaoln2RgE0bD19RzEKgvgK2hZ5VKy3MpLhLTZ2fWpXSTg4K2cyPwp+VHATFJhxfnOeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/puppeteer": { + "version": "25.11.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.11.0.tgz", + "integrity": "sha512-n+psxZFH/qlJ3gwTZMxTLUZoE98FAOEjT/BSxHXvPmNCGAfN/kq0UnIZ8m9RB4rAVvJr7zRHUnKKg98GLMUyyQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@puppeteer/browsers": "3.2.2", + "chromium-bidi": "17.0.2", + "devtools-protocol": "0.0.1680125", + "lilconfig": "^3.1.3", + "puppeteer-core": "25.11.0", + "typed-query-selector": "^2.12.2" + }, + "bin": { + "puppeteer": "lib/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/puppeteer-core": { + "version": "25.11.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.11.0.tgz", + "integrity": "sha512-Ujym7JbxoAqanaxGcHgmTomfhC01HfDCSuGZ8Hs9boHrDjzbNVMnFRJo2KtbDT9LCRgeJoFnkn9JQZ41eEUrLw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "3.2.2", + "chromium-bidi": "17.0.2", + "devtools-protocol": "0.0.1680125", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.3", + "ws": "^8.21.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-aria": { + "version": "3.52.1", + "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.52.1.tgz", + "integrity": "sha512-fdZZruC9/x/joCg0mhKGs5aHpwrXLCSZ4GOJmhhYyiE0ffyEsk9MLFt9LCOCF9tn8ErTD+UDQP4oDUSlZkmpCg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.4", + "@internationalized/number": "^3.6.8", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.36.1", + "@swc/helpers": "^0.5.0", + "aria-hidden": "^1.2.3", + "clsx": "^2.0.0", + "react-stately": "3.50.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/react-stately": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.50.0.tgz", + "integrity": "sha512-TnckvpDQGU0672wEaLZqdzvhuSeXWjgW6vijMWxiKOxc8CosQUfPGISdcZyfHqjX3XMjEtRxj4w9HumvX4h4mw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.4", + "@internationalized/number": "^3.6.8", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.36.1", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/strictdom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strictdom/-/strictdom-1.0.1.tgz", + "integrity": "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz", + "integrity": "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/use-sync-external-store": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", + "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/uuid": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.2.tgz", + "integrity": "sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.3.tgz", + "integrity": "sha512-uuN0goWfxP22B7J/uAgBpOYNPttC+XVseYE+rSY5+rQ+YBeVz/VORw8WbmLVcqW78zNg5A4qnjNXYUWR3il2ig==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz", + "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..565a1f9 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@mermaid-js/mermaid-cli": "^11.17.0" + } +} diff --git a/puppeteer.config.json b/puppeteer.config.json new file mode 100644 index 0000000..1333d16 --- /dev/null +++ b/puppeteer.config.json @@ -0,0 +1,15 @@ +{ + "executablePath": "/home/willem/.cache/puppeteer/chrome/linux-152.0.7977.75/chrome-linux64/chrome", + "headless": true, + "args": [ + "--no-sandbox", + "--disable-setuid-sandbox", + "--disable-gpu", + "--disable-gpu-compositing", + "--disable-software-rasterizer", + "--disable-dev-shm-usage", + "--disable-features=VizDisplayCompositor", + "--use-gl=swiftshader", + "--disable-gpu-driver-bug-workaround" + ] +} \ No newline at end of file diff --git a/scripts/dotnet.sh b/scripts/dotnet.sh new file mode 100755 index 0000000..3e5b137 --- /dev/null +++ b/scripts/dotnet.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Run any dotnet command for this repo. +# +# Why a wrapper: +# - dotnet is installed via mise (see mise.toml, dotnet = "10"); it is NOT on +# PATH in agent/sandbox shells, so plain `dotnet` fails with 127. +# - dotnet SDK 10 fails with "The user's home directory could not be +# determined" unless DOTNET_CLI_HOME is set. +# - The test runner must be invoked from the repo root (the parent directory +# of the test project), so we always cd here first. +# +# Usage: scripts/dotnet.sh +# scripts/dotnet.sh test db-subclass-to-dto.sln +# scripts/dotnet.sh build +set -euo pipefail + +cd "$(dirname "$0")/.." +export DOTNET_CLI_HOME="${DOTNET_CLI_HOME:-$HOME/.dotnet-cli}" +export DOTNET_NOLOGO=1 +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + +if ! command -v mise >/dev/null 2>&1; then + echo "error: mise not found on PATH; install mise or put dotnet on PATH" >&2 + exit 1 +fi +if [ "$#" -eq 0 ]; then + exec mise exec -- dotnet --version +fi +exec mise exec -- dotnet "$@" \ No newline at end of file diff --git a/scripts/mermaid.sh b/scripts/mermaid.sh new file mode 100755 index 0000000..c537e77 --- /dev/null +++ b/scripts/mermaid.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Render a mermaid diagram to SVG using mmdc with sandbox/GPU workarounds +# for Ubuntu 24.04+ (AppArmor restrictions). +# +# Usage: scripts/mermaid.sh input.mmd [output.svg] +# scripts/mermaid.sh input.mmd - # output to stdout + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +CONFIG="$REPO_ROOT/puppeteer.config.json" + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 [output.svg|-]" >&2 + exit 1 +fi + +INPUT="$1" +shift + +ARGS=("-i" "$INPUT" "-p" "$CONFIG") + +if [[ $# -gt 0 ]]; then + OUTPUT="$1" + shift + ARGS+=("-o" "$OUTPUT") +fi + +ARGS+=("$@") + +exec mmdc "${ARGS[@]}" \ No newline at end of file diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh new file mode 100755 index 0000000..7716443 --- /dev/null +++ b/scripts/run-tests.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Run the full test suite from the repo root (see scripts/dotnet.sh for why). +# Usage: scripts/run-tests.sh [extra dotnet test args] +set -euo pipefail +exec "$(cd "$(dirname "$0")" && pwd)/dotnet.sh" test db-subclass-to-dto.sln "$@" \ No newline at end of file diff --git a/src/Db/ClientData.cs b/src/Db/ClientData.cs new file mode 100644 index 0000000..47f709c --- /dev/null +++ b/src/Db/ClientData.cs @@ -0,0 +1,10 @@ +using Domain; + +namespace Db; + +/// Persisted shape of a : data only. +public sealed class ClientData : DbBase +{ + /// Display name of the client. + public string Name { get; set; } = string.Empty; +} diff --git a/src/Db/DataMappers.cs b/src/Db/DataMappers.cs new file mode 100644 index 0000000..3a1924e --- /dev/null +++ b/src/Db/DataMappers.cs @@ -0,0 +1,46 @@ +using Domain; + +namespace Db; + +/// Hand-written mappers between domain objects and persistence records; each method maps one direction and never mutates its input. +public static class DataMappers +{ + /// Map a to its persistence record . + public static ClientData ToClientData(Client client) + { + ArgumentNullException.ThrowIfNull(client); + return new ClientData { Id = client.Id, Name = client.Name }; + } + + /// Map an to its persistence record ; the client back-reference becomes a foreign key. + public static OrderData ToOrderData(Order order) + { + ArgumentNullException.ThrowIfNull(order); + return new OrderData + { + Id = order.Id, + Description = order.Description, + TotalInCents = order.TotalInCents, // plain int all the way through + ClientId = order.Client?.Id ?? Guid.Empty, + }; + } + + /// Map a record back to a . + public static Client FromClientData(ClientData row) + { + ArgumentNullException.ThrowIfNull(row); + return new Client { Id = row.Id, Name = row.Name }; + } + + /// Map an record back to an ; the context reassembles the graph. + public static Order FromOrderData(OrderData row) + { + ArgumentNullException.ThrowIfNull(row); + return new Order + { + Id = row.Id, + Description = row.Description, + TotalInCents = row.TotalInCents, // plain int all the way through + }; + } +} diff --git a/src/Db/Db.csproj b/src/Db/Db.csproj new file mode 100644 index 0000000..405d1fa --- /dev/null +++ b/src/Db/Db.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/src/Db/DbBase.cs b/src/Db/DbBase.cs new file mode 100644 index 0000000..192f57b --- /dev/null +++ b/src/Db/DbBase.cs @@ -0,0 +1,13 @@ +using Domain; + +namespace Db; + +/// Persistence framework base class: only the data records derive from it, never the domain objects. +public class DbBase +{ + /// Primary key; until the context assigns one on save. + public Guid Id { get; set; } + + /// Back-reference to the owning ; lives on persistence records only. + public DbContext? DbContext { get; internal set; } +} diff --git a/src/Db/DbContext.cs b/src/Db/DbContext.cs new file mode 100644 index 0000000..d8126fe --- /dev/null +++ b/src/Db/DbContext.cs @@ -0,0 +1,102 @@ +using Domain; + +namespace Db; + +/// The fake DbContext: per-type tables of data-only records (never domain objects); Save inserts-or-refreshes rows, Load reassembles domain objects. +public class DbContext +{ + private readonly List _clients = new(); + private readonly List _orders = new(); + + // domain object reference → its persisted record + private readonly Dictionary _byRef = new(); + + public IReadOnlyList Clients => _clients.AsReadOnly(); + public IReadOnlyList Orders => _orders.AsReadOnly(); + + /// The strongly-typed "table" for . + public IReadOnlyList Set() where T : DbBase + => typeof(T) switch + { + var t when t == typeof(ClientData) => (IReadOnlyList)_clients.AsReadOnly(), + var t when t == typeof(OrderData) => (IReadOnlyList)_orders.AsReadOnly(), + _ => throw new NotSupportedException($"No per-type table for {typeof(T)}"), + }; + + /// Persist a : insert on first save (assigning its id), refresh in place on later ones. + public void Save(Client client) + { + ArgumentNullException.ThrowIfNull(client); + var fresh = DataMappers.ToClientData(client); + if (_byRef.TryGetValue(client, out var existing)) + { + var row = (ClientData)existing; // refresh, keep row identity + row.Name = fresh.Name; + return; + } + var id = Guid.NewGuid(); + fresh.Id = client.Id = id; // write-back identity + fresh.DbContext = this; + _clients.Add(fresh); + _byRef[client] = fresh; + } + + /// Persist an (same insert-or-refresh rule; save its client first so the foreign key can be filled). + public void Save(Order order) + { + ArgumentNullException.ThrowIfNull(order); + var fresh = DataMappers.ToOrderData(order); + if (_byRef.TryGetValue(order, out var existing)) + { + var row = (OrderData)existing; // refresh, keep row identity + row.Description = fresh.Description; + row.TotalInCents = fresh.TotalInCents; + row.ClientId = fresh.ClientId; + return; + } + var id = Guid.NewGuid(); + fresh.Id = order.Id = id; // write-back identity + fresh.DbContext = this; + _orders.Add(fresh); + _byRef[order] = fresh; + } + + /// Load the stored under with all of its orders, or null. + public Client? LoadClient(Guid id) + { + var row = Find(r => r.Id == id); + if (row is null) return null; + var client = DataMappers.FromClientData(row); + foreach (var orderRow in _orders.Where(o => o.ClientId == id)) + client.Orders.Add(DataMappers.FromOrderData(orderRow)); // fix-up wires Order.Client + return client; + } + + /// Load the stored under with its client, or null; use for a client with all its orders. + public Order? LoadOrder(Guid id) + { + var row = Find(r => r.Id == id); + if (row is null) return null; + var order = DataMappers.FromOrderData(row); + if (row.ClientId != Guid.Empty) + { + var clientRow = Find(c => c.Id == row.ClientId); + if (clientRow is not null) + { + var client = DataMappers.FromClientData(clientRow); + client.Orders.Add(order); // fix-up wires Order.Client + } + } + return order; + } + + /// Search the per-type table for a record whose predicate matches. + public T? Find(Func predicate) where T : DbBase + => Set().FirstOrDefault(predicate); + + /// True if has been saved through this context. + public bool IsTracked(object entity) => _byRef.ContainsKey(entity); + + /// All data records this context currently manages. + public IReadOnlyList Tracked => _byRef.Values.ToList(); +} diff --git a/src/Db/OrderData.cs b/src/Db/OrderData.cs new file mode 100644 index 0000000..62fb580 --- /dev/null +++ b/src/Db/OrderData.cs @@ -0,0 +1,16 @@ +using Domain; + +namespace Db; + +/// Persisted shape of an : data only, object graph flattened to ids. +public sealed class OrderData : DbBase +{ + /// Free-text description of what this order is for. + public string Description { get; set; } = string.Empty; + + /// Order total in cents of the stored order. + public int TotalInCents { get; set; } + + /// Foreign key to the owning client; empty when the order has no client. + public Guid ClientId { get; set; } +} diff --git a/src/Domain/Client.cs b/src/Domain/Client.cs new file mode 100644 index 0000000..04264b5 --- /dev/null +++ b/src/Domain/Client.cs @@ -0,0 +1,40 @@ +using System.Collections.ObjectModel; + +namespace Domain; + +/// A plain domain object: no persistence base class, no persistence context knowledge. +public class Client +{ + /// Identity: until the persistence layer assigns one on save. + public Guid Id { get; set; } + + /// Display name of the client. + public string Name { get; set; } = string.Empty; + + /// The client's orders; adding one wires its back-reference. + public ClientOrders Orders { get; } + + public Client() + { + Orders = new ClientOrders(this); + } +} + +/// An collection that fakes EF's navigation fix-up. +public sealed class ClientOrders : Collection +{ + private readonly Client _owner; + + public ClientOrders(Client owner) + { + _owner = owner; + } + + // Add(...) and Insert(...) both funnel through InsertItem. + protected override void InsertItem(int index, Order item) + { + ArgumentNullException.ThrowIfNull(item); + item.Client = _owner; // navigation fix-up + base.InsertItem(index, item); + } +} diff --git a/src/Domain/Domain.csproj b/src/Domain/Domain.csproj new file mode 100644 index 0000000..9ed914b --- /dev/null +++ b/src/Domain/Domain.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/src/Domain/Order.cs b/src/Domain/Order.cs new file mode 100644 index 0000000..e1d0729 --- /dev/null +++ b/src/Domain/Order.cs @@ -0,0 +1,17 @@ +namespace Domain; + +/// A plain domain order: no persistence base class, no persistence context knowledge. +public class Order +{ + /// Identity: until the persistence layer assigns one on save. + public Guid Id { get; set; } + + /// Free-text description of what this order is for. + public string Description { get; set; } = string.Empty; + + /// Order total in cents (non-negative by convention); the DTO and storage row carry the same plain int. + public int TotalInCents { get; set; } + + /// The owning client; wired by navigation fix-up when the order is added to . + public Client? Client { get; set; } +} diff --git a/src/Web/ClientDto.cs b/src/Web/ClientDto.cs new file mode 100644 index 0000000..a6de508 --- /dev/null +++ b/src/Web/ClientDto.cs @@ -0,0 +1,16 @@ +using Domain; + +namespace Web; + +/// DTO carrying a — with its orders — to the WebApp; record: value equality keeps round-trip comparisons trivial. +public sealed record ClientDto +{ + /// Primary key of the underlying client. + public Guid Id { get; init; } + + /// Display name of the client. + public string Name { get; init; } = string.Empty; + + /// The client's orders, each carrying a back-reference to this DTO; settable so the mappers can fill it in a second pass. + public IReadOnlyList Orders { get; set; } = Array.Empty(); +} diff --git a/src/Web/DtoMappers.cs b/src/Web/DtoMappers.cs new file mode 100644 index 0000000..979a751 --- /dev/null +++ b/src/Web/DtoMappers.cs @@ -0,0 +1,92 @@ +using Domain; + +namespace Web; + +/// Hand-written mappers between domain objects and DTOs; each method maps one direction and never mutates its input. The client/order cycle is resolved with a reference-identity cache. +public static class DtoMappers +{ + /// Map a (with its orders) to a . + public static ClientDto ToClientDto(Client client) + { + ArgumentNullException.ThrowIfNull(client); + return MapClient(client, new Dictionary()); + } + + /// Map an (with its client back-reference) to an . + public static OrderDto ToOrderDto(Order order) + { + ArgumentNullException.ThrowIfNull(order); + return MapOrder(order, new Dictionary()); + } + + /// Map a (with its orders) back to a . + public static Client FromClientDto(ClientDto dto) + { + ArgumentNullException.ThrowIfNull(dto); + return UnmapClient(dto, new Dictionary()); + } + + /// Map an (with its client back-reference) back to an . + public static Order FromOrderDto(OrderDto dto) + { + ArgumentNullException.ThrowIfNull(dto); + return UnmapOrder(dto, new Dictionary()); + } + + private static ClientDto MapClient(Client client, Dictionary seen) + { + if (seen.TryGetValue(client, out var cached)) + return (ClientDto)cached; + + var dto = new ClientDto { Id = client.Id, Name = client.Name }; + seen[client] = dto; // cache first: breaks the cycle + dto.Orders = client.Orders + .Select(o => MapOrder(o, seen)) + .ToList(); // fill second, through the cache + return dto; + } + + private static OrderDto MapOrder(Order order, Dictionary seen) + { + if (seen.TryGetValue(order, out var cached)) + return (OrderDto)cached; + + var dto = new OrderDto + { + Id = order.Id, + Description = order.Description, + TotalInCents = order.TotalInCents, // plain int all the way through + ClientDto = order.Client is null ? null : MapClient(order.Client, seen), + }; + seen[order] = dto; + return dto; + } + + private static Client UnmapClient(ClientDto dto, Dictionary seen) + { + if (seen.TryGetValue(dto, out var cached)) + return (Client)cached; + + var client = new Client { Id = dto.Id, Name = dto.Name }; + seen[dto] = client; // cache first: breaks the cycle + foreach (var orderDto in dto.Orders) + client.Orders.Add(UnmapOrder(orderDto, seen)); // fix-up wires order.Client + return client; + } + + private static Order UnmapOrder(OrderDto dto, Dictionary seen) + { + if (seen.TryGetValue(dto, out var cached)) + return (Order)cached; + + var order = new Order + { + Id = dto.Id, + Description = dto.Description, + TotalInCents = dto.TotalInCents, // plain int all the way through + Client = dto.ClientDto is null ? null : UnmapClient(dto.ClientDto, seen), + }; + seen[dto] = order; + return order; + } +} diff --git a/src/Web/OrderDto.cs b/src/Web/OrderDto.cs new file mode 100644 index 0000000..a830d95 --- /dev/null +++ b/src/Web/OrderDto.cs @@ -0,0 +1,19 @@ +using Domain; + +namespace Web; + +/// DTO carrying an — including its client back-reference — to the WebApp; record: value equality. +public sealed record OrderDto +{ + /// Primary key of the underlying order. + public Guid Id { get; init; } + + /// Free-text description of what this order is for. + public string Description { get; init; } = string.Empty; + + /// Order total in cents of the underlying order. + public int TotalInCents { get; init; } + + /// Back-reference to the owning client, or null for a bare order. + public ClientDto? ClientDto { get; init; } +} diff --git a/src/Web/Web.csproj b/src/Web/Web.csproj new file mode 100644 index 0000000..ea1d7cc --- /dev/null +++ b/src/Web/Web.csproj @@ -0,0 +1,16 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/src/Web/WebApp.cs b/src/Web/WebApp.cs new file mode 100644 index 0000000..85e509b --- /dev/null +++ b/src/Web/WebApp.cs @@ -0,0 +1,16 @@ +namespace Web; + +/// The consuming application: it sees only DTOs, never domain or persistence types. +public class WebApp +{ + /// Render a client together with all of its orders, consuming DTOs only. + public string ShowClient(ClientDto client) + { + ArgumentNullException.ThrowIfNull(client); + + var lines = new List { $"{client.Name} [{client.Id}]" }; + foreach (var order in client.Orders) + lines.Add($" - order [{order.Id}]: {order.Description}"); + return string.Join(Environment.NewLine, lines); + } +} diff --git a/tests/Db.Tests/AfterPersistenceTests.cs b/tests/Db.Tests/AfterPersistenceTests.cs new file mode 100644 index 0000000..8463c82 --- /dev/null +++ b/tests/Db.Tests/AfterPersistenceTests.cs @@ -0,0 +1,186 @@ +using Domain; +using static TestData.ClientBuilder; +using static TestData.OrderBuilder; + +namespace Db.Tests; + +/// The fake stores data-only records, never domain objects; Save maps state into rows (insert or refresh), the graph flattened to ids. +public class AfterPersistenceTests +{ + [Fact] + public void Save_maps_domain_state_into_data_records() + { + var db = new DbContext(); + var order = ValidOrder().WithDescription("one").WithTotal(150_00).Build(); + var client = ValidClient().WithName("Acme").WithOrder(order).Build(); + + db.Save(client); + db.Save(order); + + var clientRow = Assert.Single(db.Clients); + Assert.Equal(client.Id, clientRow.Id); + Assert.Equal("Acme", clientRow.Name); + + var orderRow = Assert.Single(db.Orders); + Assert.Equal(order.Id, orderRow.Id); + Assert.Equal("one", orderRow.Description); + Assert.Equal(150_00, orderRow.TotalInCents); + Assert.Equal(client.Id, orderRow.ClientId); // the graph is flattened to a key + } + + [Fact] + public void An_order_without_client_stores_an_empty_client_id() + { + var db = new DbContext(); + var order = ValidOrder().WithDescription("orphan").Build(); + + db.Save(order); + + Assert.Equal(Guid.Empty, Assert.Single(db.Orders).ClientId); + } + + [Fact] + public void Saving_again_refreshes_the_row_instead_of_duplicating_it() + { + var db = new DbContext(); + var order = ValidOrder().WithDescription("one").WithTotal(150_00).Build(); + var client = ValidClient().WithName("Acme").WithOrder(order).Build(); + db.Save(client); + db.Save(order); + + order.Description = "one (edited)"; + order.TotalInCents = 99_00; + client.Name = "Acme GmbH"; + db.Save(order); + db.Save(client); + + // One row per saved object, refreshed with the *current* state: + var orderRow = Assert.Single(db.Orders); + Assert.Equal(order.Id, orderRow.Id); + Assert.Equal("one (edited)", orderRow.Description); + Assert.Equal(99_00, orderRow.TotalInCents); + Assert.Equal(client.Id, orderRow.ClientId); + + var clientRow = Assert.Single(db.Clients); + Assert.Equal("Acme GmbH", clientRow.Name); + } + + [Fact] + public void Rows_are_reachable_through_Set_and_Find() + { + var db = new DbContext(); + var order = ValidOrder().WithDescription("one").WithTotal(150_00).Build(); + var client = ValidClient().WithName("Acme").WithOrder(order).Build(); + db.Save(client); + db.Save(order); + + Assert.Single(db.Set()); + Assert.Single(db.Set()); + + var found = db.Find(row => row.Description == "one"); + Assert.Equal(order.Id, found!.Id); + Assert.Null(db.Find(row => row.Description == "no such order")); + } + + // Read-back: LoadOrder / LoadClient reassemble domain objects from the stored rows. + + [Fact] + public void Loading_an_order_restores_its_state_and_its_client() + { + var db = new DbContext(); + var order = ValidOrder().WithDescription("one").WithTotal(150_00).Build(); + var client = ValidClient().WithName("Acme").WithOrder(order).Build(); + db.Save(client); + db.Save(order); + + var loaded = db.LoadOrder(order.Id); + + Assert.NotNull(loaded); + Assert.Equal(order.Id, loaded!.Id); + Assert.Equal("one", loaded.Description); + Assert.Equal(150_00, loaded.TotalInCents); + + // The graph is reassembled: the loaded order knows its client again, + // and the client's collection holds the loaded order. + Assert.NotNull(loaded.Client); + Assert.Equal("Acme", loaded.Client!.Name); + Assert.Same(loaded, Assert.Single(loaded.Client.Orders)); + } + + [Fact] + public void Loading_a_client_restores_it_with_all_of_its_orders() + { + var db = new DbContext(); + var first = ValidOrder().WithDescription("one").WithTotal(150_00).Build(); + var second = ValidOrder().WithDescription("two").WithTotal(250_00).Build(); + var client = ValidClient().WithName("Acme").WithOrder(first).WithOrder(second).Build(); + db.Save(client); + db.Save(first); + db.Save(second); + + var loaded = db.LoadClient(client.Id); + + Assert.NotNull(loaded); + Assert.Equal("Acme", loaded!.Name); + Assert.Equal(2, loaded.Orders.Count); + Assert.All(loaded.Orders, o => Assert.Same(loaded, o.Client)); + } + + [Fact] + public void Loading_an_unknown_id_returns_null() + { + var db = new DbContext(); + + Assert.Null(db.LoadOrder(Guid.NewGuid())); + Assert.Null(db.LoadClient(Guid.NewGuid())); + } + + // Tracking: explicit Save, distinct ids; a saved object keeps no reference to the context. + [Fact] + public void DbContext_saves_plain_domain_objects_and_tracks_them() + { + var db = new DbContext(); + var order = ValidOrder().WithDescription("one").Build(); + var client = ValidClient().WithName("Acme").WithOrder(order).Build(); + + Assert.Equal(Guid.Empty, client.Id); + db.Save(client); + db.Save(order); + + Assert.NotEqual(Guid.Empty, client.Id); + Assert.NotEqual(Guid.Empty, order.Id); + Assert.NotEqual(client.Id, order.Id); + Assert.True(db.IsTracked(client)); + Assert.True(db.IsTracked(order)); + Assert.Equal(2, db.Tracked.Count); + } + + [Fact] + public void Saving_twice_does_not_reassign_the_id() + { + var db = new DbContext(); + var client = ValidClient().WithName("Acme").Build(); + db.Save(client); + var id = client.Id; + + db.Save(client); + + Assert.Equal(id, client.Id); + Assert.Single(db.Tracked); + } + + [Fact] + public void A_saved_domain_object_keeps_no_reference_to_the_context() + { + var db = new DbContext(); + var client = ValidClient().WithName("Acme").Build(); + db.Save(client); + + // Saved (the id was assigned by the context) ... + Assert.NotEqual(Guid.Empty, client.Id); + Assert.True(db.IsTracked(client)); + + // No way back to the context through the domain object (see DomainStaysPlainTests): the only "receipt" is the plain Id value. + Assert.Equal(db.Tracked.Single(t => t.Id == client.Id).Id, client.Id); + } +} diff --git a/tests/Db.Tests/Db.Tests.csproj b/tests/Db.Tests/Db.Tests.csproj new file mode 100644 index 0000000..0641dca --- /dev/null +++ b/tests/Db.Tests/Db.Tests.csproj @@ -0,0 +1,29 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Db.Tests/DomainStaysPlainTests.cs b/tests/Db.Tests/DomainStaysPlainTests.cs new file mode 100644 index 0000000..0a75a21 --- /dev/null +++ b/tests/Db.Tests/DomainStaysPlainTests.cs @@ -0,0 +1,30 @@ +using Domain; +using System.Reflection; + +namespace Db.Tests; + +/// The domain stayed clean: Client/Order do NOT derive from and expose no -typed member; asserted from the persistence side, where both are visible. +public class DomainStaysPlainTests +{ + private const BindingFlags AllMembers = + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; + + // The leak is gone: no DbBase in the base chain, no DbContext-typed member (reflection). + [Theory] + [InlineData(typeof(Client))] + [InlineData(typeof(Order))] + public void Domain_objects_are_plain__no_DbBase_no_DbContext_member(Type type) + { + // Not DbBase, and not a derived type either (walk the base chain): + for (var t = type; t is not null && t != typeof(object); t = t.BaseType) + Assert.NotSame(typeof(DbBase), t); + + // No DbContext-typed member, public or private: + foreach (var p in type.GetProperties(AllMembers)) + Assert.True(p.PropertyType != typeof(DbContext), + $"{type.Name}.{p.Name} leaks a DbContext-typed property"); + foreach (var f in type.GetFields(AllMembers)) + Assert.True(f.FieldType != typeof(DbContext), + $"{type.Name}.{f.Name} leaks a DbContext-typed field"); + } +} diff --git a/tests/Domain.Tests/Domain.Tests.csproj b/tests/Domain.Tests/Domain.Tests.csproj new file mode 100644 index 0000000..2c4d822 --- /dev/null +++ b/tests/Domain.Tests/Domain.Tests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + diff --git a/tests/TestData/ClientBuilder.cs b/tests/TestData/ClientBuilder.cs new file mode 100644 index 0000000..5e35459 --- /dev/null +++ b/tests/TestData/ClientBuilder.cs @@ -0,0 +1,36 @@ +using Domain; + +namespace TestData; + +/// Test data builder for a (TestDataBuilder pattern); starts fresh — exactly what new Client() gives. +public sealed class ClientBuilder +{ + private readonly Client _client = new(); + + /// A valid, fresh client — the neutral starting point for tests. + public static ClientBuilder ValidClient() => new(); + + public ClientBuilder WithId(Guid id) + { + _client.Id = id; + return this; + } + + public ClientBuilder WithName(string name) + { + _client.Name = name; + return this; + } + + /// Adds an order to the client (navigation fix-up wires the back-reference). + public ClientBuilder WithOrder(Order order) + { + _client.Orders.Add(order); + return this; + } + + /// Builds the given builder and adds its order — chaining sugar for .WithOrder(builder.Build()). + public ClientBuilder WithOrder(OrderBuilder order) => WithOrder(order.Build()); + + public Client Build() => _client; +} diff --git a/tests/TestData/OrderBuilder.cs b/tests/TestData/OrderBuilder.cs new file mode 100644 index 0000000..ce1f9d1 --- /dev/null +++ b/tests/TestData/OrderBuilder.cs @@ -0,0 +1,33 @@ +using Domain; + +namespace TestData; + +/// Test data builder for an (TestDataBuilder pattern); the only place tests touch an order's construction, so tests survive domain evolution. +public sealed class OrderBuilder +{ + private readonly Order _order = new(); + + /// A valid, fresh order — the neutral starting point for tests. + public static OrderBuilder ValidOrder() => new(); + + public OrderBuilder WithId(Guid id) + { + _order.Id = id; + return this; + } + + public OrderBuilder WithDescription(string description) + { + _order.Description = description; + return this; + } + + /// Sets the total in cents (150_00 = €150.00). + public OrderBuilder WithTotal(int totalInCents) + { + _order.TotalInCents = totalInCents; + return this; + } + + public Order Build() => _order; +} diff --git a/tests/TestData/TestData.csproj b/tests/TestData/TestData.csproj new file mode 100644 index 0000000..ca36ee8 --- /dev/null +++ b/tests/TestData/TestData.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + diff --git a/tests/Web.Tests/DtoMapperTests.cs b/tests/Web.Tests/DtoMapperTests.cs new file mode 100644 index 0000000..e602946 --- /dev/null +++ b/tests/Web.Tests/DtoMapperTests.cs @@ -0,0 +1,163 @@ +using Domain; +using static TestData.ClientBuilder; +using static TestData.OrderBuilder; + +namespace Web.Tests; + +/// The hand-written map both ways, never mutate their input, and round-trip losslessly; domain objects come from the test-data builders, DTO initializers stay literal. +public class DtoMapperTests +{ + // (1) Mappers map all scalar fields, in both directions. + [Fact] + public void ToClientDto_maps_all_scalar_fields() + { + var id = Guid.NewGuid(); + var client = ValidClient().WithId(id).WithName("Acme").Build(); + + var dto = DtoMappers.ToClientDto(client); + + Assert.Equal(id, dto.Id); + Assert.Equal("Acme", dto.Name); + } + + [Fact] + public void ToOrderDto_maps_all_scalar_fields() + { + var id = Guid.NewGuid(); + var order = ValidOrder().WithId(id).WithDescription("one").WithTotal(150_00).Build(); + + var dto = DtoMappers.ToOrderDto(order); + + Assert.Equal(id, dto.Id); + Assert.Equal("one", dto.Description); + Assert.Equal(150_00, dto.TotalInCents); + } + + [Fact] + public void FromClientDto_maps_all_scalar_fields() + { + var id = Guid.NewGuid(); + var dto = new ClientDto { Id = id, Name = "Acme" }; + + var client = DtoMappers.FromClientDto(dto); + + Assert.Equal(id, client.Id); + Assert.Equal("Acme", client.Name); + } + + [Fact] + public void FromOrderDto_maps_all_scalar_fields() + { + var id = Guid.NewGuid(); + var dto = new OrderDto { Id = id, Description = "one", TotalInCents = 150_00 }; + + var order = DtoMappers.FromOrderDto(dto); + + Assert.Equal(id, order.Id); + Assert.Equal("one", order.Description); + Assert.Equal(150_00, order.TotalInCents); + } + + // (2) Mappers map the relations, and the back-references stay consistent. + [Fact] + public void ToClientDto_maps_orders_and_keeps_the_back_reference_consistent() + { + var order = ValidOrder().WithDescription("one").Build(); + var client = ValidClient().WithName("Acme").WithOrder(order).Build(); + + var dto = DtoMappers.ToClientDto(client); + var orderDto = Assert.Single(dto.Orders); + + // The order is mapped with the client's data: + Assert.Equal(order.Id, orderDto.Id); + Assert.Equal(order.Description, orderDto.Description); + + // The back-reference cycle is resolved to the SAME dto reference: + Assert.Same(dto, orderDto.ClientDto); + } + + [Fact] + public void FromClientDto_wires_the_order_client_back_reference() + { + var orderDto = new OrderDto { Description = "one" }; + var dto = new ClientDto { Name = "Acme", Orders = new[] { orderDto } }; + + var client = DtoMappers.FromClientDto(dto); + var order = Assert.Single(client.Orders); + + Assert.Equal(orderDto.Description, order.Description); + // Navigation fix-up: the reconstructed order points at the client: + Assert.Same(client, order.Client); + } + + // (3) Round-trips preserve all state (compared by values, not reference). + [Fact] + public void Domain_round_trip_preserves_all_state() + { + var order = ValidOrder().WithId(Guid.NewGuid()).WithDescription("one").WithTotal(150_00).Build(); + var client = ValidClient().WithId(Guid.NewGuid()).WithName("Acme").WithOrder(order).Build(); + + var roundTrip = DtoMappers.FromClientDto(DtoMappers.ToClientDto(client)); + + Assert.Equal(client.Id, roundTrip.Id); + Assert.Equal(client.Name, roundTrip.Name); + var roundTripOrder = Assert.Single(roundTrip.Orders); + Assert.Equal(order.Id, roundTripOrder.Id); + Assert.Equal(order.Description, roundTripOrder.Description); + Assert.Equal(order.TotalInCents, roundTripOrder.TotalInCents); + // The back-reference is still consistent on the reconstructed graph: + Assert.Same(roundTrip, roundTripOrder.Client); + } + + [Fact] + public void Dto_round_trip_preserves_all_state() + { + var order = ValidOrder().WithId(Guid.NewGuid()).WithDescription("one").WithTotal(150_00).Build(); + var client = ValidClient().WithId(Guid.NewGuid()).WithName("Acme").WithOrder(order).Build(); + var dto = DtoMappers.ToClientDto(client); + var dtoOrder = dto.Orders[0]; + + var roundTrip = DtoMappers.ToClientDto(DtoMappers.FromClientDto(dto)); + + Assert.Equal(dto.Id, roundTrip.Id); + Assert.Equal(dto.Name, roundTrip.Name); + var roundTripOrder = Assert.Single(roundTrip.Orders); + Assert.Equal(dtoOrder.Id, roundTripOrder.Id); + Assert.Equal(dtoOrder.Description, roundTripOrder.Description); + Assert.Equal(dtoOrder.TotalInCents, roundTripOrder.TotalInCents); + Assert.Same(roundTrip, roundTripOrder.ClientDto); + } + + // (4) Mappers do not mutate their inputs (snapshot fields, map, compare). + [Fact] + public void ToClientDto_does_not_mutate_the_domain() + { + var order = ValidOrder().WithId(Guid.NewGuid()).WithDescription("one").Build(); + var client = ValidClient().WithId(Guid.NewGuid()).WithName("Acme").WithOrder(order).Build(); + var ordersCount = client.Orders.Count; + + DtoMappers.ToClientDto(client); + DtoMappers.ToOrderDto(order); + + Assert.Equal("Acme", client.Name); + Assert.Equal(ordersCount, client.Orders.Count); + Assert.Same(client, order.Client); // back-reference untouched + Assert.Equal("one", order.Description); + } + + [Fact] + public void FromClientDto_does_not_mutate_the_dto() + { + var orderDto = new OrderDto { Description = "one" }; + var dto = new ClientDto { Id = Guid.NewGuid(), Name = "Acme", Orders = new[] { orderDto } }; + + DtoMappers.FromClientDto(dto); + DtoMappers.FromOrderDto(orderDto); + + Assert.Equal("Acme", dto.Name); + Assert.Same(orderDto, Assert.Single(dto.Orders)); // collection untouched + Assert.Null(orderDto.ClientDto); // back-reference untouched + Assert.Equal("one", orderDto.Description); + } + +} diff --git a/tests/Web.Tests/Web.Tests.csproj b/tests/Web.Tests/Web.Tests.csproj new file mode 100644 index 0000000..02ec726 --- /dev/null +++ b/tests/Web.Tests/Web.Tests.csproj @@ -0,0 +1,30 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Web.Tests/WebAppTests.cs b/tests/Web.Tests/WebAppTests.cs new file mode 100644 index 0000000..8382cfe --- /dev/null +++ b/tests/Web.Tests/WebAppTests.cs @@ -0,0 +1,61 @@ +using Domain; +using System.Reflection; +using static TestData.ClientBuilder; +using static TestData.OrderBuilder; + +namespace Web.Tests; + +/// The WebApp consumes only DTOs; persistence types are excluded at compile time (no Web → Db project reference). +public class WebAppTests +{ + // The WebApp's public API references no domain type (reflection). + private static readonly Type[] BannedTypes = [typeof(Client), typeof(Order)]; + + private static IEnumerable TypeAndArguments(Type t) + { + yield return t; + if (t.IsGenericType) + foreach (var arg in t.GetGenericArguments()) + foreach (var nested in TypeAndArguments(arg)) + yield return nested; + if (t.IsArray && t.GetElementType() is { } elem) + foreach (var nested in TypeAndArguments(elem)) + yield return nested; + } + + [Fact] + public void WebApp_public_api_references_no_domain_types() + { + var app = typeof(WebApp); + var banned = BannedTypes.ToList(); + + foreach (var m in app.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)) + { + var signature = m.GetParameters() + .Select(p => p.ParameterType) + .Append(m.ReturnType) + .ToList(); + foreach (var t in signature.SelectMany(TypeAndArguments)) + Assert.False(banned.Contains(t), + $"WebApp.{m.Name}({string.Join(", ", signature)}) references banned type {t}"); + } + + foreach (var p in app.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)) + foreach (var t in TypeAndArguments(p.PropertyType)) + Assert.False(banned.Contains(t), + $"WebApp.{p.Name} references banned type {t}"); + } + + [Fact] + public void WebApp_consumes_dtos() + { + var app = new WebApp(); + var order = ValidOrder().WithId(Guid.NewGuid()).WithDescription("order one").WithTotal(150_00).Build(); + var client = ValidClient().WithId(Guid.NewGuid()).WithName("Acme").WithOrder(order).Build(); + + var view = app.ShowClient(DtoMappers.ToClientDto(client)); + + Assert.Contains("Acme", view); + Assert.Contains("order one", view); + } +}