workspai 0.43.0
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/LICENSE +21 -0
- package/README.md +624 -0
- package/contracts/agent-customization-pack.v1.json +229 -0
- package/contracts/analyze-last-run.v1.json +106 -0
- package/contracts/artifact-remediation-plan.v1.json +163 -0
- package/contracts/backend-import-stack-parity.snapshot.json +37 -0
- package/contracts/cli-log-event.v1.json +51 -0
- package/contracts/create-planner-capabilities.v1.json +251 -0
- package/contracts/doctor-project-evidence.v1.json +304 -0
- package/contracts/doctor-remediation-plan.v1.json +496 -0
- package/contracts/doctor-workspace-evidence.v1.json +315 -0
- package/contracts/extension-cli-compatibility.v1.json +26 -0
- package/contracts/infra-stack.v1.json +305 -0
- package/contracts/module-layout.v1.json +34 -0
- package/contracts/module-support.v1.json +20 -0
- package/contracts/pipeline-last-run.v1.json +80 -0
- package/contracts/release-readiness.v1.json +60 -0
- package/contracts/runtime-command-surface.v1.json +364 -0
- 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 +44 -0
- package/contracts/workspace-intelligence/fact-freshness.v1.json +58 -0
- package/contracts/workspace-intelligence/studio-blocker-handoff.v1.json +115 -0
- package/contracts/workspace-intelligence/workspace-context.v1.json +142 -0
- package/contracts/workspace-intelligence/workspace-contract-verify.v1.json +51 -0
- package/contracts/workspace-intelligence/workspace-dependency-graph.v1.json +152 -0
- package/contracts/workspace-intelligence/workspace-explain.v1.json +31 -0
- package/contracts/workspace-intelligence/workspace-impact.v1.json +114 -0
- package/contracts/workspace-intelligence/workspace-intelligence-history.v1.json +40 -0
- package/contracts/workspace-intelligence/workspace-model-diff.v1.json +105 -0
- package/contracts/workspace-intelligence/workspace-model-snapshot.v1.json +36 -0
- package/contracts/workspace-intelligence/workspace-model.v1.json +135 -0
- package/contracts/workspace-intelligence/workspace-operational-skill.v1.json +37 -0
- package/contracts/workspace-intelligence/workspace-skills-index.v1.json +27 -0
- package/contracts/workspace-intelligence/workspace-verify.v1.json +213 -0
- package/contracts/workspace-registry.v1.json +83 -0
- package/contracts/workspace-run-last.v1.json +112 -0
- package/data/modules-embeddings.json +16953 -0
- package/dist/analyze-5BU4QIPK.js +1 -0
- package/dist/artifact-remediation-plan-5GJWPPRS.js +3 -0
- package/dist/autopilot-release-QYUOVB26.js +1 -0
- package/dist/chunk-22DT744Z.js +1 -0
- package/dist/chunk-2E6WGUN6.js +1 -0
- package/dist/chunk-2FIX2MDC.js +1 -0
- package/dist/chunk-3KXW53QI.js +1 -0
- package/dist/chunk-3ZK2GU7C.js +1 -0
- package/dist/chunk-4KUIFXHM.js +5 -0
- package/dist/chunk-54XJNLYC.js +13 -0
- package/dist/chunk-5SWNSJGW.js +1 -0
- package/dist/chunk-5XATWNME.js +1 -0
- package/dist/chunk-753LOKFZ.js +2 -0
- package/dist/chunk-7YHK5NM3.js +2 -0
- package/dist/chunk-A5FBGRJA.js +1 -0
- package/dist/chunk-AAKUNWPR.js +78 -0
- package/dist/chunk-AFSZHLQT.js +1 -0
- package/dist/chunk-AQ4XZZC6.js +1 -0
- package/dist/chunk-CVTCPPEF.js +1 -0
- package/dist/chunk-DNAY5ALC.js +1 -0
- package/dist/chunk-EWYYLJQY.js +2 -0
- package/dist/chunk-GSWPGELT.js +2 -0
- package/dist/chunk-GZTYAEWX.js +1 -0
- package/dist/chunk-HTYMXMQM.js +1 -0
- package/dist/chunk-IBJEQHJR.js +205 -0
- package/dist/chunk-ICGWHIMK.js +1 -0
- package/dist/chunk-JEI6BTZI.js +3 -0
- package/dist/chunk-JHC6SCJC.js +1 -0
- package/dist/chunk-LO4C4VXR.js +4 -0
- package/dist/chunk-LPZFK3Y6.js +920 -0
- package/dist/chunk-LUP4U6EE.js +1 -0
- package/dist/chunk-LYC6AIF2.js +2 -0
- package/dist/chunk-LZQNVH7D.js +1 -0
- package/dist/chunk-NAAQMSO3.js +1 -0
- package/dist/chunk-NFUXULIF.js +1 -0
- package/dist/chunk-OCGZNSOE.js +1 -0
- package/dist/chunk-OJEWQO3Z.js +33 -0
- package/dist/chunk-OL64GWQC.js +9 -0
- package/dist/chunk-PI5KUXDA.js +7 -0
- package/dist/chunk-PYCJWW4B.js +1 -0
- package/dist/chunk-RE773WOX.js +13 -0
- package/dist/chunk-RO73VVHW.js +2 -0
- package/dist/chunk-S5KTATOU.js +1 -0
- package/dist/chunk-SI3AQKIP.js +1 -0
- package/dist/chunk-T3GJ7YVG.js +1 -0
- package/dist/chunk-T6OR6YOI.js +2 -0
- package/dist/chunk-VRDF2J46.js +653 -0
- package/dist/chunk-VZKSNUEE.js +2 -0
- package/dist/chunk-WU4ZRRA5.js +4 -0
- package/dist/chunk-XEJD4KCV.js +33 -0
- package/dist/chunk-XZGVNGRB.js +1 -0
- package/dist/chunk-Y45WZR5N.js +5 -0
- package/dist/create-WRZO4HK2.js +1 -0
- package/dist/demo-kit-AJ5LIM5G.js +149 -0
- package/dist/doctor-MF3M3FS3.js +1 -0
- package/dist/dotnet-webapi-clean-IPY3MCGQ.js +425 -0
- package/dist/gofiber-standard-N5N3QGH3.js +1397 -0
- package/dist/gogin-standard-LAWQTBWL.js +1459 -0
- package/dist/imported-projects-registry-FOIE27WT.js +1 -0
- package/dist/index.d.ts +141 -0
- package/dist/index.js +454 -0
- package/dist/managed-agent-markers-AXUM75OE.js +8 -0
- package/dist/module-layout-NZ43RSC5.js +1 -0
- package/dist/pipeline-QCUHFWDY.js +5 -0
- package/dist/platform-capabilities-XAVU4HSM.js +1 -0
- package/dist/pythonRapidkitExec-3L3XJBSY.js +1 -0
- package/dist/springboot-standard-OZYF7ZM7.js +697 -0
- package/dist/workspace-PR6VZRBG.js +1 -0
- package/dist/workspace-agent-sync-KTYF3HQR.js +1 -0
- package/dist/workspace-archive-GCZNI44O.js +3 -0
- package/dist/workspace-context-QUTOP2XV.js +1 -0
- package/dist/workspace-contract-XUGMCUSQ.js +1 -0
- package/dist/workspace-explain-JEONEGZZ.js +1 -0
- package/dist/workspace-explain-contract-DNS2YEL7.js +1 -0
- package/dist/workspace-feedback-R6H4UFZB.js +1 -0
- package/dist/workspace-foundation-KGSV4E6D.js +1 -0
- package/dist/workspace-graph-2A5THUCI.js +1 -0
- package/dist/workspace-history-CGCK2DJI.js +1 -0
- package/dist/workspace-intelligence-QJGVL37U.js +1 -0
- package/dist/workspace-marker-SMBC3Z2Q.js +1 -0
- package/dist/workspace-mcp-serve-AK4MYEHQ.js +3 -0
- package/dist/workspace-model-PNXXOGPO.js +1 -0
- package/dist/workspace-python-engine-state-MTWIIZPY.js +2 -0
- package/dist/workspace-registry-summary-RUJE6RPH.js +1 -0
- package/dist/workspace-run-Y7YMUIHX.js +1 -0
- package/dist/workspace-verify-WSDUUTR5.js +1 -0
- package/dist/workspace-watch-AGPOJ74Q.js +1 -0
- package/docs/AI_DYNAMIC_INTEGRATION.md +440 -0
- package/docs/AI_EXAMPLES.md +419 -0
- package/docs/AI_FEATURES.md +460 -0
- package/docs/AI_QUICKSTART.md +245 -0
- package/docs/DEVELOPMENT.md +90 -0
- package/docs/From Code to Shared Understanding.png +0 -0
- package/docs/OPEN_SOURCE_USER_SCENARIOS.md +170 -0
- package/docs/OPTIMIZATION_GUIDE.md +504 -0
- package/docs/PACKAGE_MANAGER_POLICY.md +25 -0
- package/docs/README.md +125 -0
- package/docs/SECURITY.md +63 -0
- package/docs/SETUP.md +107 -0
- package/docs/UTILITIES.md +221 -0
- package/docs/WORKSPACE_MARKER_SPEC.md +276 -0
- package/docs/ci-workflows.md +59 -0
- package/docs/commands-reference.md +172 -0
- package/docs/config-file-guide.md +299 -0
- package/docs/contracts/ARTIFACT_CATALOG.md +333 -0
- package/docs/contracts/CLI_LOG_EVENT_STREAM.md +123 -0
- package/docs/contracts/COMMAND_OWNERSHIP_MATRIX.md +138 -0
- package/docs/contracts/NAMING_AND_COEXISTENCE.md +60 -0
- package/docs/contracts/README.md +86 -0
- package/docs/contracts/RUNTIME_ACCEPTANCE_MATRIX.md +98 -0
- package/docs/contracts/RUNTIME_SUPPORT_MATRIX.md +75 -0
- package/docs/contracts/rapidkit-cli-contracts.json +239 -0
- package/docs/create-planner-capabilities.md +81 -0
- package/docs/doctor-command.md +467 -0
- package/docs/examples/ci-agent-grounding.yml +65 -0
- package/docs/from-code-to-shared-understanding.md +45 -0
- package/docs/governance-policy.enterprise.example.json +40 -0
- package/docs/mirror-config.enterprise.example.json +60 -0
- package/docs/policies.workspace.example.yml +23 -0
- package/docs/workspace-operations.md +160 -0
- package/docs/workspace-run.md +104 -0
- package/package.json +179 -0
- package/rapidkit.config.example.cjs +62 -0
- package/scripts/check-agent-customization-drift.mjs +78 -0
- package/scripts/check-cli-resolution.cjs +72 -0
- package/scripts/enforce-package-manager.cjs +10 -0
- package/scripts/enterprise-package-smoke.mjs +463 -0
- package/scripts/prepack-enterprise.mjs +43 -0
- package/templates/generator.js +179 -0
- package/templates/kits/fastapi-ddd/README.md.j2 +117 -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 +170 -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 +140 -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 +171 -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 +134 -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 +28 -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/workspai.config.example.cjs +62 -0
|
@@ -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())
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Basic result type shared across layers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Generic, Optional, TypeVar
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
E = TypeVar("E")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class Result(Generic[T, E]):
|
|
14
|
+
"""Simple functional-style result wrapper."""
|
|
15
|
+
|
|
16
|
+
value: Optional[T] = None
|
|
17
|
+
error: Optional[E] = None
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def is_ok(self) -> bool:
|
|
21
|
+
return self.error is None
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def is_err(self) -> bool:
|
|
25
|
+
return self.error is not None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
__all__ = ["Result"]
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Project CLI wrapper for {{ project_name }} (DDD edition).
|
|
3
|
+
|
|
4
|
+
Prefer a per-project CLI in `.workspai/cli.py` when present. Otherwise
|
|
5
|
+
provide helpful guidance to the developer rather than guessing environment
|
|
6
|
+
details.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import importlib.util
|
|
10
|
+
import sys
|
|
11
|
+
import argparse
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _missing_cli(name: str) -> None:
|
|
17
|
+
print(f"❌ Missing project-local CLI (.workspai/cli.py) - can't run '{name}'")
|
|
18
|
+
print("💡 If you're a new user run: workspai init # create venv + install deps")
|
|
19
|
+
sys.exit(127)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _load_project_cli_module() -> Optional[object]:
|
|
23
|
+
project_root = Path(__file__).resolve().parents[1]
|
|
24
|
+
cli_file = project_root / ".workspai" / "cli.py"
|
|
25
|
+
if not cli_file.exists():
|
|
26
|
+
legacy_cli_file = project_root / ".rapidkit" / "cli.py"
|
|
27
|
+
if not legacy_cli_file.exists():
|
|
28
|
+
return None
|
|
29
|
+
cli_file = legacy_cli_file
|
|
30
|
+
spec = importlib.util.spec_from_file_location("project_cli", str(cli_file))
|
|
31
|
+
if spec is None or spec.loader is None:
|
|
32
|
+
return None
|
|
33
|
+
module = importlib.util.module_from_spec(spec)
|
|
34
|
+
spec.loader.exec_module(module) # type: ignore
|
|
35
|
+
return module
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
_mod = _load_project_cli_module()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Default placeholders -- call `_missing_cli` if the project-local CLI isn't
|
|
42
|
+
# available. These are real functions so linters don't complain.
|
|
43
|
+
def init() -> None:
|
|
44
|
+
_missing_cli("init")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def dev() -> None:
|
|
48
|
+
_missing_cli("dev")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def start() -> None:
|
|
52
|
+
_missing_cli("start")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build() -> None:
|
|
56
|
+
_missing_cli("build")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test() -> None:
|
|
60
|
+
_missing_cli("test")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def lint() -> None:
|
|
64
|
+
_missing_cli("lint")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def format() -> None: # type: ignore[misc]
|
|
68
|
+
_missing_cli("format")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def help_cmd() -> None:
|
|
72
|
+
print("Usage: poetry run <command> # (init|dev|start|build|test|lint|format)")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if _mod is not None:
|
|
76
|
+
# prefer project-provided implementations when available
|
|
77
|
+
init = getattr(_mod, "init", init)
|
|
78
|
+
dev = getattr(_mod, "dev", dev)
|
|
79
|
+
start = getattr(_mod, "start", start)
|
|
80
|
+
build = getattr(_mod, "build", build)
|
|
81
|
+
test = getattr(_mod, "test", test)
|
|
82
|
+
lint = getattr(_mod, "lint", lint)
|
|
83
|
+
format = getattr(_mod, "format", format)
|
|
84
|
+
help_cmd = getattr(_mod, "help_cmd", help_cmd)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
_dev_impl = dev
|
|
88
|
+
_start_impl = start
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _runtime_kwargs_from_argv(argv: list[str]) -> dict:
|
|
92
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
93
|
+
parser.add_argument("-p", "--port", type=int, dest="port")
|
|
94
|
+
parser.add_argument("--host", dest="host")
|
|
95
|
+
parser.add_argument("--allow-global-runtime", action="store_true", dest="allow_global_runtime")
|
|
96
|
+
ns, _ = parser.parse_known_args(argv)
|
|
97
|
+
|
|
98
|
+
kwargs = {}
|
|
99
|
+
if ns.port is not None:
|
|
100
|
+
kwargs["port"] = ns.port
|
|
101
|
+
if ns.host is not None:
|
|
102
|
+
kwargs["host"] = ns.host
|
|
103
|
+
if ns.allow_global_runtime:
|
|
104
|
+
kwargs["allow_global_runtime"] = True
|
|
105
|
+
return kwargs
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def dev() -> None: # type: ignore[no-redef]
|
|
109
|
+
kwargs = _runtime_kwargs_from_argv(sys.argv[1:])
|
|
110
|
+
try:
|
|
111
|
+
_dev_impl(**kwargs)
|
|
112
|
+
except TypeError:
|
|
113
|
+
_dev_impl()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def start() -> None: # type: ignore[no-redef]
|
|
117
|
+
kwargs = _runtime_kwargs_from_argv(sys.argv[1:])
|
|
118
|
+
try:
|
|
119
|
+
_start_impl(**kwargs)
|
|
120
|
+
except TypeError:
|
|
121
|
+
_start_impl()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def main() -> None:
|
|
125
|
+
if len(sys.argv) < 2:
|
|
126
|
+
help_cmd()
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
command = sys.argv[1]
|
|
130
|
+
commands = {
|
|
131
|
+
"init": init,
|
|
132
|
+
"dev": dev,
|
|
133
|
+
"start": start,
|
|
134
|
+
"build": build,
|
|
135
|
+
"test": test,
|
|
136
|
+
"lint": lint,
|
|
137
|
+
"format": format,
|
|
138
|
+
"help": help_cmd,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if command in commands:
|
|
142
|
+
if command in ("dev", "start"):
|
|
143
|
+
parser = argparse.ArgumentParser(prog=f"poetry run {command}", add_help=False)
|
|
144
|
+
parser.add_argument("-p", "--port", type=int, dest="port")
|
|
145
|
+
parser.add_argument("--host", dest="host")
|
|
146
|
+
parser.add_argument("--allow-global-runtime", action="store_true", dest="allow_global_runtime")
|
|
147
|
+
ns, _ = parser.parse_known_args(sys.argv[2:])
|
|
148
|
+
|
|
149
|
+
kwargs = {}
|
|
150
|
+
if ns.port is not None:
|
|
151
|
+
kwargs["port"] = ns.port
|
|
152
|
+
if ns.host is not None:
|
|
153
|
+
kwargs["host"] = ns.host
|
|
154
|
+
if ns.allow_global_runtime:
|
|
155
|
+
kwargs["allow_global_runtime"] = True
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
commands[command](**kwargs)
|
|
159
|
+
except TypeError:
|
|
160
|
+
commands[command]()
|
|
161
|
+
else:
|
|
162
|
+
commands[command]()
|
|
163
|
+
else:
|
|
164
|
+
print(f"Unknown command: {command}")
|
|
165
|
+
print("Run 'poetry run help' to see available commands.")
|
|
166
|
+
sys.exit(1)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
main()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""{{ project_name }} FastAPI application entrypoint (DDD)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncIterator
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
|
|
10
|
+
from src.app.main import create_app
|
|
11
|
+
|
|
12
|
+
# <<<inject:imports>>>
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@asynccontextmanager
|
|
16
|
+
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
17
|
+
"""Coordinate startup/shutdown hooks contributed by RapidKit Core modules."""
|
|
18
|
+
|
|
19
|
+
_ = app # ensure the app reference stays available for injected hooks
|
|
20
|
+
# <<<inject:startup>>>
|
|
21
|
+
try:
|
|
22
|
+
yield
|
|
23
|
+
finally:
|
|
24
|
+
# <<<inject:shutdown>>>
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
app: FastAPI = create_app(
|
|
29
|
+
title="{{ project_name }}",
|
|
30
|
+
description="{{ description }}",
|
|
31
|
+
version="{{ app_version }}",
|
|
32
|
+
lifespan=lifespan,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# <<<inject:routes>>>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""API router assembly bridging DDD presentation routes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from src.app.presentation.api.router import get_api_router
|
|
6
|
+
from .health import router as health_router
|
|
7
|
+
from .notes import router as notes_router
|
|
8
|
+
# <<<inject:router-imports>>>
|
|
9
|
+
|
|
10
|
+
api_router = get_api_router()
|
|
11
|
+
api_router.include_router(health_router, prefix="/health", tags=["health"])
|
|
12
|
+
api_router.include_router(notes_router, prefix="/examples", tags=["examples"])
|
|
13
|
+
# <<<inject:router-mount>>>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Test package placeholder."""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Smoke tests for the generated FastAPI DDD scaffold."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from fastapi.testclient import TestClient
|
|
7
|
+
|
|
8
|
+
from src.app.main import create_app
|
|
9
|
+
from src.main import app
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_create_app_returns_fastapi_instance() -> None:
|
|
13
|
+
instance = create_app(title="Test", description="Sample", version="1.0.0")
|
|
14
|
+
assert isinstance(instance, FastAPI)
|
|
15
|
+
assert instance.title == "Test"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_health_route_returns_ok() -> None:
|
|
19
|
+
client = TestClient(app)
|
|
20
|
+
response = client.get("/api/health/")
|
|
21
|
+
assert response.status_code == 200
|
|
22
|
+
assert response.json()["status"] == "ok"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Health-check regression tests for FastAPI DDD projects."""
|
|
2
|
+
|
|
3
|
+
from fastapi.testclient import TestClient
|
|
4
|
+
|
|
5
|
+
from src.main import app
|
|
6
|
+
|
|
7
|
+
client = TestClient(app)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_health_endpoint() -> None:
|
|
11
|
+
response = client.get("/api/health/")
|
|
12
|
+
assert response.status_code == 200
|
|
13
|
+
payload = response.json()
|
|
14
|
+
assert payload["status"] == "ok"
|
|
15
|
+
assert "version" in payload
|
|
16
|
+
assert "uptime" in payload
|
|
17
|
+
assert "module" in payload
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Example feature tests exercising the DDD notes handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi.testclient import TestClient
|
|
6
|
+
|
|
7
|
+
from src.main import app
|
|
8
|
+
|
|
9
|
+
client = TestClient(app)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_create_and_fetch_note() -> None:
|
|
13
|
+
payload = {"title": "architecture note", "body": "Documented via FastAPI DDD kit."}
|
|
14
|
+
create_response = client.post("/api/examples/notes", json=payload)
|
|
15
|
+
assert create_response.status_code == 201
|
|
16
|
+
body = create_response.json()
|
|
17
|
+
note_id = body["id"]
|
|
18
|
+
assert body["title"] == payload["title"]
|
|
19
|
+
|
|
20
|
+
get_response = client.get(f"/api/examples/notes/{note_id}")
|
|
21
|
+
assert get_response.status_code == 200
|
|
22
|
+
assert get_response.json()["id"] == note_id
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_missing_note_returns_404() -> None:
|
|
26
|
+
response = client.get("/api/examples/notes/999")
|
|
27
|
+
assert response.status_code == 404
|