rapidkit 0.40.1 → 0.41.1
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/README.md +48 -7
- package/contracts/agent-customization-pack.v1.json +52 -2
- package/contracts/extension-cli-compatibility.v1.json +14 -2
- package/contracts/runtime-command-surface.v1.json +7 -1
- package/contracts/workspace-intelligence/agent-action-outcome.v1.json +38 -0
- package/contracts/workspace-intelligence/blocker-resolution.v1.json +65 -0
- package/contracts/workspace-intelligence/doctor-fix-result.v1.json +34 -0
- package/contracts/workspace-intelligence/studio-blocker-handoff.v1.json +91 -0
- package/contracts/workspace-intelligence/workspace-contract-verify.v1.json +51 -0
- package/contracts/workspace-intelligence/workspace-dependency-graph.v1.json +61 -1
- package/contracts/workspace-intelligence/workspace-explain.v1.json +31 -0
- package/contracts/workspace-intelligence/workspace-intelligence-history.v1.json +36 -0
- package/contracts/workspace-intelligence/workspace-operational-skill.v1.json +37 -0
- package/contracts/workspace-intelligence/workspace-skills-index.v1.json +27 -0
- package/dist/analyze-QYHMGLSG.js +1 -0
- package/dist/autopilot-release-AHMQEUFH.js +1 -0
- package/dist/chunk-33LR2QEM.js +2 -0
- package/dist/chunk-3PTJID76.js +2 -0
- package/dist/chunk-46AGNYI7.js +50 -0
- package/dist/chunk-64RTZBHU.js +2 -0
- package/dist/chunk-AQ4XZZC6.js +1 -0
- package/dist/{chunk-RXWM5DSC.js → chunk-BFEBZABL.js} +3 -3
- package/dist/{chunk-3YLMCP3V.js → chunk-CDCYRBAY.js} +1 -1
- package/dist/chunk-CDPR2YKL.js +13 -0
- package/dist/chunk-CKXJR3YT.js +7 -0
- package/dist/chunk-E5ZVQL3C.js +13 -0
- package/dist/chunk-ELU3G6DQ.js +9 -0
- package/dist/chunk-EN6YCX36.js +1 -0
- package/dist/chunk-FMBSON6H.js +33 -0
- package/dist/chunk-GBJBQ43T.js +1 -0
- package/dist/chunk-ICGWHIMK.js +1 -0
- package/dist/{chunk-4FJQWL7P.js → chunk-ITJ6RKUW.js} +3 -3
- package/dist/{workspace-graph-ICB7OVAZ.js → chunk-JEI6BTZI.js} +1 -1
- package/dist/{chunk-G76C74EV.js → chunk-JU3VNLTY.js} +1 -1
- package/dist/chunk-JW2FSKT3.js +2 -0
- package/dist/chunk-KIUSCFHF.js +1 -0
- package/dist/chunk-LKX3L7TE.js +2 -0
- package/dist/chunk-MIWDCR6I.js +2 -0
- package/dist/{chunk-6G2KSHP6.js → chunk-OLDPVVSV.js} +1 -1
- package/dist/{chunk-4Q2ZZKGB.js → chunk-PCXSTKZ5.js} +1 -1
- package/dist/{chunk-6KD5F6LX.js → chunk-Q2KZIBV4.js} +1 -1
- package/dist/{chunk-ERCD6NFF.js → chunk-RSYUNEH7.js} +13 -13
- package/dist/chunk-TJN7G2MA.js +1 -0
- package/dist/chunk-UQR6G7KH.js +32 -0
- package/dist/chunk-VMJA36WD.js +1 -0
- package/dist/chunk-WRMCPKGA.js +1 -0
- package/dist/{create-XVDDQA42.js → create-RNP5ACQL.js} +1 -1
- package/dist/demo-kit-N5U3NGAE.js +149 -0
- package/dist/{doctor-UOLOGJ2Z.js → doctor-XM6QDTDC.js} +1 -1
- package/dist/{dotnet-webapi-clean-RTBRPDPL.js → dotnet-webapi-clean-K33C77EI.js} +1 -1
- package/dist/{gofiber-standard-UGIRKPKL.js → gofiber-standard-BQ4HCXL2.js} +1 -1
- package/dist/{gogin-standard-HJ7SPFNT.js → gogin-standard-PUBCYW3A.js} +1 -1
- package/dist/index.d.ts +45 -7
- package/dist/index.js +145 -127
- package/dist/{pipeline-XK62WL4D.js → pipeline-DH6Z47O4.js} +1 -1
- package/dist/platform-capabilities-TSLK667K.js +1 -0
- package/dist/{pythonRapidkitExec-MNWRC4F2.js → pythonRapidkitExec-SGKW76XM.js} +1 -1
- package/dist/{springboot-standard-IWJSVDLZ.js → springboot-standard-XFVQI37R.js} +1 -1
- package/dist/{workspace-L4ITCKMM.js → workspace-E554C5SM.js} +1 -1
- package/dist/workspace-agent-sync-2HRPM5ZD.js +1 -0
- package/dist/{workspace-context-NMMQMHNU.js → workspace-context-VJTXW3K4.js} +1 -1
- package/dist/workspace-contract-OO4GMENV.js +1 -0
- package/dist/workspace-explain-3WSJLIJ6.js +1 -0
- package/dist/workspace-explain-contract-24RQ7KIW.js +1 -0
- package/dist/workspace-feedback-65NR3EZH.js +1 -0
- package/dist/{workspace-foundation-HNIRAIBF.js → workspace-foundation-LISDH53T.js} +1 -1
- package/dist/workspace-graph-2A5THUCI.js +1 -0
- package/dist/workspace-history-VPDADQKG.js +1 -0
- package/dist/{workspace-intelligence-64IWAYHS.js → workspace-intelligence-E3KXEZCM.js} +1 -1
- package/dist/workspace-mcp-serve-RFYDCA2L.js +3 -0
- package/dist/workspace-model-YL7W3573.js +1 -0
- package/dist/workspace-registry-summary-X5WRUU3T.js +1 -0
- package/dist/workspace-run-GCIQD73R.js +1 -0
- package/dist/workspace-verify-NRYH7RNB.js +1 -0
- package/dist/workspace-watch-H2AETGFI.js +1 -0
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/OPEN_SOURCE_USER_SCENARIOS.md +1 -1
- package/docs/README.md +1 -1
- package/docs/commands-reference.md +10 -2
- package/docs/contracts/ARTIFACT_CATALOG.md +3 -1
- package/docs/contracts/NAMING_AND_COEXISTENCE.md +58 -0
- package/docs/workspace-run.md +25 -1
- package/package.json +7 -3
- package/scripts/enterprise-package-smoke.mjs +427 -0
- package/scripts/prepack-enterprise.mjs +40 -0
- package/templates/generator.js +175 -0
- package/templates/kits/fastapi-ddd/README.md.j2 +122 -0
- package/templates/kits/fastapi-ddd/common/env.example.j2 +10 -0
- package/templates/kits/fastapi-ddd/env.example.j2 +1 -0
- package/templates/kits/fastapi-ddd/pyproject.toml.j2 +64 -0
- package/templates/kits/fastapi-ddd/src/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-ddd/src/app/__init__.py.j2 +11 -0
- package/templates/kits/fastapi-ddd/src/app/application/__init__.py.j2 +5 -0
- package/templates/kits/fastapi-ddd/src/app/application/interfaces.py.j2 +43 -0
- package/templates/kits/fastapi-ddd/src/app/application/use_cases/__init__.py.j2 +6 -0
- package/templates/kits/fastapi-ddd/src/app/application/use_cases/health.py.j2 +14 -0
- package/templates/kits/fastapi-ddd/src/app/application/use_cases/notes.py.j2 +24 -0
- package/templates/kits/fastapi-ddd/src/app/config/__init__.py.j2 +16 -0
- package/templates/kits/fastapi-ddd/src/app/domain/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-ddd/src/app/domain/models/__init__.py.j2 +6 -0
- package/templates/kits/fastapi-ddd/src/app/domain/models/health.py.j2 +16 -0
- package/templates/kits/fastapi-ddd/src/app/domain/models/note.py.j2 +27 -0
- package/templates/kits/fastapi-ddd/src/app/infrastructure/__init__.py.j2 +5 -0
- package/templates/kits/fastapi-ddd/src/app/infrastructure/repositories/__init__.py.j2 +6 -0
- package/templates/kits/fastapi-ddd/src/app/infrastructure/repositories/health.py.j2 +17 -0
- package/templates/kits/fastapi-ddd/src/app/infrastructure/repositories/notes.py.j2 +28 -0
- package/templates/kits/fastapi-ddd/src/app/main.py.j2 +61 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/__init__.py.j2 +5 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/dependencies/__init__.py.j2 +19 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/router.py.j2 +10 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/routes/__init__.py.j2 +5 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/routes/health.py.j2 +27 -0
- package/templates/kits/fastapi-ddd/src/app/presentation/api/routes/notes.py.j2 +50 -0
- package/templates/kits/fastapi-ddd/src/app/shared/__init__.py.j2 +5 -0
- package/templates/kits/fastapi-ddd/src/app/shared/result.py.j2 +28 -0
- package/templates/kits/fastapi-ddd/src/cli.py.j2 +167 -0
- package/templates/kits/fastapi-ddd/src/main.py.j2 +35 -0
- package/templates/kits/fastapi-ddd/src/modules/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-ddd/src/routing/__init__.py.j2 +13 -0
- package/templates/kits/fastapi-ddd/src/routing/health.py.j2 +7 -0
- package/templates/kits/fastapi-ddd/src/routing/notes.py.j2 +7 -0
- package/templates/kits/fastapi-ddd/tests/__init__.py.j2 +1 -0
- package/templates/kits/fastapi-ddd/tests/test_app_factory.py.j2 +22 -0
- package/templates/kits/fastapi-ddd/tests/test_health.py.j2 +17 -0
- package/templates/kits/fastapi-ddd/tests/test_notes.py.j2 +27 -0
- package/templates/kits/fastapi-standard/README.md.j2 +145 -0
- package/templates/kits/fastapi-standard/common/env.example.j2 +10 -0
- package/templates/kits/fastapi-standard/env.example.j2 +1 -0
- package/templates/kits/fastapi-standard/pyproject.toml.j2 +64 -0
- package/templates/kits/fastapi-standard/src/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-standard/src/cli.py.j2 +168 -0
- package/templates/kits/fastapi-standard/src/main.py.j2 +66 -0
- package/templates/kits/fastapi-standard/src/modules/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-standard/src/routing/__init__.py.j2 +16 -0
- package/templates/kits/fastapi-standard/src/routing/examples.py.j2 +71 -0
- package/templates/kits/fastapi-standard/src/routing/health.py.j2 +22 -0
- package/templates/kits/fastapi-standard/tests/__init__.py.j2 +1 -0
- package/templates/kits/fastapi-standard/tests/test_examples.py.j2 +29 -0
- package/templates/kits/fastapi-standard/tests/test_health.py.j2 +17 -0
- package/templates/kits/nestjs-standard/Dockerfile.j2 +41 -0
- package/templates/kits/nestjs-standard/README.md.j2 +139 -0
- package/templates/kits/nestjs-standard/docker-compose.yml.j2 +94 -0
- package/templates/kits/nestjs-standard/docs/README.md.j2 +15 -0
- package/templates/kits/nestjs-standard/env.example.j2 +18 -0
- package/templates/kits/nestjs-standard/eslint.config.cjs.j2 +9 -0
- package/templates/kits/nestjs-standard/jest.config.ts.j2 +22 -0
- package/templates/kits/nestjs-standard/nest-cli.json.j2 +10 -0
- package/templates/kits/nestjs-standard/package.json.j2 +101 -0
- package/templates/kits/nestjs-standard/src/app.controller.ts.j2 +14 -0
- package/templates/kits/nestjs-standard/src/app.module.ts.j2 +26 -0
- package/templates/kits/nestjs-standard/src/app.service.ts.j2 +16 -0
- package/templates/kits/nestjs-standard/src/auth/auth.controller.ts.j2 +20 -0
- package/templates/kits/nestjs-standard/src/auth/auth.module.ts.j2 +13 -0
- package/templates/kits/nestjs-standard/src/auth/auth.service.ts.j2 +6 -0
- package/templates/kits/nestjs-standard/src/auth/entities/token.entity.ts.j2 +3 -0
- package/templates/kits/nestjs-standard/src/auth/entities/user.entity.ts.j2 +3 -0
- package/templates/kits/nestjs-standard/src/auth/entities/webauthn.entity.ts.j2 +3 -0
- package/templates/kits/nestjs-standard/src/config/configuration.ts.j2 +85 -0
- package/templates/kits/nestjs-standard/src/config/index.ts.j2 +2 -0
- package/templates/kits/nestjs-standard/src/config/validation.ts.j2 +21 -0
- package/templates/kits/nestjs-standard/src/examples/dto/create-note.dto.ts.j2 +11 -0
- package/templates/kits/nestjs-standard/src/examples/examples.controller.ts.j2 +24 -0
- package/templates/kits/nestjs-standard/src/examples/examples.module.ts.j2 +10 -0
- package/templates/kits/nestjs-standard/src/examples/examples.service.ts.j2 +33 -0
- package/templates/kits/nestjs-standard/src/main.ts.j2 +53 -0
- package/templates/kits/nestjs-standard/src/modules/index.ts.j2 +25 -0
- package/templates/kits/nestjs-standard/test/app.controller.spec.ts.j2 +24 -0
- package/templates/kits/nestjs-standard/test/app.e2e-spec.ts.j2 +60 -0
- package/templates/kits/nestjs-standard/test/examples.controller.spec.ts.j2 +28 -0
- package/templates/kits/nestjs-standard/test/jest-e2e.json.j2 +15 -0
- package/templates/kits/nestjs-standard/tsconfig.build.json.j2 +12 -0
- package/templates/kits/nestjs-standard/tsconfig.json.j2 +26 -0
- package/dist/analyze-RHQM4AB2.js +0 -1
- package/dist/autopilot-release-OJTLXPMX.js +0 -1
- package/dist/chunk-5VBRMLRU.js +0 -7
- package/dist/chunk-7VI4U7Q5.js +0 -2
- package/dist/chunk-FV5A3N3I.js +0 -2
- package/dist/chunk-GDGATWR5.js +0 -2
- package/dist/chunk-GOM3RFB3.js +0 -2
- package/dist/chunk-GX7UU7LL.js +0 -33
- package/dist/chunk-KYH364KQ.js +0 -1
- package/dist/chunk-OWNGSAO3.js +0 -2
- package/dist/chunk-QPEBI6AB.js +0 -2
- package/dist/chunk-TYZPPUBH.js +0 -1
- package/dist/chunk-VQMZC5TC.js +0 -9
- package/dist/chunk-WHCON2VN.js +0 -50
- package/dist/chunk-X7PWDIQW.js +0 -1
- package/dist/chunk-Z5LKRG57.js +0 -1
- package/dist/chunk-ZWKLRZE5.js +0 -13
- package/dist/demo-kit-RWGOEDW4.js +0 -141
- package/dist/workspace-agent-sync-G7JU77IK.js +0 -25
- package/dist/workspace-contract-D5O4OZD5.js +0 -1
- package/dist/workspace-history-LHUTLE3S.js +0 -1
- package/dist/workspace-model-SDHH5RBC.js +0 -1
- package/dist/workspace-registry-summary-MIPHVB56.js +0 -1
- package/dist/workspace-run-SPP32MPV.js +0 -1
- package/dist/workspace-verify-6Q6MGRG6.js +0 -1
- package/dist/workspace-watch-JDXVGW4H.js +0 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# {{ project_name }}
|
|
2
|
+
|
|
3
|
+
Domain-driven FastAPI service generated with [RapidKit](https://github.com/rapidkitlabs/rapidkit-core).
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Load the project-aware RapidKit CLI (adds .rapidkit/rapidkit to PATH)
|
|
9
|
+
source .rapidkit/activate
|
|
10
|
+
|
|
11
|
+
# Bootstrap dependencies (creates .venv + installs Poetry deps)
|
|
12
|
+
rapidkit init # use make init if you prefer a Make target
|
|
13
|
+
|
|
14
|
+
# Copy env templates and install hooks/tooling
|
|
15
|
+
./bootstrap.sh
|
|
16
|
+
|
|
17
|
+
# Type-check, lint, and test
|
|
18
|
+
make lint
|
|
19
|
+
make typecheck
|
|
20
|
+
make test
|
|
21
|
+
|
|
22
|
+
# Run the service
|
|
23
|
+
make dev
|
|
24
|
+
|
|
25
|
+
# Use the RapidKit CLI helpers when you prefer auto-detected environments
|
|
26
|
+
rapidkit dev
|
|
27
|
+
rapidkit lint
|
|
28
|
+
rapidkit test
|
|
29
|
+
|
|
30
|
+
# Run supply-chain and dependency audits
|
|
31
|
+
make audit
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
> Re-source `.rapidkit/activate` whenever you open a new shell so the project-local `rapidkit` launcher is always on your PATH.
|
|
35
|
+
|
|
36
|
+
> Re-run `rapidkit init` (or `make init`) when dependencies change. If you only need to refresh tooling/hooks, run `SKIP_INIT=1 make install` or rely on `./bootstrap.sh`.
|
|
37
|
+
|
|
38
|
+
> Lockfiles are generated automatically during scaffolding. Set `RAPIDKIT_SKIP_LOCKS=1` (or `RAPIDKIT_GENERATE_LOCKS=0`) before running `rapidkit create` if you want to skip the lockfile attempt.
|
|
39
|
+
|
|
40
|
+
> Need to see every RapidKit CLI command? Run `rapidkit --help` or check the CLI reference in the docs for the full catalog.
|
|
41
|
+
|
|
42
|
+
## Project structure
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
src/
|
|
46
|
+
app/
|
|
47
|
+
application/ # Use cases and service contracts
|
|
48
|
+
domain/ # Aggregates, entities, and value objects
|
|
49
|
+
infrastructure/ # Adapters and repositories
|
|
50
|
+
presentation/ # API routers and dependencies
|
|
51
|
+
shared/ # Cross-cutting primitives (Result, types, etc.)
|
|
52
|
+
routing/ # Glue for module-provided routers
|
|
53
|
+
modules/ # RapidKit module bootstrapping
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Layer overview
|
|
57
|
+
|
|
58
|
+
- **Domain** – Pure business rules (`src/app/domain`).
|
|
59
|
+
- **Application** – Orchestrates use-cases and enforces boundaries (`src/app/application`).
|
|
60
|
+
- **Infrastructure** – Implements gateways and repositories (`src/app/infrastructure`).
|
|
61
|
+
- **Presentation** – Exposes the API using FastAPI (`src/app/presentation`).
|
|
62
|
+
|
|
63
|
+
Modules added via `rapidkit add module ...` integrate automatically using the preserved
|
|
64
|
+
injection anchors inside `src/main.py` and `src/routing/__init__.py`.
|
|
65
|
+
|
|
66
|
+
## Example feature
|
|
67
|
+
|
|
68
|
+
An in-memory notes module is included under `/api/examples/notes` to demonstrate how the domain, application, infrastructure, and presentation layers collaborate:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Create a note
|
|
72
|
+
curl -s -X POST http://localhost:8000/api/examples/notes \
|
|
73
|
+
-H "Content-Type: application/json" \
|
|
74
|
+
-d '{"title":"design","body":"Describe your bounded context here."}'
|
|
75
|
+
|
|
76
|
+
# Retrieve the newly created note
|
|
77
|
+
curl -s http://localhost:8000/api/examples/notes/1 | jq
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Swap the in-memory repository with a RapidKit database module when you are ready for persistence.
|
|
81
|
+
|
|
82
|
+
## Deployment
|
|
83
|
+
|
|
84
|
+
- `Dockerfile` packages the service for container hosting with production-ready defaults.
|
|
85
|
+
- `docker-compose.yml` provisions the FastAPI app plus dependencies (for example PostgreSQL) for local production parity.
|
|
86
|
+
- Run `docker compose up --build` to start the stack; copy `.env.example` to `.env` to configure credentials before deploying.
|
|
87
|
+
- CI/CD workflows under `.github/workflows/` demonstrate automated deployment pipelines.
|
|
88
|
+
|
|
89
|
+
## Testing
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
rapidkit test
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The scaffold ships with a smoke test (`tests/test_app_factory.py`) that validates the
|
|
96
|
+
health endpoint and the application factory.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Local development
|
|
101
|
+
|
|
102
|
+
- `rapidkit init` bootstraps dependencies via the project-local CLI (run it after sourcing `.rapidkit/activate`).
|
|
103
|
+
- `make init` is an optional alias for `rapidkit init` when you prefer Make targets.
|
|
104
|
+
- `make install` re-runs `rapidkit init` unless `SKIP_INIT=1` is set and refreshes developer tooling such as pre-commit hooks.
|
|
105
|
+
- `./bootstrap.sh` copies `.env.example` to `.env` (when missing) and runs `SKIP_INIT=1 make install` for you.
|
|
106
|
+
- `make dev` or `rapidkit dev` starts the FastAPI server with hot-reload and ensures environment variables from `.env` are loaded.
|
|
107
|
+
- `make lint`, `make typecheck`, and `make test` (or `rapidkit lint` / `rapidkit test`) are thin wrappers around Ruff, mypy, and pytest for consistent CI parity.
|
|
108
|
+
- `make audit` runs `pip-audit` and `npm audit` to surface vulnerable dependencies early.
|
|
109
|
+
|
|
110
|
+
Pre-commit is configured with Ruff, Black, mypy, and hygiene hooks; `./bootstrap.sh` installs them automatically (or run `SKIP_INIT=1 make install`). Use the RapidKit CLI variants when you need it to auto-discover the right virtual environment inside automation scripts.
|
|
111
|
+
|
|
112
|
+
<!-- <<<inject:module-snippet>>> -->
|
|
113
|
+
|
|
114
|
+
## 📄 License
|
|
115
|
+
|
|
116
|
+
This project is licensed under the **{{ license }}** License - see the `LICENSE` file included at the project root for details.
|
|
117
|
+
|
|
118
|
+
## 🔒 Security & secrets
|
|
119
|
+
|
|
120
|
+
- Copy `.env.example` to `.env` and populate secrets (`SECRET_KEY`, DB credentials, etc.) before deploying.
|
|
121
|
+
- **Do not** commit real secrets or `.env` to git; `.gitignore` already contains `.env` to protect accidental commits.
|
|
122
|
+
- For production, tighten CORS and allowed hosts rather than using wildcard settings provided for convenience in dev.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{% include 'common/env.example.j2' %}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "{{ project_name }}"
|
|
3
|
+
version = "{{ app_version }}"
|
|
4
|
+
description = "{{ description }}"
|
|
5
|
+
authors = ["{{ author }} <you@example.com>"]
|
|
6
|
+
license = "{{ license }}"
|
|
7
|
+
packages = [{ include = "src" }]
|
|
8
|
+
|
|
9
|
+
[tool.poetry.scripts]
|
|
10
|
+
dev = "src.cli:dev"
|
|
11
|
+
start = "src.cli:start"
|
|
12
|
+
build = "src.cli:build"
|
|
13
|
+
test = "src.cli:test"
|
|
14
|
+
lint = "src.cli:lint"
|
|
15
|
+
format = "src.cli:format"
|
|
16
|
+
help = "src.cli:help_cmd"
|
|
17
|
+
|
|
18
|
+
[tool.poetry.dependencies]
|
|
19
|
+
python = "^{{ python_version }}"
|
|
20
|
+
fastapi = "^0.128.0"
|
|
21
|
+
uvicorn = { extras = ["standard"], version = "^0.40.0" }
|
|
22
|
+
pydantic = "^2.12.5"
|
|
23
|
+
{% if has_settings -%}
|
|
24
|
+
pydantic-settings = { version = "^2.12.0", extras = ["yaml"] }
|
|
25
|
+
{% endif %}
|
|
26
|
+
# <<<inject:poetry-dependencies>>>
|
|
27
|
+
|
|
28
|
+
[tool.poetry.group.dev.dependencies]
|
|
29
|
+
pytest = "^9.0.2"
|
|
30
|
+
pytest-asyncio = "^1.3.0"
|
|
31
|
+
pytest-cov = "^7.0.0"
|
|
32
|
+
httpx = "^0.28.1"
|
|
33
|
+
ruff = "^0.14.10"
|
|
34
|
+
black = "^25.12.0"
|
|
35
|
+
isort = "^7.0.0"
|
|
36
|
+
mypy = "^1.19.1"
|
|
37
|
+
build = "^1.3.0"
|
|
38
|
+
|
|
39
|
+
# <<<inject:poetry-dev-dependencies>>>
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = ["poetry-core"]
|
|
43
|
+
build-backend = "poetry.core.masonry.api"
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
markers = [
|
|
47
|
+
"integration: mark tests as integration tests",
|
|
48
|
+
"template_integration: mark template integration tests",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
line-length = 100
|
|
53
|
+
exclude = [".rapidkit/vendor"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
ignore = ["E501"]
|
|
57
|
+
|
|
58
|
+
[tool.mypy]
|
|
59
|
+
python_version = "{{ python_version }}"
|
|
60
|
+
ignore_missing_imports = true
|
|
61
|
+
warn_unused_configs = false
|
|
62
|
+
show_error_codes = true
|
|
63
|
+
# Ignore vendor snapshots and generated health shims from strict static checking
|
|
64
|
+
exclude = "(^\\.rapidkit/vendor/|src/health/|src/main.py$|src/app/)"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Contracts between the application layer and infrastructure adapters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
|
|
8
|
+
from src.app.domain.models import HealthStatus, Note, NoteDraft
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HealthStatusProvider(Protocol):
|
|
12
|
+
"""Protocol describing how to obtain the current health status."""
|
|
13
|
+
|
|
14
|
+
def get_status(self) -> HealthStatus:
|
|
15
|
+
"""Return the latest domain health status snapshot."""
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NoteRepository(Protocol):
|
|
20
|
+
"""Protocol for managing example notes."""
|
|
21
|
+
|
|
22
|
+
def create(self, draft: NoteDraft) -> Note:
|
|
23
|
+
"""Persist a note draft and return the resulting entity."""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
def list(self) -> list[Note]:
|
|
27
|
+
"""Return all available notes."""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def get(self, note_id: int) -> Note | None:
|
|
31
|
+
"""Return a single note or None if it does not exist."""
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(slots=True)
|
|
36
|
+
class ServiceContext:
|
|
37
|
+
"""Aggregates infrastructure adapters used by application use-cases."""
|
|
38
|
+
|
|
39
|
+
health_provider: HealthStatusProvider
|
|
40
|
+
note_repository: NoteRepository
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = ["HealthStatusProvider", "NoteRepository", "ServiceContext"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Example use-case exposing service health information."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from src.app.application.interfaces import ServiceContext
|
|
6
|
+
from src.app.domain.models import HealthStatus
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_service_health(context: ServiceContext) -> dict[str, str]:
|
|
10
|
+
"""Retrieve an immutable health snapshot for presentation layers."""
|
|
11
|
+
status = context.health_provider.get_status()
|
|
12
|
+
if isinstance(status, HealthStatus):
|
|
13
|
+
return status.to_dict()
|
|
14
|
+
return {"status": str(status)}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Application use-cases orchestrating the example notes feature."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from src.app.application.interfaces import ServiceContext
|
|
6
|
+
from src.app.domain.models import Note, NoteDraft
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def list_notes(context: ServiceContext) -> list[Note]:
|
|
10
|
+
"""Return every note currently stored."""
|
|
11
|
+
|
|
12
|
+
return context.note_repository.list()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_note(context: ServiceContext, draft: NoteDraft) -> Note:
|
|
16
|
+
"""Create a new note."""
|
|
17
|
+
|
|
18
|
+
return context.note_repository.create(draft)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_note(context: ServiceContext, note_id: int) -> Note | None:
|
|
22
|
+
"""Retrieve a single note by identifier."""
|
|
23
|
+
|
|
24
|
+
return context.note_repository.get(note_id)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Configuration primitives for the application layer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(slots=True)
|
|
9
|
+
class AppConfig:
|
|
10
|
+
"""Application-level configuration aggregated from RapidKit settings."""
|
|
11
|
+
|
|
12
|
+
environment: str = "development"
|
|
13
|
+
service_name: str = "{{ project_name }}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ["AppConfig"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Domain entity representing system health."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class HealthStatus:
|
|
10
|
+
"""Immutable representation of the service health state."""
|
|
11
|
+
|
|
12
|
+
status: str = "ok"
|
|
13
|
+
|
|
14
|
+
def to_dict(self) -> dict[str, str]:
|
|
15
|
+
"""Serialize the status for transport layers."""
|
|
16
|
+
return {"status": self.status}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Domain entity and value object for example notes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(slots=True)
|
|
9
|
+
class NoteDraft:
|
|
10
|
+
"""Value object describing the data needed to create a note."""
|
|
11
|
+
|
|
12
|
+
title: str
|
|
13
|
+
body: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(slots=True)
|
|
17
|
+
class Note:
|
|
18
|
+
"""Domain entity representing an immutable note."""
|
|
19
|
+
|
|
20
|
+
id: int
|
|
21
|
+
title: str
|
|
22
|
+
body: str
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> dict[str, str | int]:
|
|
25
|
+
"""Serialize the note for presentation layers."""
|
|
26
|
+
|
|
27
|
+
return {"id": self.id, "title": self.title, "body": self.body}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Infrastructure repository providing health status data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from src.app.domain.models import HealthStatus
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class StaticHealthRepository:
|
|
12
|
+
"""Simple repository returning a constant health status."""
|
|
13
|
+
|
|
14
|
+
status: str = "ok"
|
|
15
|
+
|
|
16
|
+
def get_status(self) -> HealthStatus:
|
|
17
|
+
return HealthStatus(status=self.status)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""In-memory repository backing the example notes use-case."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Dict, List
|
|
7
|
+
|
|
8
|
+
from src.app.domain.models import Note, NoteDraft
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(slots=True)
|
|
12
|
+
class InMemoryNoteRepository:
|
|
13
|
+
"""Very small repository storing notes in process memory."""
|
|
14
|
+
|
|
15
|
+
_store: Dict[int, Note] = field(default_factory=dict)
|
|
16
|
+
_next_id: int = 1
|
|
17
|
+
|
|
18
|
+
def create(self, draft: NoteDraft) -> Note:
|
|
19
|
+
note = Note(id=self._next_id, title=draft.title, body=draft.body)
|
|
20
|
+
self._store[self._next_id] = note
|
|
21
|
+
self._next_id += 1
|
|
22
|
+
return note
|
|
23
|
+
|
|
24
|
+
def list(self) -> List[Note]:
|
|
25
|
+
return list(self._store.values())
|
|
26
|
+
|
|
27
|
+
def get(self, note_id: int) -> Note | None:
|
|
28
|
+
return self._store.get(note_id)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Application factory wiring the FastAPI instance for DDD layers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncIterator, Callable
|
|
6
|
+
|
|
7
|
+
from fastapi import FastAPI
|
|
8
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
9
|
+
|
|
10
|
+
from src.routing import api_router as root_api_router
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
# canonical health package
|
|
14
|
+
from src.health import register_health_routes as _core_register_health_routes
|
|
15
|
+
except ImportError: # pragma: no cover - health package not generated yet
|
|
16
|
+
|
|
17
|
+
def _register_health_routes(_: FastAPI) -> None:
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
else:
|
|
21
|
+
|
|
22
|
+
def _register_health_routes(app: FastAPI) -> None:
|
|
23
|
+
try:
|
|
24
|
+
_core_register_health_routes(app)
|
|
25
|
+
except Exception: # pragma: no cover - defensive best-effort registration
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
LifespanHandler = Callable[[FastAPI], AsyncIterator[None]]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def create_app(
|
|
33
|
+
*,
|
|
34
|
+
title: str,
|
|
35
|
+
description: str,
|
|
36
|
+
version: str,
|
|
37
|
+
lifespan: LifespanHandler | None = None,
|
|
38
|
+
) -> FastAPI:
|
|
39
|
+
"""Build the FastAPI application configured with shared middlewares and routers."""
|
|
40
|
+
|
|
41
|
+
app = FastAPI(
|
|
42
|
+
title=title,
|
|
43
|
+
description=description,
|
|
44
|
+
version=version,
|
|
45
|
+
docs_url="/docs",
|
|
46
|
+
redoc_url="/redoc",
|
|
47
|
+
lifespan=lifespan,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
app.add_middleware(
|
|
51
|
+
CORSMiddleware,
|
|
52
|
+
allow_origins=["*"],
|
|
53
|
+
allow_credentials=True,
|
|
54
|
+
allow_methods=["*"],
|
|
55
|
+
allow_headers=["*"],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
app.include_router(root_api_router, prefix="/api")
|
|
59
|
+
_register_health_routes(app)
|
|
60
|
+
|
|
61
|
+
return app
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""FastAPI dependencies that wire infrastructure to application use-cases."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
|
|
7
|
+
from src.app.application.interfaces import ServiceContext
|
|
8
|
+
from src.app.infrastructure.repositories import InMemoryNoteRepository, StaticHealthRepository
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@lru_cache(maxsize=1)
|
|
12
|
+
def get_service_context() -> ServiceContext:
|
|
13
|
+
"""Return a cached service context for request handlers."""
|
|
14
|
+
health_repository = StaticHealthRepository()
|
|
15
|
+
note_repository = InMemoryNoteRepository()
|
|
16
|
+
return ServiceContext(health_provider=health_repository, note_repository=note_repository)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ["get_service_context"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Health endpoints bridging the DDD layers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from time import monotonic
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends
|
|
8
|
+
|
|
9
|
+
from src.app.application.interfaces import ServiceContext
|
|
10
|
+
from src.app.application.use_cases import get_service_health
|
|
11
|
+
from src.app.presentation.api.dependencies import get_service_context
|
|
12
|
+
|
|
13
|
+
router = APIRouter(tags=["health"])
|
|
14
|
+
|
|
15
|
+
_START_TIME = monotonic()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@router.get("/", summary="Health check")
|
|
19
|
+
async def heartbeat(
|
|
20
|
+
context: ServiceContext = Depends(get_service_context),
|
|
21
|
+
) -> dict[str, object]:
|
|
22
|
+
"""Return the current service health decal."""
|
|
23
|
+
payload: dict[str, object] = dict(get_service_health(context))
|
|
24
|
+
payload.setdefault("version", "{{ app_version }}")
|
|
25
|
+
payload.setdefault("uptime", monotonic() - _START_TIME)
|
|
26
|
+
payload.setdefault("module", "service")
|
|
27
|
+
return payload
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""HTTP handlers for the example notes feature."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from src.app.application.interfaces import ServiceContext
|
|
9
|
+
from src.app.application.use_cases import create_note, get_note, list_notes
|
|
10
|
+
from src.app.domain.models import NoteDraft
|
|
11
|
+
from src.app.presentation.api.dependencies import get_service_context
|
|
12
|
+
|
|
13
|
+
router = APIRouter(tags=["examples"])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NotePayload(BaseModel):
|
|
17
|
+
title: str = Field(..., max_length=80)
|
|
18
|
+
body: str = Field(..., max_length=500)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NoteResponse(NotePayload):
|
|
22
|
+
id: int
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.post("/notes", response_model=NoteResponse, status_code=status.HTTP_201_CREATED)
|
|
26
|
+
async def create_example_note(
|
|
27
|
+
payload: NotePayload,
|
|
28
|
+
context: ServiceContext = Depends(get_service_context),
|
|
29
|
+
) -> NoteResponse:
|
|
30
|
+
note = create_note(context, NoteDraft(title=payload.title, body=payload.body))
|
|
31
|
+
return NoteResponse(**note.to_dict())
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@router.get("/notes", response_model=list[NoteResponse])
|
|
35
|
+
async def list_example_notes(
|
|
36
|
+
context: ServiceContext = Depends(get_service_context),
|
|
37
|
+
) -> list[NoteResponse]:
|
|
38
|
+
notes = list_notes(context)
|
|
39
|
+
return [NoteResponse(**note.to_dict()) for note in notes]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@router.get("/notes/{note_id}", response_model=NoteResponse)
|
|
43
|
+
async def get_example_note(
|
|
44
|
+
note_id: int,
|
|
45
|
+
context: ServiceContext = Depends(get_service_context),
|
|
46
|
+
) -> NoteResponse:
|
|
47
|
+
note = get_note(context, note_id)
|
|
48
|
+
if not note:
|
|
49
|
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Note not found")
|
|
50
|
+
return NoteResponse(**note.to_dict())
|