aw-spec-cli 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/.agents/agent.json +1 -1
- package/assets/.agents/skills/{aw-spec-orch → appweaver}/SKILL.md +10 -10
- package/assets/.agents/skills/{aw-spec-orch-demo → appweaver-demo}/SKILL.md +9 -9
- package/assets/.claude/agent.json +1 -1
- package/assets/.claude/skills/{aw-spec-orch → appweaver}/SKILL.md +10 -10
- package/assets/.claude/skills/{aw-spec-orch-demo → appweaver-demo}/SKILL.md +9 -9
- package/assets/.kilocode/agent.json +1 -1
- package/assets/.kilocode/workflows/{aw-spec-orch-demo.md → appweaver-demo.md} +9 -9
- package/assets/.kilocode/workflows/{aw-spec-orch.md → appweaver.md} +10 -10
- package/assets/docs/pharma-constitution.my-ref.md +86 -0
- package/dist/utils/paths.js +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/package.json +1 -1
- package/assets/.aw-spec/DESIGN-FORMAT.md +0 -151
- package/assets/.aw-spec/ORCH-DIALECT.md +0 -156
- package/assets/.aw-spec/RULE-PARTITION.md +0 -126
- package/assets/.aw-spec/domain.json +0 -3
- package/assets/.aw-spec/extensions/.registry +0 -19
- package/assets/.aw-spec/extensions/agent-context/README.md +0 -57
- package/assets/.aw-spec/extensions/agent-context/agent-context-config.yml +0 -4
- package/assets/.aw-spec/extensions/agent-context/commands/aw-spec.agent-context.update.md +0 -26
- package/assets/.aw-spec/extensions/agent-context/extension.yml +0 -37
- package/assets/.aw-spec/extensions/agent-context/scripts/bash/update-agent-context.sh +0 -201
- package/assets/.aw-spec/extensions/agent-context/scripts/powershell/update-agent-context.ps1 +0 -237
- package/assets/.aw-spec/extensions.yml +0 -16
- package/assets/.aw-spec/feature.json +0 -3
- package/assets/.aw-spec/init-options.json +0 -9
- package/assets/.aw-spec/integration.json +0 -15
- package/assets/.aw-spec/integrations/aw-spec.manifest.json +0 -9
- package/assets/.aw-spec/interpreter/run-skill.py +0 -1158
- package/assets/.aw-spec/memory/constitution.md +0 -50
- package/assets/.aw-spec/orchestrator/AGENT-PROFILE.md +0 -94
- package/assets/.aw-spec/orchestrator/README.md +0 -300
- package/assets/.aw-spec/orchestrator/adapters/claude_agent_sdk.py +0 -74
- package/assets/.aw-spec/orchestrator/adapters/google_antigravity.py +0 -74
- package/assets/.aw-spec/orchestrator/modules/delta-design.orch.yaml +0 -105
- package/assets/.aw-spec/orchestrator/modules/delta-spec.orch.yaml +0 -79
- package/assets/.aw-spec/orchestrator/modules/domain-select.orch.yaml +0 -81
- package/assets/.aw-spec/orchestrator/modules/tech-select.orch.yaml +0 -75
- package/assets/.aw-spec/orchestrator/orchestrator.py +0 -1721
- package/assets/.aw-spec/orchestrator/rev-templates/ai-rules.reference.md +0 -40
- package/assets/.aw-spec/orchestrator/rev-templates/comandrulemap.skeleton.md +0 -59
- package/assets/.aw-spec/orchestrator/rev-templates/commandtechrulemap.skeleton.md +0 -97
- package/assets/.aw-spec/orchestrator/rev-templates/domain-constitution.skeleton.md +0 -61
- package/assets/.aw-spec/orchestrator/rev-templates/domain-requirements.skeleton.md +0 -48
- package/assets/.aw-spec/orchestrator/rev-templates/domain-rule-file.skeleton.md +0 -39
- package/assets/.aw-spec/orchestrator/rev-templates/technical-constitution.skeleton.md +0 -106
- package/assets/.aw-spec/orchestrator/rev-templates/technical-rule-file.skeleton.md +0 -54
- package/assets/.aw-spec/orchestrator/workflows/_smoke.orch.yaml +0 -160
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-add-component.orch.yaml +0 -244
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-adopt.orch.yaml +0 -775
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-analyze-delta-design.orch.yaml +0 -248
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-analyze-design.orch.yaml +0 -261
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +0 -425
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-build.orch.yaml +0 -188
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-characterize.orch.yaml +0 -474
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-close-delta.orch.yaml +0 -181
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-constitution.orch.yaml +0 -284
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-demo.orch.yaml +0 -103
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-dependency-tree.orch.yaml +0 -69
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-deploy.orch.yaml +0 -85
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-design.orch.yaml +0 -382
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +0 -382
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-foundation.orch.yaml +0 -246
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-implement-delta.orch.yaml +0 -231
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-implement.orch.yaml +0 -258
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-inventory.orch.yaml +0 -236
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-onboard.orch.yaml +0 -949
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-parity.orch.yaml +0 -293
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-run.orch.yaml +0 -177
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-select-domain.orch.yaml +0 -79
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-select-feature.orch.yaml +0 -128
- package/assets/.aw-spec/orchestrator/workflows/aw-spec-specify.orch.yaml +0 -412
- package/assets/.aw-spec/scripts/bash/aw-inventory.sh +0 -14
- package/assets/.aw-spec/scripts/bash/aw-parity.sh +0 -13
- package/assets/.aw-spec/scripts/bash/aw-rev-constitution.sh +0 -13
- package/assets/.aw-spec/scripts/bash/aw-spec-actions.sh +0 -1899
- package/assets/.aw-spec/scripts/bash/aw-xref.sh +0 -15
- package/assets/.aw-spec/scripts/bash/common.sh +0 -533
- package/assets/.aw-spec/scripts/powershell/aw-inventory.ps1 +0 -14
- package/assets/.aw-spec/scripts/powershell/aw-parity.ps1 +0 -13
- package/assets/.aw-spec/scripts/powershell/aw-rev-constitution.ps1 +0 -13
- package/assets/.aw-spec/scripts/powershell/aw-spec-actions.ps1 +0 -1563
- package/assets/.aw-spec/scripts/powershell/aw-xref.ps1 +0 -15
- package/assets/.aw-spec/scripts/python/aw-inventory.py +0 -683
- package/assets/.aw-spec/scripts/python/aw-parity.py +0 -380
- package/assets/.aw-spec/scripts/python/aw-rev-constitution.py +0 -312
- package/assets/.aw-spec/scripts/python/aw-xref.py +0 -1330
- package/assets/.aw-spec/scripts/python/validate_okf.py +0 -349
- package/assets/.aw-spec/templates/constitution-domain-template.md +0 -75
- package/assets/.aw-spec/templates/constitution-technical-template.md +0 -70
- package/assets/.aw-spec/templates/delta-design-template.md +0 -69
- package/assets/.aw-spec/templates/delta-spec-template.md +0 -60
- package/assets/.aw-spec/templates/design-template.md +0 -383
- package/assets/.aw-spec/templates/okf-concept-template.md +0 -103
- package/assets/.aw-spec/templates/parity-manifest-template.json +0 -49
- package/assets/.aw-spec/templates/source-map-template.md +0 -85
- package/assets/.aw-spec/templates/spec-template.md +0 -252
- package/assets/docs/ModularMonolith/ModularMonolith.Reference.pack.txt +0 -25248
- package/assets/docs/ModularMonolith/ModularMonolith.pack.txt +0 -5726
- package/assets/docs/ModularMonolith/pack-foundation.ps1 +0 -145
- package/assets/docs/ModularMonolith/pack-foundation.sh +0 -196
- package/assets/docs/ModularMonolith/pack-reference-foundation.ps1 +0 -157
- package/assets/docs/ModularMonolith/pack-reference-foundation.sh +0 -209
- package/assets/docs/ModularMonolith/unpack-foundation.ps1 +0 -126
- package/assets/docs/ModularMonolith/unpack-foundation.sh +0 -217
- package/assets/docs/ModularMonolith/unpack-reference-foundation.ps1 +0 -129
- package/assets/docs/ModularMonolith/unpack-reference-foundation.sh +0 -218
- package/assets/docs/README.md +0 -106
- package/assets/docs/VerticalSlice/PACKAGING.md +0 -185
- package/assets/docs/VerticalSlice/VerticalSlice.Reference.pack.txt +0 -19318
- package/assets/docs/VerticalSlice/VerticalSlice.pack.txt +0 -2833
- package/assets/docs/VerticalSlice/pack-foundation.ps1 +0 -145
- package/assets/docs/VerticalSlice/pack-foundation.sh +0 -196
- package/assets/docs/VerticalSlice/pack-reference-foundation.ps1 +0 -157
- package/assets/docs/VerticalSlice/pack-reference-foundation.sh +0 -209
- package/assets/docs/VerticalSlice/unpack-foundation.ps1 +0 -126
- package/assets/docs/VerticalSlice/unpack-foundation.sh +0 -217
- package/assets/docs/VerticalSlice/unpack-reference-foundation.ps1 +0 -129
- package/assets/docs/VerticalSlice/unpack-reference-foundation.sh +0 -218
- package/assets/docs/architecture/clean-architecture.md +0 -71
- package/assets/docs/architecture/cqrs.md +0 -141
- package/assets/docs/architecture/eventing.md +0 -83
- package/assets/docs/architecture/folder-structure.md +0 -61
- package/assets/docs/architecture/modular-monolith.md +0 -47
- package/assets/docs/architecture/persistence.md +0 -269
- package/assets/docs/architecture/testing.md +0 -55
- package/assets/docs/architecture/vertical-slices.md +0 -201
- package/assets/docs/components/Email/README.md +0 -119
- package/assets/docs/components/Email/ReferenceEndpoints/SendEmailEndpoint.cs +0 -84
- package/assets/docs/components/Email/integration.md +0 -300
- package/assets/docs/components/Jobs/README.md +0 -113
- package/assets/docs/components/Jobs/integration.md +0 -259
- package/assets/docs/components/QrGenerator/README.md +0 -85
- package/assets/docs/components/QrGenerator/ReferenceEndpoints/GenerateQrEndpoint.cs +0 -57
- package/assets/docs/components/QrGenerator/integration.md +0 -230
- package/assets/docs/components/README.md +0 -74
- package/assets/docs/domain/domain-driven-design.md +0 -212
- package/assets/docs/domainspecs/healthcare/ai-rules.domain.md +0 -34
- package/assets/docs/domainspecs/healthcare/api-versioning.domain.md +0 -17
- package/assets/docs/domainspecs/healthcare/auditing.domain.md +0 -97
- package/assets/docs/domainspecs/healthcare/authorization.domain.md +0 -26
- package/assets/docs/domainspecs/healthcare/clinical-terms.md +0 -20
- package/assets/docs/domainspecs/healthcare/coding-standards.domain.md +0 -26
- package/assets/docs/domainspecs/healthcare/comandrulemap.md +0 -100
- package/assets/docs/domainspecs/healthcare/commandtechrulemap.md +0 -119
- package/assets/docs/domainspecs/healthcare/consent.md +0 -19
- package/assets/docs/domainspecs/healthcare/cqrs.domain.md +0 -12
- package/assets/docs/domainspecs/healthcare/data-classification.domain.md +0 -28
- package/assets/docs/domainspecs/healthcare/dependency-policy.domain.md +0 -11
- package/assets/docs/domainspecs/healthcare/dicom.md +0 -15
- package/assets/docs/domainspecs/healthcare/domain-driven-design.domain.md +0 -28
- package/assets/docs/domainspecs/healthcare/domain-requirements.md +0 -55
- package/assets/docs/domainspecs/healthcare/eventing.domain.md +0 -18
- package/assets/docs/domainspecs/healthcare/fhir.md +0 -36
- package/assets/docs/domainspecs/healthcare/folder-structure.domain.md +0 -23
- package/assets/docs/domainspecs/healthcare/hipaa.md +0 -34
- package/assets/docs/domainspecs/healthcare/hl7.md +0 -15
- package/assets/docs/domainspecs/healthcare/integration-terms.md +0 -21
- package/assets/docs/domainspecs/healthcare/persistence.domain.md +0 -20
- package/assets/docs/domainspecs/healthcare/terminology.md +0 -30
- package/assets/docs/domainspecs/healthcare/testing.domain.md +0 -15
- package/assets/docs/domainspecs/healthcare/vertical-slices.domain.md +0 -18
- package/assets/docs/domainspecs/healthcare/zero-trust.domain.md +0 -12
- package/assets/docs/domainspecs/pharma/ai-rules.domain.md +0 -32
- package/assets/docs/domainspecs/pharma/api-versioning.domain.md +0 -20
- package/assets/docs/domainspecs/pharma/atc.md +0 -22
- package/assets/docs/domainspecs/pharma/auditing.domain.md +0 -90
- package/assets/docs/domainspecs/pharma/authorization.domain.md +0 -25
- package/assets/docs/domainspecs/pharma/coding-standards.domain.md +0 -28
- package/assets/docs/domainspecs/pharma/comandrulemap.md +0 -108
- package/assets/docs/domainspecs/pharma/commandtechrulemap.md +0 -119
- package/assets/docs/domainspecs/pharma/cqrs.domain.md +0 -16
- package/assets/docs/domainspecs/pharma/data-classification.domain.md +0 -24
- package/assets/docs/domainspecs/pharma/data-provenance.md +0 -21
- package/assets/docs/domainspecs/pharma/dependency-policy.domain.md +0 -11
- package/assets/docs/domainspecs/pharma/domain-driven-design.domain.md +0 -36
- package/assets/docs/domainspecs/pharma/domain-requirements.md +0 -99
- package/assets/docs/domainspecs/pharma/eventing.domain.md +0 -18
- package/assets/docs/domainspecs/pharma/fhir.md +0 -31
- package/assets/docs/domainspecs/pharma/folder-structure.domain.md +0 -24
- package/assets/docs/domainspecs/pharma/hl7.md +0 -17
- package/assets/docs/domainspecs/pharma/idmp.md +0 -26
- package/assets/docs/domainspecs/pharma/integration-terms.md +0 -21
- package/assets/docs/domainspecs/pharma/ndc.md +0 -28
- package/assets/docs/domainspecs/pharma/persistence.domain.md +0 -23
- package/assets/docs/domainspecs/pharma/pharma-terms.md +0 -27
- package/assets/docs/domainspecs/pharma/product-lifecycle.domain.md +0 -30
- package/assets/docs/domainspecs/pharma/regulatory-compliance.md +0 -19
- package/assets/docs/domainspecs/pharma/terminology.md +0 -32
- package/assets/docs/domainspecs/pharma/testing.domain.md +0 -19
- package/assets/docs/domainspecs/pharma/vertical-slices.domain.md +0 -17
- package/assets/docs/domainspecs/pharma/zero-trust.domain.md +0 -12
- package/assets/docs/glossary/ai-rules.md +0 -59
- package/assets/docs/governance/api-versioning.md +0 -99
- package/assets/docs/governance/coding-standards.md +0 -87
- package/assets/docs/governance/dependency-policy.md +0 -40
- package/assets/docs/interop/ihe-conformance.md +0 -31
- package/assets/docs/requirement-spec.txt +0 -27
- package/assets/docs/security/auditing.md +0 -160
- package/assets/docs/security/authorization-matrix.md +0 -14
- package/assets/docs/security/authorization.md +0 -87
- package/assets/docs/security/data-classification.md +0 -38
- package/assets/docs/security/zero-trust.md +0 -76
- package/assets/docs/technology-constitution.md +0 -172
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
# Jobs Component — Integration Workflow (agent playbook)
|
|
2
|
-
|
|
3
|
-
> Pure technology (domain-agnostic). This file is an **agent runbook**: read it top-to-bottom and
|
|
4
|
-
> execute the workflow in order. It drives an interactive integration of `IJobScheduler` /
|
|
5
|
-
> `IJobProgress` into a project, asking the user which engine + persistence implementation(s) to
|
|
6
|
-
> wire. Read [README.md](./README.md) first for the contract. Replace `<SampleProjectName>` with
|
|
7
|
-
> the project name. Commands: `/aw-spec-implement`.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Agent workflow — execute in order
|
|
12
|
-
|
|
13
|
-
1. **Load context.** Read [README.md](./README.md) and this file. The functional engine is
|
|
14
|
-
**Quartz** — `AppWeaver.Jobs` + `AppWeaver.Jobs.Quartz` are always installed.
|
|
15
|
-
2. **Resolve the host project** `<HostProject>` (see [[folder-structure]], [[components-readme]] R0.2):
|
|
16
|
-
the single startup project that owns **`Program.cs`** + the ASP.NET Core web SDK —
|
|
17
|
-
`<SampleProjectName>.Service` (Vertical Slice foundation), `<SampleProjectName>.API` (Modular
|
|
18
|
-
Monolith foundation), or another name in a future template. **Find it by locating `Program.cs`, not
|
|
19
|
-
by assuming the name.** In these foundations job classes, handlers, registration, `DbContext`, and
|
|
20
|
-
`appsettings.json` all live in this one project (modules are folders) — there is **no** separate
|
|
21
|
-
`…Application` / `…Infrastructure` project. `<HostProject>` is the target for the steps below.
|
|
22
|
-
3. **Present the implementation catalog (below) as a selection list and ASK the user which
|
|
23
|
-
implementation(s) to integrate.** Use a multi-select prompt (`AskUserQuestion`,
|
|
24
|
-
`multiSelect: true`). Enforce the grouping rules when reading the answer:
|
|
25
|
-
- **Group A — Scheduler store (pick exactly one; default `Quartz.InMemoryStore`).**
|
|
26
|
-
- **Group B — Status persistence (pick zero or one; recommended `EfCore.JobDatabase`).**
|
|
27
|
-
- A valid selection is one Group A item, optionally plus one Group B item
|
|
28
|
-
(e.g. `Quartz.PersistentStore` + `EfCore.DataStore`).
|
|
29
|
-
4. **Apply “Common steps” once** (§Common steps) — abstraction + Quartz + base registration.
|
|
30
|
-
5. **For each selected implementation, apply its block** (§Implementation blocks) — Group A tunes
|
|
31
|
-
`UseQuartzJobScheduler`; Group B chains `.UseEfCoreJobs().UseXxx(...)`.
|
|
32
|
-
6. **Create the DB schema** if a Group B (or persistent Group A) implementation was selected
|
|
33
|
-
(§Schema).
|
|
34
|
-
7. **Author job classes + dispatch/poll** in the feature slice (§Usage); register each job `Scoped`.
|
|
35
|
-
8. **Verify** (§Verification) and report what was added.
|
|
36
|
-
|
|
37
|
-
> **Guardrails.** Do **not** use `AppWeaver.Jobs.InMemory` (work-in-progress, not functional). Always
|
|
38
|
-
> set `options.Provider = "Quartz"`. Long-running jobs must check `IJobProgress.CancellationToken`
|
|
39
|
-
> (cancellation is cooperative). In a Quartz **persistent** store, do not rename a job class/method
|
|
40
|
-
> after jobs are scheduled — names are serialized as strings; version instead (`…JobV2`).
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Implementation catalog (selection list)
|
|
45
|
-
|
|
46
|
-
| Key | Group | Implementation | Best for | Extra package (v) |
|
|
47
|
-
|---|---|---|---|---|
|
|
48
|
-
| `Quartz.InMemoryStore` | A · Scheduler store | Quartz with in-memory store | Single instance; schedule may be lost on restart | — (Quartz only) |
|
|
49
|
-
| `Quartz.PersistentStore` | A · Scheduler store | Quartz with persistent store (Postgres/SqlServer/SQLite) | Durable + multi-instance scheduling | — (Quartz only; needs DB + DDL) |
|
|
50
|
-
| `EfCore.JobDatabase` | B · Status persistence | `JobStatus` in a dedicated EF Core DB | Standalone apps; status survives restart | `AppWeaver.Jobs.Persistence.EfCore` (0.1.0) |
|
|
51
|
-
| `EfCore.DataStore` | B · Status persistence | `JobStatus` in the shared AppWeaver DataStore | Apps already using `AppWeaver.DataStore` | `AppWeaver.Jobs.Persistence.EfCore` (0.1.0) |
|
|
52
|
-
|
|
53
|
-
> Recommended production set: **`Quartz.PersistentStore` + `EfCore.JobDatabase`** (or `EfCore.DataStore`
|
|
54
|
-
> when DataStore is present). Quartz store = durable *schedule*; EF Core store = durable *status*;
|
|
55
|
-
> they are independent concerns.
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
## Common steps (apply once, always)
|
|
60
|
-
|
|
61
|
-
### C1 — Package versions
|
|
62
|
-
|
|
63
|
-
**File:** `Directory.Packages.props`
|
|
64
|
-
|
|
65
|
-
```xml
|
|
66
|
-
<ItemGroup>
|
|
67
|
-
<!-- AppWeaver — Jobs (engine: Quartz) -->
|
|
68
|
-
<PackageVersion Include="AppWeaver.Jobs" Version="0.1.1" />
|
|
69
|
-
<PackageVersion Include="AppWeaver.Jobs.Quartz" Version="0.1.0" />
|
|
70
|
-
<!-- + AppWeaver.Jobs.Persistence.EfCore 0.1.0 if a Group B item is selected -->
|
|
71
|
-
</ItemGroup>
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### C2 — Project references (no `Version=`)
|
|
75
|
-
|
|
76
|
-
**File:** `src/<HostProject>/<HostProject>.csproj` (the host project resolved in step 2 — single
|
|
77
|
-
project, no separate Application/Infrastructure). Add the abstraction, the Quartz engine, and (if a
|
|
78
|
-
Group B item is selected) the EF Core persistence package here:
|
|
79
|
-
```xml
|
|
80
|
-
<ItemGroup>
|
|
81
|
-
<PackageReference Include="AppWeaver.Jobs" /> <!-- abstractions: IJob, IJobScheduler, IJobProgress -->
|
|
82
|
-
<PackageReference Include="AppWeaver.Jobs.Quartz" />
|
|
83
|
-
<!-- + AppWeaver.Jobs.Persistence.EfCore if a Group B item is selected -->
|
|
84
|
-
</ItemGroup>
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### C3 — appsettings
|
|
88
|
-
|
|
89
|
-
**File:** `src/<HostProject>/appsettings.json` (the host project resolved in step 2)
|
|
90
|
-
```jsonc
|
|
91
|
-
{ "AppWeaver": { "Jobs": { "Provider": "Quartz", "Quartz": { "MaxConcurrency": 10 } } } }
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### C4 — Registration scaffold
|
|
95
|
-
|
|
96
|
-
**File:** the host project `<HostProject>` (step 2) — register in its
|
|
97
|
-
`Extensions/InfrastructureServiceRegistration.cs` (`AddInfrastructure`) when that file exists (it does
|
|
98
|
-
in the Vertical Slice / Modular Monolith foundations), otherwise directly in `Program.cs` **before**
|
|
99
|
-
`builder.Build()`. (`configuration` below = the `IConfiguration` in scope: `config` inside
|
|
100
|
-
`AddInfrastructure`, `builder.Configuration` in `Program.cs`.)
|
|
101
|
-
|
|
102
|
-
The selected blocks fill in the `UseQuartzJobScheduler` body (Group A) and the persistence chain
|
|
103
|
-
(Group B).
|
|
104
|
-
|
|
105
|
-
```csharp
|
|
106
|
-
using AppWeaver.Jobs.Extensions;
|
|
107
|
-
using AppWeaver.Jobs.Quartz.Extensions;
|
|
108
|
-
// + using AppWeaver.Jobs.Persistence.EfCore.Extensions; (if Group B selected)
|
|
109
|
-
// + using Microsoft.EntityFrameworkCore; (if Group B selected)
|
|
110
|
-
|
|
111
|
-
services.AddAppWeaverJobs(
|
|
112
|
-
configureBuilder: builder => builder
|
|
113
|
-
.UseQuartzJobScheduler(opts =>
|
|
114
|
-
{
|
|
115
|
-
opts.MaxConcurrency = 10;
|
|
116
|
-
// ⬇ Group A block sets opts.ConfigureQuartz (persistent) or leaves it unset (in-memory)
|
|
117
|
-
})
|
|
118
|
-
// ⬇ Group B block chains .UseEfCoreJobs().UseJobDatabase(...) or .UseAppWeaverDataStore()
|
|
119
|
-
,
|
|
120
|
-
configureOptions: options => options.Provider = "Quartz");
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
> **Dual local/Aspire startup ([[components-readme]] R0.2a).** If the host project also has an
|
|
124
|
-
> `AddAspireInfrastructure` method (`Extensions/AspireInfrastructureExtensions.cs`) selected by
|
|
125
|
-
> `if (isAspire) … else …` in `Program.cs`, add this `AddAppWeaverJobs(...)` registration to **both**
|
|
126
|
-
> `AddInfrastructure` and `AddAspireInfrastructure`, or `IJobScheduler` is unregistered whenever the
|
|
127
|
-
> app runs under Aspire. Keep the structure identical, but make any **provider-specific** call match
|
|
128
|
-
> each method's database: in `AddAspireInfrastructure` use the PostgreSQL forms (`s.UsePostgres(...)`,
|
|
129
|
-
> `opts.UseNpgsql(...)`); in `AddInfrastructure` use the SQLite forms (`s.UseMicrosoftSQLite(...)`,
|
|
130
|
-
> `opts.UseSqlite(...)`). `EfCore.DataStore` (`.UseAppWeaverDataStore()`) is provider-agnostic — it
|
|
131
|
-
> rides whichever DataStore that method already registered.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Implementation blocks
|
|
136
|
-
|
|
137
|
-
### A · `Quartz.InMemoryStore`
|
|
138
|
-
Leave `opts.ConfigureQuartz` **unset** — Quartz uses its in-memory store automatically. No DB, no
|
|
139
|
-
DDL. Schedule and pending jobs are lost on restart; single instance only.
|
|
140
|
-
|
|
141
|
-
### A · `Quartz.PersistentStore`
|
|
142
|
-
Set `ConfigureQuartz` inside `UseQuartzJobScheduler`:
|
|
143
|
-
```csharp
|
|
144
|
-
opts.ConfigureQuartz = q => q.UsePersistentStore(s =>
|
|
145
|
-
{
|
|
146
|
-
s.UsePostgres(configuration.GetConnectionString("Quartz")!); // or UseSqlServer / UseMicrosoftSQLite
|
|
147
|
-
s.UseSystemTextJsonSerializer();
|
|
148
|
-
});
|
|
149
|
-
```
|
|
150
|
-
> Apply the official Quartz DDL to that database **before first run** (Postgres/SQL Server scripts —
|
|
151
|
-
> see framework `Jobs/README.md` → *Database Setup*). Do **not** set `opts.ConnectionString` without
|
|
152
|
-
> `ConfigureQuartz` — that combination throws at startup.
|
|
153
|
-
|
|
154
|
-
### B · `EfCore.JobDatabase` (standalone status DB)
|
|
155
|
-
Add the package (C1/C2) and chain a dedicated `JobStatusDbContext`:
|
|
156
|
-
```csharp
|
|
157
|
-
.UseEfCoreJobs()
|
|
158
|
-
.UseJobDatabase(opts => opts.UseNpgsql(configuration.GetConnectionString("Jobs"))) // or UseSqlite("Data Source=jobs.db")
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### B · `EfCore.DataStore` (shared AppWeaver DataStore)
|
|
162
|
-
Requires `AddDataStore().UseEfCoreDataStore()` to be registered **first**. Chain:
|
|
163
|
-
```csharp
|
|
164
|
-
.UseEfCoreJobs()
|
|
165
|
-
.UseAppWeaverDataStore()
|
|
166
|
-
```
|
|
167
|
-
> `UseEfCoreJobs()` alone does **not** register a data context — it must be followed by exactly one
|
|
168
|
-
> of `UseJobDatabase(...)` or `UseAppWeaverDataStore()`.
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## Schema (only if a Group B / persistent Group A item was selected)
|
|
173
|
-
|
|
174
|
-
- **`EfCore.JobDatabase`** — scaffold + apply a `JobStatusDbContext` migration (or
|
|
175
|
-
`EnsureCreatedAsync()` for dev):
|
|
176
|
-
```bash
|
|
177
|
-
dotnet ef migrations add InitialJobs --context JobStatusDbContext \
|
|
178
|
-
--project src/<SampleProjectName>.Infrastructure --startup-project src/<SampleProjectName>.Service
|
|
179
|
-
dotnet ef database update --context JobStatusDbContext --startup-project src/<SampleProjectName>.Service
|
|
180
|
-
```
|
|
181
|
-
- **`EfCore.DataStore`** — Shared strategy: job tables ride in the `SharedComponentDbContext`
|
|
182
|
-
migration (only `dataStore.MigrateAsync()` at runtime). Isolated strategy: run the
|
|
183
|
-
`JobStatusDbContext` migration too (see [[persistence]]).
|
|
184
|
-
- **`Quartz.PersistentStore`** — apply the Quartz DDL (above).
|
|
185
|
-
|
|
186
|
-
> AI MAY scaffold migrations but MUST NOT run them against shared environments (see [[ai-rules]]).
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
## Usage (job classes + dispatch)
|
|
191
|
-
|
|
192
|
-
### Author a job — implement the `IJob` marker; inject `IJobProgress` for progress/cancel
|
|
193
|
-
**File:** `src/<SampleProjectName>.Application/Jobs/ExportDataJob.cs`
|
|
194
|
-
```csharp
|
|
195
|
-
using AppWeaver.Jobs.Abstractions;
|
|
196
|
-
|
|
197
|
-
public class ExportDataJob(IExportService _exports, IJobProgress _progress) : IJob
|
|
198
|
-
{
|
|
199
|
-
public async Task RunAsync(string table, Guid requestId)
|
|
200
|
-
{
|
|
201
|
-
await _progress.ReportProgressAsync(0, "Starting export…");
|
|
202
|
-
var rows = await _exports.FetchAsync(table);
|
|
203
|
-
for (int i = 0; i < rows.Count; i++)
|
|
204
|
-
{
|
|
205
|
-
_progress.CancellationToken.ThrowIfCancellationRequested(); // cooperative cancel
|
|
206
|
-
await _exports.ProcessAsync(rows[i]);
|
|
207
|
-
await _progress.ReportProgressAsync((i + 1) * 100 / rows.Count);
|
|
208
|
-
}
|
|
209
|
-
await _progress.ReportResultUrlAsync(await _exports.UploadAsync(requestId));
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Register every job class as `Scoped`
|
|
215
|
-
**File:** the host project's `Extensions/InfrastructureServiceRegistration.cs` (or `Program.cs`) — the
|
|
216
|
-
same registration site as C4
|
|
217
|
-
```csharp
|
|
218
|
-
services.AddScoped<ExportDataJob>();
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Dispatch / poll / cancel — inject `IJobScheduler` in a handler or endpoint
|
|
222
|
-
```csharp
|
|
223
|
-
var jobId = await _scheduler.EnqueueAsync<ExportDataJob>(
|
|
224
|
-
j => j.RunAsync(req.Table, req.RequestId), description: $"Export {req.Table}", userId: currentUserId);
|
|
225
|
-
|
|
226
|
-
await _scheduler.ScheduleAsync<SendReminderJob>(j => j.SendAsync(userId), TimeSpan.FromMinutes(30)); // delayed
|
|
227
|
-
JobStatus? status = await _scheduler.GetStatusAsync(jobId); // null = unknown/expired → 404
|
|
228
|
-
await _scheduler.CancelAsync(jobId);
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### Recurring jobs — register at startup via `IHostedService` (fire at next CRON occurrence)
|
|
232
|
-
```csharp
|
|
233
|
-
public class JobConfigurator(IJobScheduler _scheduler) : IHostedService
|
|
234
|
-
{
|
|
235
|
-
public Task StartAsync(CancellationToken ct) =>
|
|
236
|
-
_scheduler.AddOrUpdateRecurringAsync<NightlyReportJob>(
|
|
237
|
-
"nightly-report", j => j.GenerateAsync(), "0 2 * * *"); // daily 02:00
|
|
238
|
-
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
|
|
239
|
-
}
|
|
240
|
-
// services.AddHostedService<JobConfigurator>();
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
## Verification
|
|
246
|
-
|
|
247
|
-
1. **Builds** — `dotnet build` succeeds; no `Version=` on references; `AppWeaver.Jobs.InMemory` absent.
|
|
248
|
-
2. **Startup validates** — app boots; `Provider` is `"Quartz"`; the Quartz hosted service starts.
|
|
249
|
-
3. **Schema exists** — if a persistence implementation was selected, the `JobStatus` table (and/or
|
|
250
|
-
Quartz tables) are present.
|
|
251
|
-
4. **Resolves** — `IJobScheduler` resolves; each job class resolves (registered `Scoped`).
|
|
252
|
-
5. **Round-trip** — enqueue → poll `GetStatusAsync` → reaches `Completed` with expected
|
|
253
|
-
`ProgressPercent`/`ResultUrl`; status is readable from the persistence store.
|
|
254
|
-
6. **Cancel** — a cancellable job moves to `Cancelled` after `CancelAsync`.
|
|
255
|
-
|
|
256
|
-
## Related
|
|
257
|
-
|
|
258
|
-
- [README.md](./README.md) · framework `Jobs/README.md` (deep-dive) · [[components-readme]] ·
|
|
259
|
-
[[cqrs]] · [[persistence]] · [[vertical-slices]] · [[dependency-policy]] · [[ai-rules]]
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# QrGenerator Component (`AppWeaver.QrGenerator`)
|
|
2
|
-
|
|
3
|
-
> Pure technology (domain-agnostic). Reference sheet for the QR-code generation component.
|
|
4
|
-
> To wire it into a project, follow the agent runbook in [integration.md](./integration.md).
|
|
5
|
-
> Commands: `/aw-spec-add-component`.
|
|
6
|
-
|
|
7
|
-
## What it is
|
|
8
|
-
|
|
9
|
-
A small provider-agnostic component that turns a string into a QR-code **PNG byte array**.
|
|
10
|
-
Application code depends on the `IQRGenerator` abstraction; the implementation
|
|
11
|
-
(`QRCoder` library) is selected once during DI registration.
|
|
12
|
-
|
|
13
|
-
**Use when:** a feature must produce a scannable QR code — patient/asset cards, share links,
|
|
14
|
-
check-in tokens, downloadable QR images.
|
|
15
|
-
|
|
16
|
-
## Public contract (`AppWeaver.QrGenerator`)
|
|
17
|
-
|
|
18
|
-
```csharp
|
|
19
|
-
namespace AppWeaver.QRGenerator; // note: namespace is "QRGenerator" (capital R)
|
|
20
|
-
|
|
21
|
-
public interface IQRGenerator
|
|
22
|
-
{
|
|
23
|
-
/// <summary>Encodes the content and returns a PNG image as a byte[].</summary>
|
|
24
|
-
byte[] GenerateQR(string QRContent);
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Implementation detail (`QRCoderService`): ECC level **Q**, **20px** per module, output **PNG**.
|
|
29
|
-
`null` content throws `ArgumentNullException`; an **empty string is allowed** (returns a valid
|
|
30
|
-
PNG).
|
|
31
|
-
|
|
32
|
-
## Packages
|
|
33
|
-
|
|
34
|
-
| Package | Version | Role | Builder method | Provider key |
|
|
35
|
-
|---|---|---|---|---|
|
|
36
|
-
| `AppWeaver.QrGenerator` | `0.1.2` | **Required.** Abstraction + `AddQRGenerator` + facade. | — | — |
|
|
37
|
-
| `AppWeaver.QrGenerator.QRCoder` | `0.1.2` | QRCoder-backed implementation. | `UseQRCoder` | `QRCoder` |
|
|
38
|
-
|
|
39
|
-
> **Casing trap:** the **package IDs** use `QrGenerator` (lower `r`) but the **namespaces/types**
|
|
40
|
-
> use `QRGenerator` (upper `R`). `using AppWeaver.QRGenerator;` is correct.
|
|
41
|
-
|
|
42
|
-
## Registration entry point
|
|
43
|
-
|
|
44
|
-
```csharp
|
|
45
|
-
// In AppWeaver.QRGenerator.Extensions
|
|
46
|
-
IComponentBuilder<IQRGenerator> AddQRGenerator(
|
|
47
|
-
this IServiceCollection services,
|
|
48
|
-
Action<IComponentBuilder<IQRGenerator>> configureBuilder, // required
|
|
49
|
-
Action<QRConfiguration>? configureOptions = null, // set Provider here
|
|
50
|
-
IConfiguration? configuration = null); // OR bind from config
|
|
51
|
-
|
|
52
|
-
// In AppWeaver.QRGenerator.QRCoder.Extensions
|
|
53
|
-
IComponentBuilder<IQRGenerator> UseQRCoder(
|
|
54
|
-
this IComponentBuilder<IQRGenerator> builder,
|
|
55
|
-
Action<QRGeneratorOptions>? options = null, // QRGeneratorOptions is currently empty
|
|
56
|
-
IConfiguration? configuration = null); // binds section "QRGenerator.QRCoder"
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
`QRConfiguration` exposes a single `[Required] string Provider`. The facade resolves the keyed
|
|
60
|
-
provider whose key equals `Provider`.
|
|
61
|
-
|
|
62
|
-
## Source map (framework)
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
framework/QrGenerator/
|
|
66
|
-
Integration.md ← original endpoint tutorial (reference)
|
|
67
|
-
README.md ← component overview
|
|
68
|
-
src/
|
|
69
|
-
AppWeaver.QrGenerator/ ← IQRGenerator, AddQRGenerator, DefaultImplementation
|
|
70
|
-
AppWeaver.QrGenerator.QRCoder/ ← UseQRCoder + QRCoderService
|
|
71
|
-
test/
|
|
72
|
-
QrGenerator.QrCoder.Tests/ ← QRCoderServiceTests (xUnit)
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Sample reference endpoint
|
|
76
|
-
|
|
77
|
-
A ready-to-run FastEndpoint that generates a QR PNG inline (no command/handler) is bundled at
|
|
78
|
-
[`ReferenceEndpoints/GenerateQrEndpoint.cs`](./ReferenceEndpoints/GenerateQrEndpoint.cs). It is
|
|
79
|
-
offered (opt-in) at the end of the [integration.md](./integration.md) runbook and, if accepted,
|
|
80
|
-
copied into the host project's `ReferenceEndpoints/` folder. Endpoint: `POST /reference/qr-codes`.
|
|
81
|
-
|
|
82
|
-
## Related
|
|
83
|
-
|
|
84
|
-
- [integration.md](./integration.md) · [[components-readme]] · [[cqrs]] ·
|
|
85
|
-
[[vertical-slices]] · [[dependency-policy]]
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
using FastEndpoints;
|
|
2
|
-
using AppWeaver.QRGenerator; // namespace uses QRGenerator (capital R); package id is QrGenerator
|
|
3
|
-
|
|
4
|
-
// NOTE: This is a bundled SAMPLE endpoint for the AppWeaver QrGenerator component.
|
|
5
|
-
// It lives under docs/ and is NOT compiled here. /aw-spec-addcomponent copies it into the
|
|
6
|
-
// host project's ReferenceEndpoints/ folder and replaces the __HOSTPROJECT__ namespace token
|
|
7
|
-
// with the host project's root namespace (e.g. AppWeaver.Health.Service).
|
|
8
|
-
namespace __HOSTPROJECT__.ReferenceEndpoints;
|
|
9
|
-
|
|
10
|
-
/// <summary>
|
|
11
|
-
/// Sample reference endpoint for the AppWeaver QrGenerator component.
|
|
12
|
-
///
|
|
13
|
-
/// Demonstrates the full sequence inline — it injects the <see cref="IQRGenerator"/> abstraction
|
|
14
|
-
/// directly, calls <c>GenerateQR</c>, and streams the PNG back as <c>image/png</c>. There is
|
|
15
|
-
/// intentionally no command / command-handler / mediator: this is a copy-paste sample only.
|
|
16
|
-
///
|
|
17
|
-
/// Behaviour note: <c>GenerateQR(null)</c> throws <see cref="System.ArgumentNullException"/>, but an
|
|
18
|
-
/// empty string is valid and returns a (small) PNG, so only <c>null</c> content is rejected here.
|
|
19
|
-
/// </summary>
|
|
20
|
-
public class GenerateQrEndpoint(IQRGenerator _qr) : Endpoint<GenerateQrRequest>
|
|
21
|
-
{
|
|
22
|
-
public override void Configure()
|
|
23
|
-
{
|
|
24
|
-
Post("/reference/qr-codes");
|
|
25
|
-
AllowAnonymous();
|
|
26
|
-
Summary(s =>
|
|
27
|
-
{
|
|
28
|
-
s.Summary = "Generate a QR-code PNG from text content";
|
|
29
|
-
s.Description = "Reference endpoint for the AppWeaver QrGenerator component. Injects "
|
|
30
|
-
+ "IQRGenerator directly (no command/handler) and returns an image/png body.";
|
|
31
|
-
});
|
|
32
|
-
Options(o => o.WithTags("Reference - QrGenerator"));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public override async Task HandleAsync(GenerateQrRequest req, CancellationToken ct)
|
|
36
|
-
{
|
|
37
|
-
if (req.Content is null) // null throws inside the provider; empty string is allowed
|
|
38
|
-
{
|
|
39
|
-
HttpContext.Response.StatusCode = 400;
|
|
40
|
-
await HttpContext.Response.WriteAsJsonAsync(
|
|
41
|
-
new { error = "'content' is required (an empty string is allowed)." }, ct);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
byte[] png = _qr.GenerateQR(req.Content);
|
|
46
|
-
|
|
47
|
-
HttpContext.Response.ContentType = "image/png";
|
|
48
|
-
HttpContext.Response.Headers["Content-Disposition"] = "inline; filename=qr-code.png";
|
|
49
|
-
await HttpContext.Response.Body.WriteAsync(png, ct);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public class GenerateQrRequest
|
|
54
|
-
{
|
|
55
|
-
/// <summary>Text to encode into the QR code. Empty string is valid; null is rejected.</summary>
|
|
56
|
-
public string? Content { get; set; }
|
|
57
|
-
}
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
# QrGenerator Component — Integration Workflow (agent playbook)
|
|
2
|
-
|
|
3
|
-
> Pure technology (domain-agnostic). This file is an **agent runbook**: read it top-to-bottom and
|
|
4
|
-
> execute the workflow in order. It drives an interactive integration of `IQRGenerator` into a
|
|
5
|
-
> project. Read [README.md](./README.md) first for the contract. Replace `<SampleProjectName>`
|
|
6
|
-
> with the project name. Commands: `/aw-spec-implement`.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## Agent workflow — execute in order
|
|
11
|
-
|
|
12
|
-
1. **Load context.** Read [README.md](./README.md) and this file.
|
|
13
|
-
2. **Resolve the host project** `<HostProject>` (see [[folder-structure]], [[components-readme]] R0.2):
|
|
14
|
-
the single startup project that owns **`Program.cs`** + the ASP.NET Core web SDK —
|
|
15
|
-
`<SampleProjectName>.Service` (Vertical Slice foundation), `<SampleProjectName>.API` (Modular
|
|
16
|
-
Monolith foundation), or another name in a future template. **Find it by locating `Program.cs`, not
|
|
17
|
-
by assuming the name.** There is **no** separate `…Application` / `…Infrastructure` project in these
|
|
18
|
-
foundations; `<HostProject>` is the target for C1–C3 below and holds the API endpoints.
|
|
19
|
-
3. **Present the implementation catalog (below) as a selection list and ASK the user which
|
|
20
|
-
implementation(s) to integrate.** Use a multi-select prompt (`AskUserQuestion`,
|
|
21
|
-
`multiSelect: true`) for consistency with the other components.
|
|
22
|
-
- This component currently ships **one** implementation (`QRCoder`). If the catalog has a single
|
|
23
|
-
row, tell the user it is the only option and proceed once they confirm (do not invent others).
|
|
24
|
-
4. **Apply “Common steps”** (§Common steps).
|
|
25
|
-
5. **For each selected implementation, apply its block** (§Implementation blocks).
|
|
26
|
-
6. **Set the provider:** `configureOptions: options => options.Provider = "<selected key>"`.
|
|
27
|
-
7. **(Optional) Build the feature slice** (§Usage) — DTO + Command + Handler + Endpoint.
|
|
28
|
-
8. **Offer the bundled sample endpoint (ASK).** After the wiring above, ask the user (single-select
|
|
29
|
-
yes/no via `AskUserQuestion`) whether to add the ready-made sample endpoint. If **yes**, copy it
|
|
30
|
-
into the host project — see §Sample reference endpoint. If **no**, skip silently.
|
|
31
|
-
9. **Verify** (§Verification) and report what was added.
|
|
32
|
-
|
|
33
|
-
> **Guardrail — namespace casing.** Package IDs use `QrGenerator` (lower `r`); **namespaces/types
|
|
34
|
-
> use `QRGenerator` (upper `R`)**. `using AppWeaver.QRGenerator;` is correct — getting this wrong is
|
|
35
|
-
> the most common build break.
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## Implementation catalog (selection list)
|
|
40
|
-
|
|
41
|
-
| Key | Implementation | Best for | Package (v) | Settings |
|
|
42
|
-
|---|---|---|---|---|
|
|
43
|
-
| `QRCoder` | QRCoder library (PNG, ECC level Q, 20px/module) | All QR-code generation | `AppWeaver.QrGenerator.QRCoder` (0.1.2) | none (`QRGeneratorOptions` is empty) |
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Common steps (apply once)
|
|
48
|
-
|
|
49
|
-
### C1 — Abstraction package version
|
|
50
|
-
|
|
51
|
-
**File:** `Directory.Packages.props`
|
|
52
|
-
|
|
53
|
-
```xml
|
|
54
|
-
<ItemGroup>
|
|
55
|
-
<!-- AppWeaver — QrGenerator (abstraction) -->
|
|
56
|
-
<PackageVersion Include="AppWeaver.QrGenerator" Version="0.1.2" />
|
|
57
|
-
<!-- + one <PackageVersion> per selected implementation — see block below -->
|
|
58
|
-
</ItemGroup>
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### C2 — Project references (no `Version=`)
|
|
62
|
-
|
|
63
|
-
**File:** `src/<HostProject>/<HostProject>.csproj` (the host project resolved in step 2 — single
|
|
64
|
-
project, no separate Application/Infrastructure).
|
|
65
|
-
|
|
66
|
-
```xml
|
|
67
|
-
<ItemGroup>
|
|
68
|
-
<PackageReference Include="AppWeaver.QrGenerator" />
|
|
69
|
-
<!-- + each selected implementation’s package — see block below -->
|
|
70
|
-
</ItemGroup>
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### C3 — Registration scaffold
|
|
74
|
-
|
|
75
|
-
**File:** the host project `<HostProject>` (step 2) — register in its
|
|
76
|
-
`Extensions/InfrastructureServiceRegistration.cs` (`AddInfrastructure`) when that file exists (it does
|
|
77
|
-
in the Vertical Slice / Modular Monolith foundations), otherwise directly in `Program.cs` **before**
|
|
78
|
-
`builder.Build()`.
|
|
79
|
-
|
|
80
|
-
```csharp
|
|
81
|
-
using AppWeaver.QRGenerator.Extensions; // AddQRGenerator (capital R)
|
|
82
|
-
|
|
83
|
-
services.AddQRGenerator(
|
|
84
|
-
configureBuilder: builder =>
|
|
85
|
-
{
|
|
86
|
-
// ⬇ one Use…() call per selected implementation
|
|
87
|
-
},
|
|
88
|
-
configureOptions: options => options.Provider = "<selected key>"); // step 6
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
> **Dual local/Aspire startup ([[components-readme]] R0.2a).** If the host project also has an
|
|
92
|
-
> `AddAspireInfrastructure` method (`Extensions/AspireInfrastructureExtensions.cs`) selected by
|
|
93
|
-
> `if (isAspire) … else …` in `Program.cs`, add this **same** `AddQRGenerator(...)` block to **both**
|
|
94
|
-
> `AddInfrastructure` and `AddAspireInfrastructure` (it is DB-agnostic — identical in both), or
|
|
95
|
-
> `IQRGenerator` is unresolvable when the app runs under Aspire.
|
|
96
|
-
|
|
97
|
-
No `appsettings.json` entry is required — there is nothing to configure.
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Implementation blocks
|
|
102
|
-
|
|
103
|
-
### QRCoder
|
|
104
|
-
```xml
|
|
105
|
-
<!-- Directory.Packages.props --> <PackageVersion Include="AppWeaver.QrGenerator.QRCoder" Version="0.1.2" />
|
|
106
|
-
<!-- *.csproj --> <PackageReference Include="AppWeaver.QrGenerator.QRCoder" />
|
|
107
|
-
```
|
|
108
|
-
```csharp
|
|
109
|
-
using AppWeaver.QRGenerator.QRCoder.Extensions; // UseQRCoder
|
|
110
|
-
// inside AddQRGenerator builder:
|
|
111
|
-
builder.UseQRCoder();
|
|
112
|
-
// → set options.Provider = "QRCoder" in step 6
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## Usage (feature slice — DTO + Command + Handler + Endpoint)
|
|
118
|
-
|
|
119
|
-
Generate inside an Application command handler; inject **`IQRGenerator`**. Return the image as
|
|
120
|
-
base64 in a DTO so the API layer owns the response shape (see [[cqrs]], [[vertical-slices]]).
|
|
121
|
-
|
|
122
|
-
**DTO** — `src/<SampleProjectName>.Application/Features/Qr/Dtos/QrCodeDto.cs`
|
|
123
|
-
```csharp
|
|
124
|
-
public record QrCodeDto
|
|
125
|
-
{
|
|
126
|
-
public string Base64Image { get; init; } = string.Empty;
|
|
127
|
-
public string ContentType { get; init; } = "image/png";
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
**Command + Handler** — `…/Features/Qr/Commands/GenerateQrCommand.cs`
|
|
132
|
-
```csharp
|
|
133
|
-
public record GenerateQrCommand(string Content) : ICommand<Result<QrCodeDto>>;
|
|
134
|
-
|
|
135
|
-
public class GenerateQrCommandHandler(IQRGenerator _qr) // using AppWeaver.QRGenerator;
|
|
136
|
-
: ICommandHandler<GenerateQrCommand, Result<QrCodeDto>>
|
|
137
|
-
{
|
|
138
|
-
public ValueTask<Result<QrCodeDto>> Handle(GenerateQrCommand req, CancellationToken ct)
|
|
139
|
-
{
|
|
140
|
-
if (string.IsNullOrWhiteSpace(req.Content)) // GenerateQR(null) throws; guard first
|
|
141
|
-
return ValueTask.FromResult(
|
|
142
|
-
Result<QrCodeDto>.Failure(new Error("qr.empty_content", "QR content is required.", 400)));
|
|
143
|
-
|
|
144
|
-
byte[] png = _qr.GenerateQR(req.Content);
|
|
145
|
-
return ValueTask.FromResult(Result<QrCodeDto>.Ok(new QrCodeDto
|
|
146
|
-
{
|
|
147
|
-
Base64Image = Convert.ToBase64String(png),
|
|
148
|
-
ContentType = "image/png"
|
|
149
|
-
}));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
**Endpoint** — `src/<SampleProjectName>.Service/Qr/Features/GenerateQr/GenerateQrEndpoint.cs`
|
|
155
|
-
```csharp
|
|
156
|
-
public class GenerateQrEndpoint(IMediator _mediator) : Endpoint<GenerateQrRequest>
|
|
157
|
-
{
|
|
158
|
-
public override void Configure()
|
|
159
|
-
{
|
|
160
|
-
Post("/qr-codes");
|
|
161
|
-
Summary(s => s.Summary = "Generate a QR-code PNG from text content");
|
|
162
|
-
Options(o => o.WithTags("04 - QR - (FastEndpoints)"));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
public override async Task HandleAsync(GenerateQrRequest req, CancellationToken ct)
|
|
166
|
-
{
|
|
167
|
-
var result = await _mediator.Send(new GenerateQrCommand(req.Content), ct);
|
|
168
|
-
if (result.IsSuccess && result.Value is not null)
|
|
169
|
-
{
|
|
170
|
-
var bytes = Convert.FromBase64String(result.Value.Base64Image);
|
|
171
|
-
HttpContext.Response.ContentType = result.Value.ContentType; // image/png
|
|
172
|
-
HttpContext.Response.Headers["Content-Disposition"] = "attachment; filename=qr-code.png";
|
|
173
|
-
await HttpContext.Response.Body.WriteAsync(bytes, ct);
|
|
174
|
-
}
|
|
175
|
-
else
|
|
176
|
-
{
|
|
177
|
-
HttpContext.Response.StatusCode = 400;
|
|
178
|
-
await HttpContext.Response.WriteAsJsonAsync(new { error = "Failed to generate QR code" }, ct);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
public class GenerateQrRequest { public string Content { get; set; } = string.Empty; }
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
---
|
|
187
|
-
|
|
188
|
-
## Sample reference endpoint
|
|
189
|
-
|
|
190
|
-
The §Usage slice above routes through a command + handler. For a quick, self-contained sample there
|
|
191
|
-
is also a ready-to-run FastEndpoint bundled in this component folder at
|
|
192
|
-
[`ReferenceEndpoints/GenerateQrEndpoint.cs`](./ReferenceEndpoints/GenerateQrEndpoint.cs). It injects
|
|
193
|
-
`IQRGenerator` directly and does the **full sequence inline** — no command/handler/mediator. It is
|
|
194
|
-
**not** wired automatically; it is offered in workflow step 8.
|
|
195
|
-
|
|
196
|
-
**When the user opts in (step 8) — copy procedure:**
|
|
197
|
-
|
|
198
|
-
1. Confirm `docs/components/QrGenerator/ReferenceEndpoints/GenerateQrEndpoint.cs` exists. If it is
|
|
199
|
-
missing, tell the user and skip (do not hand-write a replacement).
|
|
200
|
-
2. Create `src/<HostProject>/ReferenceEndpoints/` if it does not exist (`<HostProject>` = the project
|
|
201
|
-
resolved in step 2).
|
|
202
|
-
3. Copy the file to `src/<HostProject>/ReferenceEndpoints/GenerateQrEndpoint.cs`, replacing the
|
|
203
|
-
namespace token `__HOSTPROJECT__` with `<HostProject>`'s root namespace (e.g.
|
|
204
|
-
`AppWeaver.Health.Service`). Keep the `using AppWeaver.QRGenerator;` (capital R) — do not "fix" it
|
|
205
|
-
to match the package id. Change nothing else.
|
|
206
|
-
4. Do **not** touch the C1–C3 wiring. FastEndpoints auto-discovers the endpoint by assembly scan, so
|
|
207
|
-
no extra registration is required.
|
|
208
|
-
|
|
209
|
-
**Endpoint:** `POST /reference/qr-codes` — body `{ "content" }` — returns an `image/png` body.
|
|
210
|
-
An empty `content` string is valid (returns a small PNG); `null` is rejected with 400.
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## Verification
|
|
215
|
-
|
|
216
|
-
1. **Builds** — `dotnet build` succeeds; `using` directives use `AppWeaver.QRGenerator…`; no
|
|
217
|
-
`Version=` on package references.
|
|
218
|
-
2. **Startup validates** — app boots; `options.Provider` is `"QRCoder"`.
|
|
219
|
-
3. **Resolves** — `IQRGenerator` resolves from DI.
|
|
220
|
-
4. **Generates** — `GenerateQR("test")` returns a non-empty `byte[]`; `POST /qr-codes` returns an
|
|
221
|
-
`image/png` body that opens as a scannable QR image. (`null` content throws; empty string is
|
|
222
|
-
valid and returns a PNG.)
|
|
223
|
-
5. **Sample endpoint (only if copied in step 8)** — the build still succeeds with
|
|
224
|
-
`ReferenceEndpoints/GenerateQrEndpoint.cs` added, the `__HOSTPROJECT__` token was replaced, and
|
|
225
|
-
`POST /reference/qr-codes` returns an `image/png`.
|
|
226
|
-
|
|
227
|
-
## Related
|
|
228
|
-
|
|
229
|
-
- [README.md](./README.md) · [[components-readme]] · [[cqrs]] · [[vertical-slices]] ·
|
|
230
|
-
[[clean-architecture]] · [[dependency-policy]]
|