aw-spec-cli 0.1.4 → 0.1.6

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.
Files changed (225) hide show
  1. package/assets/.agents/agent.json +28 -0
  2. package/assets/.agents/skills/appweaver/SKILL.md +56 -0
  3. package/assets/.agents/skills/appweaver-demo/SKILL.md +59 -0
  4. package/assets/.claude/agent.json +27 -0
  5. package/assets/.claude/skills/appweaver/SKILL.md +55 -0
  6. package/assets/.claude/skills/appweaver-demo/SKILL.md +59 -0
  7. package/assets/.kilocode/agent.json +28 -0
  8. package/assets/.kilocode/workflows/appweaver-demo.md +59 -0
  9. package/assets/.kilocode/workflows/appweaver.md +55 -0
  10. package/dist/commands/init.d.ts.map +1 -1
  11. package/dist/commands/init.js +7 -54
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/index.js +0 -2
  14. package/dist/index.js.map +1 -1
  15. package/package.json +1 -1
  16. package/assets/.agents/skills/aw-spec-addcomponent/SKILL.md +0 -129
  17. package/assets/.agents/skills/aw-spec-agent-context-update/SKILL.md +0 -31
  18. package/assets/.agents/skills/aw-spec-analyze/SKILL.md +0 -296
  19. package/assets/.agents/skills/aw-spec-checklist/SKILL.md +0 -374
  20. package/assets/.agents/skills/aw-spec-clarify/SKILL.md +0 -320
  21. package/assets/.agents/skills/aw-spec-constitution/SKILL.md +0 -194
  22. package/assets/.agents/skills/aw-spec-fast-implement/SKILL.md +0 -268
  23. package/assets/.agents/skills/aw-spec-foundation/SKILL.md +0 -358
  24. package/assets/.agents/skills/aw-spec-implement/SKILL.md +0 -279
  25. package/assets/.agents/skills/aw-spec-minimal-implementation/SKILL.md +0 -209
  26. package/assets/.agents/skills/aw-spec-plan/SKILL.md +0 -221
  27. package/assets/.agents/skills/aw-spec-select-domain/SKILL.md +0 -73
  28. package/assets/.agents/skills/aw-spec-specify/SKILL.md +0 -382
  29. package/assets/.agents/skills/aw-spec-tasks/SKILL.md +0 -259
  30. package/assets/.agents/skills/aw-spec-taskstoissues/SKILL.md +0 -107
  31. package/assets/.aw-spec/domain.json +0 -3
  32. package/assets/.aw-spec/extensions/.registry +0 -19
  33. package/assets/.aw-spec/extensions/agent-context/README.md +0 -57
  34. package/assets/.aw-spec/extensions/agent-context/agent-context-config.yml +0 -4
  35. package/assets/.aw-spec/extensions/agent-context/commands/aw-spec.agent-context.update.md +0 -26
  36. package/assets/.aw-spec/extensions/agent-context/extension.yml +0 -34
  37. package/assets/.aw-spec/extensions/agent-context/scripts/bash/update-agent-context.sh +0 -201
  38. package/assets/.aw-spec/extensions/agent-context/scripts/powershell/update-agent-context.ps1 +0 -237
  39. package/assets/.aw-spec/extensions.yml +0 -23
  40. package/assets/.aw-spec/feature.json +0 -3
  41. package/assets/.aw-spec/init-options.json +0 -9
  42. package/assets/.aw-spec/integration.json +0 -15
  43. package/assets/.aw-spec/integrations/aw-spec.manifest.json +0 -17
  44. package/assets/.aw-spec/integrations/claude.manifest.json +0 -17
  45. package/assets/.aw-spec/memory/constitution.md +0 -50
  46. package/assets/.aw-spec/scripts/bash/check-prerequisites.sh +0 -189
  47. package/assets/.aw-spec/scripts/bash/common.sh +0 -582
  48. package/assets/.aw-spec/scripts/bash/create-new-feature.sh +0 -299
  49. package/assets/.aw-spec/scripts/bash/setup-plan.sh +0 -84
  50. package/assets/.aw-spec/scripts/bash/setup-tasks.sh +0 -91
  51. package/assets/.aw-spec/templates/checklist-template.md +0 -40
  52. package/assets/.aw-spec/templates/constitution-template.md +0 -57
  53. package/assets/.aw-spec/templates/plan-template.md +0 -113
  54. package/assets/.aw-spec/templates/spec-template.md +0 -131
  55. package/assets/.aw-spec/templates/tasks-template.md +0 -252
  56. package/assets/.aw-spec/workflows/aw-spec/workflow.yml +0 -77
  57. package/assets/.aw-spec/workflows/workflow-registry.json +0 -13
  58. package/assets/.claude/skills/aw-spec-addcomponent/SKILL.md +0 -129
  59. package/assets/.claude/skills/aw-spec-agent-context-update/SKILL.md +0 -31
  60. package/assets/.claude/skills/aw-spec-analyze/SKILL.md +0 -296
  61. package/assets/.claude/skills/aw-spec-checklist/SKILL.md +0 -374
  62. package/assets/.claude/skills/aw-spec-clarify/SKILL.md +0 -320
  63. package/assets/.claude/skills/aw-spec-constitution/SKILL.md +0 -194
  64. package/assets/.claude/skills/aw-spec-fast-implement/SKILL.md +0 -268
  65. package/assets/.claude/skills/aw-spec-foundation/SKILL.md +0 -358
  66. package/assets/.claude/skills/aw-spec-implement/SKILL.md +0 -279
  67. package/assets/.claude/skills/aw-spec-minimal-implementation/SKILL.md +0 -209
  68. package/assets/.claude/skills/aw-spec-plan/SKILL.md +0 -221
  69. package/assets/.claude/skills/aw-spec-select-domain/SKILL.md +0 -73
  70. package/assets/.claude/skills/aw-spec-specify/SKILL.md +0 -382
  71. package/assets/.claude/skills/aw-spec-tasks/SKILL.md +0 -259
  72. package/assets/.claude/skills/aw-spec-taskstoissues/SKILL.md +0 -107
  73. package/assets/.kilocode/rules/specify-rules.md +0 -4
  74. package/assets/.kilocode/workflows/aw-spec-addcomponent.md +0 -129
  75. package/assets/.kilocode/workflows/aw-spec-agent-context.update.md +0 -31
  76. package/assets/.kilocode/workflows/aw-spec-analyze.md +0 -296
  77. package/assets/.kilocode/workflows/aw-spec-checklist.md +0 -374
  78. package/assets/.kilocode/workflows/aw-spec-clarify.md +0 -320
  79. package/assets/.kilocode/workflows/aw-spec-constitution.md +0 -194
  80. package/assets/.kilocode/workflows/aw-spec-fast-implement.md +0 -263
  81. package/assets/.kilocode/workflows/aw-spec-foundation.md +0 -358
  82. package/assets/.kilocode/workflows/aw-spec-implement.md +0 -279
  83. package/assets/.kilocode/workflows/aw-spec-minimal-implementation.md +0 -204
  84. package/assets/.kilocode/workflows/aw-spec-plan.md +0 -221
  85. package/assets/.kilocode/workflows/aw-spec-select-domain.md +0 -73
  86. package/assets/.kilocode/workflows/aw-spec-specify.md +0 -382
  87. package/assets/.kilocode/workflows/aw-spec-tasks.md +0 -259
  88. package/assets/.kilocode/workflows/aw-spec-taskstoissues.md +0 -107
  89. package/assets/docs/ModularMonolith/ModularMonolith.Reference.pack.txt +0 -25248
  90. package/assets/docs/ModularMonolith/ModularMonolith.pack.txt +0 -5726
  91. package/assets/docs/ModularMonolith/pack-foundation.ps1 +0 -145
  92. package/assets/docs/ModularMonolith/pack-foundation.sh +0 -196
  93. package/assets/docs/ModularMonolith/pack-reference-foundation.ps1 +0 -157
  94. package/assets/docs/ModularMonolith/pack-reference-foundation.sh +0 -209
  95. package/assets/docs/ModularMonolith/unpack-foundation.ps1 +0 -126
  96. package/assets/docs/ModularMonolith/unpack-foundation.sh +0 -217
  97. package/assets/docs/ModularMonolith/unpack-reference-foundation.ps1 +0 -129
  98. package/assets/docs/ModularMonolith/unpack-reference-foundation.sh +0 -218
  99. package/assets/docs/README.md +0 -106
  100. package/assets/docs/UI.Visualization/PACKAGING.md +0 -191
  101. package/assets/docs/UI.Visualization/UI.Visualization.Reference.pack.txt +0 -11768
  102. package/assets/docs/UI.Visualization/UI.Visualization.pack.txt +0 -11214
  103. package/assets/docs/UI.Visualization/pack-foundation.ps1 +0 -165
  104. package/assets/docs/UI.Visualization/pack-foundation.sh +0 -206
  105. package/assets/docs/UI.Visualization/pack-reference-foundation.ps1 +0 -159
  106. package/assets/docs/UI.Visualization/pack-reference-foundation.sh +0 -205
  107. package/assets/docs/UI.Visualization/unpack-foundation.ps1 +0 -126
  108. package/assets/docs/UI.Visualization/unpack-foundation.sh +0 -217
  109. package/assets/docs/UI.Visualization/unpack-reference-foundation.ps1 +0 -129
  110. package/assets/docs/UI.Visualization/unpack-reference-foundation.sh +0 -218
  111. package/assets/docs/VerticalSlice/PACKAGING.md +0 -185
  112. package/assets/docs/VerticalSlice/VerticalSlice.Reference.pack.txt +0 -19318
  113. package/assets/docs/VerticalSlice/VerticalSlice.pack.txt +0 -2833
  114. package/assets/docs/VerticalSlice/pack-foundation.ps1 +0 -145
  115. package/assets/docs/VerticalSlice/pack-foundation.sh +0 -196
  116. package/assets/docs/VerticalSlice/pack-reference-foundation.ps1 +0 -157
  117. package/assets/docs/VerticalSlice/pack-reference-foundation.sh +0 -209
  118. package/assets/docs/VerticalSlice/unpack-foundation.ps1 +0 -126
  119. package/assets/docs/VerticalSlice/unpack-foundation.sh +0 -217
  120. package/assets/docs/VerticalSlice/unpack-reference-foundation.ps1 +0 -129
  121. package/assets/docs/VerticalSlice/unpack-reference-foundation.sh +0 -218
  122. package/assets/docs/architecture/clean-architecture.md +0 -71
  123. package/assets/docs/architecture/cqrs.md +0 -141
  124. package/assets/docs/architecture/eventing.md +0 -83
  125. package/assets/docs/architecture/folder-structure.md +0 -61
  126. package/assets/docs/architecture/modular-monolith.md +0 -47
  127. package/assets/docs/architecture/persistence.md +0 -269
  128. package/assets/docs/architecture/testing.md +0 -55
  129. package/assets/docs/architecture/vertical-slices.md +0 -201
  130. package/assets/docs/command.txt +0 -295
  131. package/assets/docs/components/Email/README.md +0 -119
  132. package/assets/docs/components/Email/ReferenceEndpoints/SendEmailEndpoint.cs +0 -84
  133. package/assets/docs/components/Email/integration.md +0 -300
  134. package/assets/docs/components/Jobs/README.md +0 -113
  135. package/assets/docs/components/Jobs/integration.md +0 -259
  136. package/assets/docs/components/QrGenerator/README.md +0 -85
  137. package/assets/docs/components/QrGenerator/ReferenceEndpoints/GenerateQrEndpoint.cs +0 -57
  138. package/assets/docs/components/QrGenerator/integration.md +0 -230
  139. package/assets/docs/components/README.md +0 -74
  140. package/assets/docs/domain/domain-driven-design.md +0 -212
  141. package/assets/docs/domainspecs/UIVisualization/chart-bubble.md +0 -70
  142. package/assets/docs/domainspecs/UIVisualization/chart-dimred.md +0 -58
  143. package/assets/docs/domainspecs/UIVisualization/chart-heatmap.md +0 -62
  144. package/assets/docs/domainspecs/UIVisualization/chart-network.md +0 -69
  145. package/assets/docs/domainspecs/UIVisualization/chart-parallel.md +0 -75
  146. package/assets/docs/domainspecs/UIVisualization/chart-scurve.md +0 -66
  147. package/assets/docs/domainspecs/UIVisualization/chart-survival.md +0 -69
  148. package/assets/docs/domainspecs/UIVisualization/chart-trellis.md +0 -68
  149. package/assets/docs/domainspecs/UIVisualization/chart-violin.md +0 -61
  150. package/assets/docs/domainspecs/UIVisualization/chart-volcano.md +0 -63
  151. package/assets/docs/domainspecs/UIVisualization/charting-standards.domain.md +0 -80
  152. package/assets/docs/domainspecs/UIVisualization/clean-architecture.domain.md +0 -87
  153. package/assets/docs/domainspecs/UIVisualization/coding-standards.domain.md +0 -47
  154. package/assets/docs/domainspecs/UIVisualization/comandrulemap.md +0 -130
  155. package/assets/docs/domainspecs/UIVisualization/component-contracts.domain.md +0 -67
  156. package/assets/docs/domainspecs/UIVisualization/data-source.domain.md +0 -68
  157. package/assets/docs/domainspecs/UIVisualization/domain-requirements.md +0 -110
  158. package/assets/docs/domainspecs/UIVisualization/folder-structure.domain.md +0 -55
  159. package/assets/docs/domainspecs/UIVisualization/state-management.domain.md +0 -67
  160. package/assets/docs/domainspecs/UIVisualization/testing.domain.md +0 -52
  161. package/assets/docs/domainspecs/UIVisualization/ui-viz-terms.md +0 -39
  162. package/assets/docs/domainspecs/healthcare/ai-rules.domain.md +0 -34
  163. package/assets/docs/domainspecs/healthcare/api-versioning.domain.md +0 -17
  164. package/assets/docs/domainspecs/healthcare/auditing.domain.md +0 -29
  165. package/assets/docs/domainspecs/healthcare/authorization.domain.md +0 -26
  166. package/assets/docs/domainspecs/healthcare/clinical-terms.md +0 -20
  167. package/assets/docs/domainspecs/healthcare/coding-standards.domain.md +0 -26
  168. package/assets/docs/domainspecs/healthcare/comandrulemap.md +0 -166
  169. package/assets/docs/domainspecs/healthcare/consent.md +0 -19
  170. package/assets/docs/domainspecs/healthcare/cqrs.domain.md +0 -12
  171. package/assets/docs/domainspecs/healthcare/data-classification.domain.md +0 -28
  172. package/assets/docs/domainspecs/healthcare/dependency-policy.domain.md +0 -11
  173. package/assets/docs/domainspecs/healthcare/dicom.md +0 -15
  174. package/assets/docs/domainspecs/healthcare/domain-driven-design.domain.md +0 -28
  175. package/assets/docs/domainspecs/healthcare/domain-requirements.md +0 -55
  176. package/assets/docs/domainspecs/healthcare/eventing.domain.md +0 -18
  177. package/assets/docs/domainspecs/healthcare/fhir.md +0 -36
  178. package/assets/docs/domainspecs/healthcare/folder-structure.domain.md +0 -23
  179. package/assets/docs/domainspecs/healthcare/hipaa.md +0 -34
  180. package/assets/docs/domainspecs/healthcare/hl7.md +0 -15
  181. package/assets/docs/domainspecs/healthcare/integration-terms.md +0 -21
  182. package/assets/docs/domainspecs/healthcare/persistence.domain.md +0 -20
  183. package/assets/docs/domainspecs/healthcare/terminology.md +0 -30
  184. package/assets/docs/domainspecs/healthcare/testing.domain.md +0 -15
  185. package/assets/docs/domainspecs/healthcare/vertical-slices.domain.md +0 -18
  186. package/assets/docs/domainspecs/healthcare/zero-trust.domain.md +0 -12
  187. package/assets/docs/domainspecs/pharma/ai-rules.domain.md +0 -32
  188. package/assets/docs/domainspecs/pharma/api-versioning.domain.md +0 -20
  189. package/assets/docs/domainspecs/pharma/atc.md +0 -22
  190. package/assets/docs/domainspecs/pharma/auditing.domain.md +0 -26
  191. package/assets/docs/domainspecs/pharma/authorization.domain.md +0 -25
  192. package/assets/docs/domainspecs/pharma/coding-standards.domain.md +0 -28
  193. package/assets/docs/domainspecs/pharma/comandrulemap.md +0 -186
  194. package/assets/docs/domainspecs/pharma/cqrs.domain.md +0 -16
  195. package/assets/docs/domainspecs/pharma/data-classification.domain.md +0 -24
  196. package/assets/docs/domainspecs/pharma/data-provenance.md +0 -21
  197. package/assets/docs/domainspecs/pharma/dependency-policy.domain.md +0 -11
  198. package/assets/docs/domainspecs/pharma/domain-driven-design.domain.md +0 -36
  199. package/assets/docs/domainspecs/pharma/domain-requirements.md +0 -99
  200. package/assets/docs/domainspecs/pharma/eventing.domain.md +0 -18
  201. package/assets/docs/domainspecs/pharma/fhir.md +0 -31
  202. package/assets/docs/domainspecs/pharma/folder-structure.domain.md +0 -24
  203. package/assets/docs/domainspecs/pharma/hl7.md +0 -17
  204. package/assets/docs/domainspecs/pharma/idmp.md +0 -26
  205. package/assets/docs/domainspecs/pharma/integration-terms.md +0 -21
  206. package/assets/docs/domainspecs/pharma/ndc.md +0 -28
  207. package/assets/docs/domainspecs/pharma/persistence.domain.md +0 -23
  208. package/assets/docs/domainspecs/pharma/pharma-terms.md +0 -27
  209. package/assets/docs/domainspecs/pharma/product-lifecycle.domain.md +0 -30
  210. package/assets/docs/domainspecs/pharma/regulatory-compliance.md +0 -19
  211. package/assets/docs/domainspecs/pharma/terminology.md +0 -32
  212. package/assets/docs/domainspecs/pharma/testing.domain.md +0 -19
  213. package/assets/docs/domainspecs/pharma/vertical-slices.domain.md +0 -17
  214. package/assets/docs/domainspecs/pharma/zero-trust.domain.md +0 -12
  215. package/assets/docs/glossary/ai-rules.md +0 -59
  216. package/assets/docs/governance/api-versioning.md +0 -99
  217. package/assets/docs/governance/coding-standards.md +0 -87
  218. package/assets/docs/governance/dependency-policy.md +0 -40
  219. package/assets/docs/interop/ihe-conformance.md +0 -31
  220. package/assets/docs/security/auditing.md +0 -117
  221. package/assets/docs/security/authorization-matrix.md +0 -14
  222. package/assets/docs/security/authorization.md +0 -87
  223. package/assets/docs/security/data-classification.md +0 -38
  224. package/assets/docs/security/zero-trust.md +0 -76
  225. package/assets/docs/technology-constitution.md +0 -172
@@ -1,300 +0,0 @@
1
- # Email 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 `IEmailService` into a
5
- > project, asking the user which transport implementation(s) to wire. Read [README.md](./README.md)
6
- > first for the contract. Replace `<SampleProjectName>` with the project name.
7
- > Commands: `/aw-spec-implement`.
8
-
9
- ---
10
-
11
- ## Agent workflow — execute in order
12
-
13
- 1. **Load context.** Read [README.md](./README.md) (contract + provider matrix) and this file.
14
- 2. **Resolve the host project** `<HostProject>` (see [[folder-structure]], [[components-readme]] R0.2):
15
- the single startup project that owns **`Program.cs`** + the ASP.NET Core web SDK —
16
- `<SampleProjectName>.Service` (Vertical Slice foundation), `<SampleProjectName>.API` (Modular
17
- Monolith foundation), or another name in a future template. **Find it by locating `Program.cs`, not
18
- by assuming the name.** In these foundations application/domain/infrastructure code all live in this
19
- one project (modules are folders); there is **no** separate `…Application` / `…Infrastructure`
20
- project. `<HostProject>` is the target for C2–C4 below.
21
- 3. **Present the implementation catalog (below) as a selection list and ASK the user which
22
- implementation(s) to integrate.** Use an interactive multi-select prompt (`AskUserQuestion`,
23
- `multiSelect: true`). The list options are the rows of the *Implementation catalog*.
24
- - The user MAY select **more than one** (e.g., `MsGraph` for production + `File` for local dev).
25
- - If more than one is selected, **ask a follow-up single-select**: *which one is the default
26
- (active) provider?* — this becomes `options.Provider`.
27
- - If the user selects exactly one, that one is the default.
28
- 4. **Apply “Common steps” once** (§Common steps) — abstraction package + registration scaffold.
29
- 5. **For each selected implementation, apply its block** under §Implementation blocks — add its
30
- package, its `Use…` call inside the `AddEmailMessaging` builder, and its config section.
31
- 6. **Set the default provider:** `configureOptions: options => options.Provider = "<default key>"`.
32
- All selected implementations stay registered; the active one is switchable later via config.
33
- 7. **(Optional) Wire a usage example** in the relevant feature handler (§Usage).
34
- 8. **Offer the bundled sample endpoint (ASK).** After the wiring above, ask the user (single-select
35
- yes/no via `AskUserQuestion`) whether to add the ready-made sample endpoint. If **yes**, copy it
36
- into the host project — see §Sample reference endpoint. If **no**, skip silently.
37
- 9. **Verify** (§Verification) and report what was added.
38
-
39
- > **Guardrail — always set `options.Provider`.** When `Provider` is null the facade falls back to a
40
- > **hardcoded `"SendGrid"`** key; if SendGrid is not registered, resolution throws at runtime. Never
41
- > rely on the implicit fallback.
42
-
43
- ---
44
-
45
- ## Implementation catalog (selection list)
46
-
47
- Present these as the multi-select options. `Key` is the provider key and the `Use…` method suffix.
48
-
49
- | Key | Implementation | Best for | Package (v) | Required settings |
50
- |---|---|---|---|---|
51
- | `MsGraph` | Microsoft 365 / Graph API | Production on an O365 tenant | `AppWeaver.Messaging.Email.MsGraph` (0.1.2) | `TenantId`, `ClientId`, `ClientSecret`, `DefaultFrom` |
52
- | `Smtp` | Classic SMTP | Any SMTP server | `AppWeaver.Messaging.Email.Smtp` (0.1.3) | `Host`, `Port`, `Username`, `Password`, `EnableSsl` |
53
- | `SendGrid` | SendGrid API | SendGrid accounts | `AppWeaver.Messaging.Email.SendGrid` (0.1.2) | `ApiKey` *(required)*, `DefaultFrom` |
54
- | `OAuth2` | OAuth2-authenticated SMTP | O365 SMTP with OAuth2 | `AppWeaver.Messaging.Email.OAuth2` (0.1.3) | `TenantId`, `ClientId`, `ClientSecret`, `Host`, `Port` |
55
- | `File` | Writes email to a local `.txt` | Local dev / tests (no real send) | `AppWeaver.Messaging.Email.File` (0.1.2) | `DefaultFrom` |
56
-
57
- > Recommended pairing: **`MsGraph` (default) + `File`** so developers can run locally without
58
- > sending real mail, while production uses Graph.
59
-
60
- ---
61
-
62
- ## Common steps (apply once, regardless of selection)
63
-
64
- ### C1 — Abstraction package version
65
-
66
- **File:** `Directory.Packages.props` (versions live here only — see [[dependency-policy]])
67
-
68
- ```xml
69
- <ItemGroup>
70
- <!-- AppWeaver — Email (abstraction) -->
71
- <PackageVersion Include="AppWeaver.Messaging.Email" Version="0.1.3" />
72
- <!-- + one <PackageVersion> per selected implementation — see each block below -->
73
- </ItemGroup>
74
- ```
75
-
76
- ### C2 — Project references
77
-
78
- **File:** `src/<HostProject>/<HostProject>.csproj` (the host project resolved in step 2 — single
79
- project, no separate Application/Infrastructure). Add the abstraction **and** every selected
80
- implementation's package here (no `Version=` — versions live in `Directory.Packages.props`, C1).
81
-
82
- ```xml
83
- <ItemGroup>
84
- <PackageReference Include="AppWeaver.Messaging.Email" /> <!-- no Version= -->
85
- <!-- + each selected implementation’s package — see its block below -->
86
- </ItemGroup>
87
- ```
88
-
89
- ### C3 — Registration scaffold
90
-
91
- **File:** the host project `<HostProject>` (step 2) — add the registration in its
92
- `Extensions/InfrastructureServiceRegistration.cs` (`AddInfrastructure`) when that file exists (it does
93
- in the Vertical Slice / Modular Monolith foundations), otherwise directly in `Program.cs` **before**
94
- `builder.Build()`. (`configuration` below = the `IConfiguration` in scope: `config` inside
95
- `AddInfrastructure`, `builder.Configuration` in `Program.cs`.)
96
-
97
- Add the base call; each selected implementation contributes one `builder.Use…(…)` line (C4).
98
-
99
- ```csharp
100
- using AppWeaver.Messaging.Email.Extensions;
101
- // + one using per selected implementation (see its block)
102
-
103
- services.AddEmailMessaging(
104
- configureBuilder: builder =>
105
- {
106
- // ⬇ one Use…() call per selected implementation is inserted here
107
- },
108
- configureOptions: options => options.Provider = "<default key>"); // step 6
109
- ```
110
-
111
- > **Dual local/Aspire startup ([[components-readme]] R0.2a).** If the host project also has an
112
- > `AddAspireInfrastructure` method (`Extensions/AspireInfrastructureExtensions.cs`) selected by
113
- > `if (isAspire) … else …` in `Program.cs`, add this **same** `AddEmailMessaging(...)` block to
114
- > **both** `AddInfrastructure` and `AddAspireInfrastructure`. The Email registration is DB-agnostic, so
115
- > it is identical in both methods — copy it verbatim. Registering in only one leaves `IEmailService`
116
- > unresolvable (throws at runtime) in the other run mode.
117
-
118
- ### C4 — Base appsettings block
119
-
120
- **File:** `src/<HostProject>/appsettings.json` (the host project resolved in step 2)
121
-
122
- ```jsonc
123
- {
124
- "Messaging": {
125
- "Email": {
126
- "DefaultFrom": "no-reply@your-domain.com",
127
- "AdminEmail": "admin@your-domain.com"
128
- }
129
- }
130
- }
131
- ```
132
-
133
- ---
134
-
135
- ## Implementation blocks
136
-
137
- For each implementation the user selected, do all three sub-steps: **package**, **registration
138
- line**, **config**. Secrets MUST be supplied via user-secrets / env / key vault — never committed.
139
-
140
- ### MsGraph
141
- ```xml
142
- <!-- Directory.Packages.props --> <PackageVersion Include="AppWeaver.Messaging.Email.MsGraph" Version="0.1.2" />
143
- <!-- host .csproj --> <PackageReference Include="AppWeaver.Messaging.Email.MsGraph" />
144
- ```
145
- ```csharp
146
- using AppWeaver.Messaging.Email.MsGraph.Extensions;
147
- // inside AddEmailMessaging builder:
148
- builder.UseMsGraph(o =>
149
- {
150
- o.TenantId = configuration["Messaging.Email.MsGraph:TenantId"];
151
- o.ClientId = configuration["Messaging.Email.MsGraph:ClientId"];
152
- o.ClientSecret = configuration["Messaging.Email.MsGraph:ClientSecret"]; // user-secrets
153
- o.DefaultFrom = configuration["Messaging:Email:DefaultFrom"];
154
- });
155
- ```
156
- ```jsonc
157
- // appsettings.json — secret set out-of-band:
158
- // dotnet user-secrets set "Messaging.Email.MsGraph:ClientSecret" "<secret>"
159
- "Messaging.Email.MsGraph": { "TenantId": "<guid>", "ClientId": "<guid>", "DefaultFrom": "no-reply@your-domain.com" }
160
- ```
161
-
162
- ### Smtp
163
- ```xml
164
- <PackageVersion Include="AppWeaver.Messaging.Email.Smtp" Version="0.1.3" />
165
- <PackageReference Include="AppWeaver.Messaging.Email.Smtp" />
166
- ```
167
- ```csharp
168
- using AppWeaver.Messaging.Email.Smtp.Extensions;
169
- builder.UseSmtp(o =>
170
- {
171
- o.Host = configuration["Messaging.Email.Smtp:Host"]!;
172
- o.Port = int.Parse(configuration["Messaging.Email.Smtp:Port"] ?? "587");
173
- o.Username = configuration["Messaging.Email.Smtp:Username"]!;
174
- o.Password = configuration["Messaging.Email.Smtp:Password"]!; // user-secrets
175
- o.EnableSsl = true;
176
- o.DefaultFrom = configuration["Messaging:Email:DefaultFrom"];
177
- });
178
- ```
179
- ```jsonc
180
- "Messaging.Email.Smtp": { "Host": "smtp.example.com", "Port": 587, "Username": "<user>", "EnableSsl": true }
181
- ```
182
- > Note: the `SendAsync(from,to,…)` convenience overload of the SMTP service writes to a file; the
183
- > `EmailMessage` overload performs the real SMTP send. Use `SendAsync(EmailMessage, ct)` for delivery.
184
-
185
- ### SendGrid
186
- ```xml
187
- <PackageVersion Include="AppWeaver.Messaging.Email.SendGrid" Version="0.1.2" />
188
- <PackageReference Include="AppWeaver.Messaging.Email.SendGrid" />
189
- ```
190
- ```csharp
191
- using AppWeaver.Messaging.Email.SendGrid.Extensions;
192
- builder.UseSendGrid(o =>
193
- {
194
- o.ApiKey = configuration["Messaging.Email.SendGrid:ApiKey"]!; // [Required] — user-secrets
195
- o.DefaultFrom = configuration["Messaging:Email:DefaultFrom"];
196
- });
197
- ```
198
- > `ApiKey` is `[Required]` and validates on startup — a missing key fails app boot, not the first send.
199
-
200
- ### OAuth2
201
- ```xml
202
- <PackageVersion Include="AppWeaver.Messaging.Email.OAuth2" Version="0.1.3" />
203
- <PackageReference Include="AppWeaver.Messaging.Email.OAuth2" />
204
- ```
205
- ```csharp
206
- using AppWeaver.Messaging.Email.OAuth2.Extensions;
207
- builder.UseOAuth2(o =>
208
- {
209
- o.TenantId = configuration["Messaging.Email.OAuth2:TenantId"];
210
- o.ClientId = configuration["Messaging.Email.OAuth2:ClientId"];
211
- o.ClientSecret = configuration["Messaging.Email.OAuth2:ClientSecret"]; // user-secrets
212
- o.Host = "smtp.office365.com"; o.Port = 587;
213
- o.DefaultFrom = configuration["Messaging:Email:DefaultFrom"];
214
- });
215
- ```
216
-
217
- ### File
218
- ```xml
219
- <PackageVersion Include="AppWeaver.Messaging.Email.File" Version="0.1.2" />
220
- <PackageReference Include="AppWeaver.Messaging.Email.File" />
221
- ```
222
- ```csharp
223
- using AppWeaver.Messaging.Email.File.Extensions;
224
- builder.UseFile(o => o.DefaultFrom = configuration["Messaging:Email:DefaultFrom"]);
225
- ```
226
- > Pass `DefaultFrom` **inline** as shown. The File provider’s config binder reads the
227
- > `Messaging.Email.Smtp` section (a known quirk), so do not rely on a `Messaging.Email.File` config
228
- > section. Output is written to `Emails_<yyyy-MM-dd>.txt` in `AppContext.BaseDirectory`.
229
-
230
- ---
231
-
232
- ## Usage (inject the abstraction in a feature handler)
233
-
234
- Email is a side effect of a business operation — call it from the Application command handler that
235
- owns the operation (see [[cqrs]], [[vertical-slices]]). Inject **`IEmailService`**, never a provider.
236
-
237
- ```csharp
238
- using AppWeaver.Messaging.Email;
239
-
240
- public class CreateUserCommandHandler(IEmailService _email, IConfiguration _config /* … */)
241
- : ICommandHandler<CreateUserCommand, Result<UserDto>>
242
- {
243
- public async ValueTask<Result<UserDto>> Handle(CreateUserCommand req, CancellationToken ct)
244
- {
245
- // … persist aggregate …
246
- var from = _config["Messaging:Email:DefaultFrom"]!;
247
- var to = _config["Messaging:Email:AdminEmail"]!;
248
- await _email.SendAsync(from, to, $"New user: {req.Name}", "<p>Created.</p>", ct: ct);
249
- return Result<UserDto>.Ok(/* dto */);
250
- }
251
- }
252
- ```
253
-
254
- Multi-recipient / attachments — use the `EmailMessage` overload (To/Cc/Bcc + `EmailAttachment`,
255
- see [README.md](./README.md)).
256
-
257
- ---
258
-
259
- ## Sample reference endpoint
260
-
261
- A ready-to-run FastEndpoint is bundled in this component folder at
262
- [`ReferenceEndpoints/SendEmailEndpoint.cs`](./ReferenceEndpoints/SendEmailEndpoint.cs). It injects
263
- `IEmailService` directly and performs the **full send inline** — no command/handler/mediator — purely
264
- as a copy-paste sample. It is **not** wired automatically; it is offered in workflow step 8.
265
-
266
- **When the user opts in (step 8) — copy procedure:**
267
-
268
- 1. Confirm `docs/components/Email/ReferenceEndpoints/SendEmailEndpoint.cs` exists. If it is missing,
269
- tell the user and skip (do not hand-write a replacement).
270
- 2. Create `src/<HostProject>/ReferenceEndpoints/` if it does not exist (`<HostProject>` = the project
271
- resolved in step 2).
272
- 3. Copy the file to `src/<HostProject>/ReferenceEndpoints/SendEmailEndpoint.cs`, replacing the
273
- namespace token `__HOSTPROJECT__` with `<HostProject>`'s root namespace (e.g.
274
- `AppWeaver.Health.Service`). Change nothing else.
275
- 4. Do **not** touch the C1–C4 wiring. FastEndpoints auto-discovers the endpoint by assembly scan, so
276
- no extra registration is required.
277
-
278
- **Endpoint:** `POST /reference/email/send` — body `{ "to", "subject", "body", "isHtml"?, "from"? }`.
279
- `from` falls back to `Messaging:Email:DefaultFrom`. With the `File` provider the message is written
280
- to `Emails_<yyyy-MM-dd>.txt` in the app base directory (no real mail is sent).
281
-
282
- ---
283
-
284
- ## Verification
285
-
286
- 1. **Builds** — `dotnet build` succeeds; no `Version=` on package references.
287
- 2. **Startup validates** — app boots; options `ValidateOnStart()` passes; `options.Provider` equals
288
- one of the selected keys.
289
- 3. **Resolves** — `IEmailService` resolves from DI.
290
- 4. **Sends** — a send succeeds; for `File`, confirm `Emails_<yyyy-MM-dd>.txt` appears in the app
291
- base directory.
292
- 5. **No secrets in diff** — `git diff` shows no secret / API key / password literals.
293
- 6. **Sample endpoint (only if copied in step 8)** — the build still succeeds with
294
- `ReferenceEndpoints/SendEmailEndpoint.cs` added, the `__HOSTPROJECT__` token was replaced, and
295
- `POST /reference/email/send` is discoverable.
296
-
297
- ## Related
298
-
299
- - [README.md](./README.md) · [[components-readme]] · [[cqrs]] · [[clean-architecture]] ·
300
- [[vertical-slices]] · [[dependency-policy]] · [[ai-rules]]
@@ -1,113 +0,0 @@
1
- # Jobs Component (`AppWeaver.Jobs`)
2
-
3
- > Pure technology (domain-agnostic). Reference sheet for the background-job component.
4
- > To wire it into a project, follow the agent runbook in [integration.md](./integration.md).
5
- > Commands: `/aw-spec-plan`, `/aw-spec-tasks`, `/aw-spec-implement`.
6
-
7
- ## What it is
8
-
9
- A plug-and-play background-job framework. Application code depends only on the `IJobScheduler`
10
- and `IJobProgress` abstractions; the engine (Quartz.NET, optionally with EF Core status
11
- persistence) is a single registration decision. Supports **fire-and-forget**, **delayed**, and
12
- **recurring (CRON)** jobs with progress tracking, status polling, and cooperative cancellation.
13
-
14
- **Use when:** work must run outside the request thread — exports, batch processing, archival,
15
- report generation, scheduled maintenance, sending bulk notifications.
16
-
17
- ## Public contract (`AppWeaver.Jobs.Abstractions`)
18
-
19
- ```csharp
20
- public interface IJob { } // marker — implement on every job class
21
-
22
- public interface IJobScheduler
23
- {
24
- Task<string> EnqueueAsync<TJob>(Expression<Func<TJob, Task>> jobMethod,
25
- string? description = null, string? userId = null) where TJob : class, IJob;
26
-
27
- Task<string> ScheduleAsync<TJob>(Expression<Func<TJob, Task>> jobMethod, TimeSpan delay,
28
- string? description = null, string? userId = null) where TJob : class, IJob;
29
-
30
- Task AddOrUpdateRecurringAsync<TJob>(string jobId,
31
- Expression<Func<TJob, Task>> jobMethod, string cronExpression) where TJob : class, IJob;
32
-
33
- Task RemoveRecurringAsync(string jobId);
34
- Task<JobStatus?> GetStatusAsync(string jobId);
35
- Task<IEnumerable<JobStatus>> GetStatusesAsync(string userId, DateTime? from = null, DateTime? to = null);
36
- Task CancelAsync(string jobId);
37
- }
38
-
39
- public interface IJobProgress
40
- {
41
- Task ReportProgressAsync(int percent, string? message = null, object? progressDetails = null);
42
- Task ReportResultUrlAsync(string url);
43
- CancellationToken CancellationToken { get; } // check inside loops for cooperative cancel
44
- }
45
- ```
46
-
47
- `JobStatus` carries: `JobId`, `State`, `CreatedAt`, `StartedAt`, `CompletedAt`, `ProgressPercent`,
48
- `ProgressMessage`, `ResultUrl`, `ErrorMessage`, `Description`, `UserId`, `ProgressDetails`.
49
- `JobState` = `Pending → Running → Completed | Failed | Cancelled`.
50
-
51
- ## Packages
52
-
53
- | Package | Version | Role | Builder method |
54
- |---|---|---|---|
55
- | `AppWeaver.Jobs` | `0.1.1` | **Required.** Abstractions + `AddAppWeaverJobs` + facade. | — |
56
- | `AppWeaver.Jobs.Quartz` | `0.1.0` | **Recommended engine.** Quartz.NET scheduler; in-memory or persistent store. | `UseQuartzJobScheduler` |
57
- | `AppWeaver.Jobs.Persistence.EfCore` | `0.1.0` | EF Core persistence of `JobStatus`. | `UseEfCoreJobs` + `UseJobDatabase` / `UseAppWeaverDataStore` |
58
- | `AppWeaver.Jobs.InMemory` | `0.1.0` | ⚠️ **Work in progress — not functional. Do not use.** | — |
59
-
60
- **Recommended stack:** `AppWeaver.Jobs` + `AppWeaver.Jobs.Quartz` + `AppWeaver.Jobs.Persistence.EfCore`.
61
-
62
- ## Registration shapes
63
-
64
- ```csharp
65
- // In AppWeaver.Jobs.Extensions
66
- IComponentBuilder<IJobScheduler> AddAppWeaverJobs(
67
- this IServiceCollection services,
68
- Action<IComponentBuilder<IJobScheduler>> configureBuilder, // required
69
- Action<JobsConfiguration>? configureOptions = null, // set Provider = "Quartz"
70
- IConfiguration? configuration = null); // OR bind "AppWeaver:Jobs"
71
-
72
- // In AppWeaver.Jobs.Quartz.Extensions
73
- builder.UseQuartzJobScheduler(opts => { opts.MaxConcurrency = 10; opts.ConfigureQuartz = q => { … }; });
74
-
75
- // In AppWeaver.Jobs.Persistence.EfCore.Extensions (choose ONE store wiring)
76
- builder.UseEfCoreJobs().UseJobDatabase(opts => opts.UseSqlite("Data Source=jobs.db")); // standalone
77
- builder.UseEfCoreJobs().UseAppWeaverDataStore(); // shared DataStore
78
- ```
79
-
80
- `JobsConfiguration.Provider` (config section `AppWeaver:Jobs`) selects the engine key; for Quartz
81
- it is `"Quartz"`. `UseQuartzJobScheduler` registers `IJobProgress` (`QuartzJobProgress`) and a
82
- Quartz hosted service automatically.
83
-
84
- ## Persistence modes (EF Core)
85
-
86
- | Mode | Wiring | DbContext | Migration |
87
- |---|---|---|---|
88
- | **Standalone** | `.UseJobDatabase(opts => opts.UseXxx(conn))` | dedicated `JobStatusDbContext` | own migration / `EnsureCreated` |
89
- | **Shared DataStore** | `.UseAppWeaverDataStore()` (after `AddDataStore().UseEfCoreDataStore()`) | shares DataStore connection | Shared: in `SharedComponentDbContext` migration; Isolated: own migration |
90
-
91
- The Quartz **persistent store** (for distributed/durable scheduling) is configured separately via
92
- `opts.ConfigureQuartz = q => q.UsePersistentStore(...)` and needs the Quartz DDL applied to the DB.
93
- With no `ConfigureQuartz`, Quartz uses an in-memory store (single instance, lost on restart).
94
-
95
- ## Source map (framework)
96
-
97
- ```
98
- framework/Jobs/
99
- README.md ← full component manual (authoritative deep-dive)
100
- src/
101
- AppWeaver.Jobs/ ← IJob, IJobScheduler, IJobProgress, JobStatus, AddAppWeaverJobs
102
- AppWeaver.Jobs.Quartz/ ← UseQuartzJobScheduler + QuartzJobScheduler/Wrapper/Progress
103
- AppWeaver.Jobs.Persistence.EfCore/ ← UseEfCoreJobs/UseJobDatabase/UseAppWeaverDataStore
104
- AppWeaver.Jobs.InMemory/ ← ⚠️ WIP, not functional
105
- tests/
106
- AppWeaver.Jobs.Tests/ ← DefaultJobSchedulerTests
107
- TestApp/ ← Quartz + EF Core end-to-end scenarios (A–O)
108
- ```
109
-
110
- ## Related
111
-
112
- - [integration.md](./integration.md) · [[components-readme]] · [[cqrs]] ·
113
- [[persistence]] · [[clean-architecture]] · [[dependency-policy]]
@@ -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]]