fix: repair cp1252-mangled em-dashes and ellipsis in the reference-app wiki
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
All four 06_reference_app pages round-tripped U+2014 (em-dash) and U+2026 (ellipsis) through cp1252 during earlier PowerShell edits; restored the characters and regenerated the wiki HTML + MCP modules.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"module": "wiki_reference_app_client_admin",
|
||||
"content": {
|
||||
"path": "wiki_reference_app_client_admin",
|
||||
"docstring": "# Client Admin — the spec-driven frontend\n\nThe client sample (`standards/src/templates/client/`) renders a full admin —\nauth pages, resource lists, forms, detail views — **from the server's\n`openapi.yaml`**, driven entirely by the x- extension fields. There are no\nhand-written screens per resource.\n\n## What ships\n\nA Vite + React + MUI app wiring the two Aetos packages:\n\n- **`@aetoskia/react-openapi`** — fetches `${VITE_API_BASE_URL}/openapi.json`,\n builds the router and the resource admin from the spec's schemas and x-\n fields (list columns, autocomplete, fk pickers, enum selects, hidden fields…)\n- **`@aetoskia/react-auth`** — login/register/logout against the auth-server\n paths declared in the spec's `securitySchemes.bearerAuth`, and attaches the\n JWT to every API call\n- a shared MUI theme (`src/shared-theme/`) and the shell\n (`Header` / `Footer` / `Home`)\n\n`src/openapi-config.ts` is the only configuration:\n\n```ts\nexport const specConfiguration: SpecConfiguration = {\n specUrl: `${apiBase}/openapi.json`,\n baseApiUrl: apiBase,\n title: \"Vet Admin\",\n getToken: () => tokenStore.get(),\n};\n```\n\n## Pair it with the sample server\n\n```bash\ncd apps/vet-sample-client\nprintf 'VITE_API_BASE_URL=http://localhost:8020\\nVITE_AUTH_BASE_URL=http://localhost:8020\\n' > .env.local\nnpm install\nnpm run dev\n```\n\nOpen the dev-server URL; the admin lists Parents, Pets, Treatments, Vets,\nAppointments and Jobs straight from the vet spec. Edit `openapi.yaml` on the\nserver and the client follows on reload — the spec is the contract on both\nsides.\n\n## Where the x- extensions come from\n\nThe canonical catalog lives in the react-openapi package\n(`spec-extensions.ts`, served to agents as the `specExtensions` module of the\nreact-openapi MCP bundle). The worked examples on the server side are the\n`Job` schema (enum selects via `x-edit-as: select` + `x-ui-type: enum`,\n`x-hidden` field masks, `x-list-columns`) and the FK fields on `Appointment`\n(`x-fk: {resource: pets, prefetch: true}`).\n\n## CI\n\nThe client ships a `drone-npm` shape (Type B in\n[Drone Pipelines](../lib/90_drone_pipelines/)) and follows the\n[Versioning](../lib/91_versioning/) standard (`package.json` +\n`CHANGELOG.md`).\n",
|
||||
"docstring": "# Client Admin — the spec-driven frontend\n\nThe client sample (`standards/src/templates/client/`) renders a full admin —\nauth pages, resource lists, forms, detail views — **from the server's\n`openapi.yaml`**, driven entirely by the x- extension fields. There are no\nhand-written screens per resource.\n\n## What ships\n\nA Vite + React + MUI app wiring the two Aetos packages:\n\n- **`@aetoskia/react-openapi`** — fetches `${VITE_API_BASE_URL}/openapi.json`,\n builds the router and the resource admin from the spec's schemas and x-\n fields (list columns, autocomplete, fk pickers, enum selects, hidden fields…)\n- **`@aetoskia/react-auth`** — login/register/logout against the auth-server\n paths declared in the spec's `securitySchemes.bearerAuth`, and attaches the\n JWT to every API call\n- a shared MUI theme (`src/shared-theme/`) and the shell\n (`Header` / `Footer` / `Home`)\n\n`src/openapi-config.ts` is the only configuration:\n\n```ts\nexport const specConfiguration: SpecConfiguration = {\n specUrl: `${apiBase}/openapi.json`,\n baseApiUrl: apiBase,\n title: \"Vet Admin\",\n getToken: () => tokenStore.get(),\n};\n```\n\n## Pair it with the sample server\n\n```bash\ncd apps/vet-sample-client\nprintf 'VITE_API_BASE_URL=http://localhost:8020\\nVITE_AUTH_BASE_URL=http://localhost:8020\\n' > .env.local\nnpm install\nnpm run dev\n```\n\nOpen the dev-server URL; the admin lists Parents, Pets, Treatments, Vets,\nAppointments and Jobs straight from the vet spec. Edit `openapi.yaml` on the\nserver and the client follows on reload — the spec is the contract on both\nsides.\n\n## Where the x- extensions come from\n\nThe canonical catalog lives in the react-openapi package\n(`spec-extensions.ts`, served to agents as the `specExtensions` module of the\nreact-openapi MCP bundle). The worked examples on the server side are the\n`Job` schema (enum selects via `x-edit-as: select` + `x-ui-type: enum`,\n`x-hidden` field masks, `x-list-columns`) and the FK fields on `Appointment`\n(`x-fk: {resource: pets, prefetch: true}`).\n\n## CI\n\nThe client ships a `drone-npm` shape (Type B in\n[Drone Pipelines](../lib/90_drone_pipelines/)) and follows the\n[Versioning](../lib/91_versioning/) standard (`package.json` +\n`CHANGELOG.md`).\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"module": "wiki_reference_app_domain_and_api",
|
||||
"content": {
|
||||
"path": "wiki_reference_app_domain_and_api",
|
||||
"docstring": "# Domain & API — the resource axis\n\nEvery aggregate follows the same four-file slice\n([CRUD Template](../lib/21_crud_template/)); walk `parents` first, then\n`appointments` when you need foreign keys.\n\n## The slice, end to end (parents)\n\n```\ndomain/parents/\n models.py # framework-free Pydantic model (id optional)\n ports/\n db.py # ParentDocument(Parent, BaseDocument) + create_parent_repo()\n service.py # ParentCrudsService(CrudsServiceAdaptor[...]) + get_parent_service()\ninterfaces/parents/\n routes.py # list/create/get/update/delete — names == operationIds\n```\n\nWiring happens in exactly one place — the composition root\n`init.py`:\n\n```python\nparent_repo = create_parent_repo()\n_resources[\"parent_svc\"] = get_parent_service(parent_repo)\n```\n\nRoutes resolve the service from app state through the shared helper\n(`interfaces/_common.py`):\n\n```python\nasync def list_parents(request: Request, limit: int = 20, offset: int = 0) -> dict:\n docs = await svc(request, \"parent\").search({})\n return paginate([dump(d) for d in docs], limit, offset)\n```\n\n## The spec is the contract\n\n`openapi.yaml` is OpenAPI 3.1 and the\nsingle source of truth: `main.py` builds an `OpenAPIFirstApp` that binds each\n`operationId` to a function of the same name in `interfaces/openapi.py` (a flat\nre-export of every routes module). There are no decorator routers.\n\nThe x- extension fields on schemas and properties are what make the admin\nclient render itself — see [Auto-Rendered Admin](../lib/40_ui_admin/) and\nthe worked `Job` schema (enum selects, hidden fields, filterable columns).\n\n## Foreign keys (appointments)\n\n`Appointment` declares `pet`, `vet`, `treatment` as `dict | int | None`. The\nwrite path normalizes bare ids through `expand_ref`; the read path returns\nfully expanded objects:\n\n```python\npet = await expand_ref(svc(request, \"pet\"), body.get(\"pet\"), \"pet\")\n```\n\nThe list endpoint additionally filters by date prefix and embedded fk ids —\nclient-side over the repository result, the pattern for small reference data.\n\n## Ids on the wire\n\nThe domain models declare `id: int | None = None` and documents use the\n**integer-id** repository variants (`infra/db/repos/mongo_int.py`,\n`create_repo()` in `domain/*/ports/db.py`, `REPO_BACKEND=memory|mongo`). This\nkeeps the OpenAPI `id: integer` contract, the in-memory demo store, and the\nMongo persistence layer consistent.\n\n## Tests\n\n- `tests/integration/test_openapi_app_builds.py` — generic gate: every\n `operationId` resolves and the app builds. Copy it verbatim.\n- `tests/unit/test_jobs_routes.py` — the pattern for route unit tests with a\n service double (no HTTP client needed).\n",
|
||||
"docstring": "# Domain & API — the resource axis\n\nEvery aggregate follows the same four-file slice\n([CRUD Template](../lib/21_crud_template/)); walk `parents` first, then\n`appointments` when you need foreign keys.\n\n## The slice, end to end (parents)\n\n```\ndomain/parents/\n models.py # framework-free Pydantic model (id optional)\n ports/\n db.py # ParentDocument(Parent, BaseDocument) + create_parent_repo()\n service.py # ParentCrudsService(CrudsServiceAdaptor[...]) + get_parent_service()\ninterfaces/parents/\n routes.py # list/create/get/update/delete — names == operationIds\n```\n\nWiring happens in exactly one place — the composition root\n`init.py`:\n\n```python\nparent_repo = create_parent_repo()\n_resources[\"parent_svc\"] = get_parent_service(parent_repo)\n```\n\nRoutes resolve the service from app state through the shared helper\n(`interfaces/_common.py`):\n\n```python\nasync def list_parents(request: Request, limit: int = 20, offset: int = 0) -> dict:\n docs = await svc(request, \"parent\").search({})\n return paginate([dump(d) for d in docs], limit, offset)\n```\n\n## The spec is the contract\n\n`openapi.yaml` is OpenAPI 3.1 and the\nsingle source of truth: `main.py` builds an `OpenAPIFirstApp` that binds each\n`operationId` to a function of the same name in `interfaces/openapi.py` (a flat\nre-export of every routes module). There are no decorator routers.\n\nThe x- extension fields on schemas and properties are what make the admin\nclient render itself — see [Auto-Rendered Admin](../lib/40_ui_admin/) and\nthe worked `Job` schema (enum selects, hidden fields, filterable columns).\n\n## Foreign keys (appointments)\n\n`Appointment` declares `pet`, `vet`, `treatment` as `dict | int | None`. The\nwrite path normalizes bare ids through `expand_ref`; the read path returns\nfully expanded objects:\n\n```python\npet = await expand_ref(svc(request, \"pet\"), body.get(\"pet\"), \"pet\")\n```\n\nThe list endpoint additionally filters by date prefix and embedded fk ids —\nclient-side over the repository result, the pattern for small reference data.\n\n## Ids on the wire\n\nThe domain models declare `id: int | None = None` and documents use the\n**integer-id** repository variants (`infra/db/repos/mongo_int.py`,\n`create_repo()` in `domain/*/ports/db.py`, `REPO_BACKEND=memory|mongo`). This\nkeeps the OpenAPI `id: integer` contract, the in-memory demo store, and the\nMongo persistence layer consistent.\n\n## Tests\n\n- `tests/integration/test_openapi_app_builds.py` — generic gate: every\n `operationId` resolves and the app builds. Copy it verbatim.\n- `tests/unit/test_jobs_routes.py` — the pattern for route unit tests with a\n service double (no HTTP client needed).\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"module": "wiki_reference_app_overview",
|
||||
"content": {
|
||||
"path": "wiki_reference_app_overview",
|
||||
"docstring": "# Reference App — Overview\n\nThe standards ship a **runnable sample** of the whole blueprint: the **vet**\nveterinary-clinic application. It exists to be copied — it is the reference\nfor how a real domain sits on the architecture, and the code an agent pulls\nthrough the MCP tools (`get_app_template`, `get_app_component`).\n\n!!! note \"One sample, two templates\"\n The **server** sample lives at `standards/src/templates/app/`\n (FastAPI + openapi-first + mongo-ops + hexa). The **client** sample lives\n at `standards/src/templates/client/` (Vite + MUI + `@aetoskia/react-openapi`\n + `@aetoskia/react-auth`). Together they are the full-stack reference.\n\n---\n\n## The domain at a glance\n\n| Aggregate | What it demonstrates |\n|---------------|---------------------------------------------------------------|\n| **parents** | The simplest CRUD slice (no foreign keys) — copy this first |\n| **pets** | Embedded list-of-refs (`parents[]`), file upload, enum fields |\n| **treatments**| Plain resource with pricing fields |\n| **vets** | Plain resource; target of prefetch FKs |\n| **appointments** | Single FKs (`pet`, `vet`, `treatment`) expanded on read, filtered list |\n| **jobs** | The worker's queue — durable status machine |\n\nThe pipeline axis ships one worked family, **`appointment_reminder`**, plus the\nreusable `pipelines/shared/` scaffold and a `worker.py` that dispatches jobs by\n`job.kind`.\n\n## Run it\n\n```bash\ncp -r <docs>/standards/src/templates/app apps/vet-sample\ncd apps/vet-sample\npip install -e \".[dev]\"\n\nuvicorn main:app --reload # API on :8020 (memory backend, zero services)\npython worker.py # background worker (polls jobs)\npython -m scripts.seed # optional: seed demo data (mongo backend)\n```\n\nThe default `REPO_BACKEND=memory` means the API boots with **no external\nservices** — repositories are process-local dictionaries. Switch to\n`REPO_BACKEND=mongo` for persistence (see `.env.example`).\n\nThe client pairs with it:\n\n```bash\ncp -r <docs>/standards/src/templates/client apps/vet-sample-client\ncd apps/vet-sample-client\necho \"VITE_API_BASE_URL=http://localhost:8020\" > .env.local\necho \"VITE_AUTH_BASE_URL=http://localhost:8020\" >> .env.local\nnpm install\nnpm run dev # Vite dev server\n```\n\n!!! warning \"Auth\"\n The server ships with the `bearerAuth` security scheme declared\n (`{AUTH_SERVER}` introspection). Without a reachable auth-server the API\n answers `401` on resource routes; `/health` stays public. For a local\n no-auth demo, remove the `security` block from `openapi.yaml` and the\n global security entry — see\n [Auth Integration](../lib/22_auth_integration/).\n\n## Next\n\n- [Domain & API](02_domain_and_api.md) — walking the resource axis\n- [Pipelines & Worker](03_pipelines_and_worker.md) — the pipeline axis\n- [Client Admin](04_client_admin.md) — the spec-driven frontend\n",
|
||||
"docstring": "# Reference App — Overview\n\nThe standards ship a **runnable sample** of the whole blueprint: the **vet**\nveterinary-clinic application. It exists to be copied — it is the reference\nfor how a real domain sits on the architecture, and the code an agent pulls\nthrough the MCP tools (`get_app_template`, `get_app_component`).\n\n!!! note \"One sample, two templates\"\n The **server** sample lives at `standards/src/templates/app/`\n (FastAPI + openapi-first + mongo-ops + hexa). The **client** sample lives\n at `standards/src/templates/client/` (Vite + MUI + `@aetoskia/react-openapi`\n + `@aetoskia/react-auth`). Together they are the full-stack reference.\n\n---\n\n## The domain at a glance\n\n| Aggregate | What it demonstrates |\n|---------------|---------------------------------------------------------------|\n| **parents** | The simplest CRUD slice (no foreign keys) — copy this first |\n| **pets** | Embedded list-of-refs (`parents[]`), file upload, enum fields |\n| **treatments**| Plain resource with pricing fields |\n| **vets** | Plain resource; target of prefetch FKs |\n| **appointments** | Single FKs (`pet`, `vet`, `treatment`) expanded on read, filtered list |\n| **jobs** | The worker's queue — durable status machine |\n\nThe pipeline axis ships one worked family, **`appointment_reminder`**, plus the\nreusable `pipelines/shared/` scaffold and a `worker.py` that dispatches jobs by\n`job.kind`.\n\n## Run it\n\n```bash\ncp -r <docs>/standards/src/templates/app apps/vet-sample\ncd apps/vet-sample\npip install -e \".[dev]\"\n\nuvicorn main:app --reload # API on :8020 (memory backend, zero services)\npython worker.py # background worker (polls jobs)\npython -m scripts.seed # optional: seed demo data (mongo backend)\n```\n\nThe default `REPO_BACKEND=memory` means the API boots with **no external\nservices** — repositories are process-local dictionaries. Switch to\n`REPO_BACKEND=mongo` for persistence (see `.env.example`).\n\nThe client pairs with it:\n\n```bash\ncp -r <docs>/standards/src/templates/client apps/vet-sample-client\ncd apps/vet-sample-client\necho \"VITE_API_BASE_URL=http://localhost:8020\" > .env.local\necho \"VITE_AUTH_BASE_URL=http://localhost:8020\" >> .env.local\nnpm install\nnpm run dev # Vite dev server\n```\n\n!!! warning \"Auth\"\n The server ships with the `bearerAuth` security scheme declared\n (`{AUTH_SERVER}` introspection). Without a reachable auth-server the API\n answers `401` on resource routes; `/health` stays public. For a local\n no-auth demo, remove the `security` block from `openapi.yaml` and the\n global security entry — see\n [Auth Integration](../lib/22_auth_integration/).\n\n## Next\n\n- [Domain & API](02_domain_and_api.md) — walking the resource axis\n- [Pipelines & Worker](03_pipelines_and_worker.md) — the pipeline axis\n- [Client Admin](04_client_admin.md) — the spec-driven frontend\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"module": "wiki_reference_app_pipelines_and_worker",
|
||||
"content": {
|
||||
"path": "wiki_reference_app_pipelines_and_worker",
|
||||
"docstring": "# Pipelines & Worker — the pipeline axis\n\nThe sample proves the full loop: **API creates a job document → worker polls →\npipeline family runs → job reaches a terminal status.**\n\n## The jobs slice\n\n`domain/jobs/` is an ordinary CRUD slice; its model is the durable state\nmachine the worker drives:\n\n```python\nclass Job(BaseModel):\n name: str\n kind: str # dispatch key\n status: Literal[\"pending\", \"processing\", \"done\", \"failed\"] = \"pending\"\n payload: dict | None = None\n retry_count: int = 0\n error_message: str | None = None\n```\n\n`POST /jobs` with `{\"name\": \"...\", \"kind\": \"appointment_reminder\",\n\"payload\": {\"appointment_id\": 1}}` enqueues work; `GET /jobs?status=...`\ninspects the queue.\n\n## The appointment_reminder family\n\n```\npipelines/appointment_reminder/\n abc.py # LoadPort / ComposePort / DeliverPort + root interface\n impl.py # concrete stages, wired root, register(\"appointment_reminder\", ...)\n pipeline.yaml # graph spec (nodes, edges, roots)\n resources/\n pipeline.yaml # runtime spec consumed by the loader\n patterns/reminder.yaml # message template fragments\npipelines/shared/\n ports.py loader.py registry.py # reused by every family\n```\n\nEach stage is a small, independently testable unit. The loader\n(`pipelines/shared/loader.py`) wires the root through `hexa.build` and merges\n`resources/patterns/*.yaml` into the stage-injected registry:\n\n```python\npipeline = get_appointment_reminder_pipeline(appointment_svc)\nreminder = await pipeline.run(appointment_id)\n```\n\nTo add a family: copy `appointment_reminder/`, replace the stages, call\n`register(kind, variant, root_cls)` — see\n[Pipeline Scaffold](../lib/50_pipeline_scaffold/).\n\n## The worker\n\n`worker.py` is the process that turns\ndocuments into execution:\n\n1. poll `job_svc.search({\"status\": \"pending\"})` on `WORKER_POLL_SECONDS`\n2. claim a job: `status → processing`\n3. dispatch through the `HANDLERS` table — `HANDLERS[job.kind](job, resources)`\n4. on success `status → done`; on exception `status → failed`,\n `retry_count += 1`, `error_message` captured; jobs at\n `WORKER_MAX_RETRIES` are skipped\n\n```python\nHANDLERS: dict[str, Callable] = {\n \"appointment_reminder\": run_appointment_reminder,\n # add yours here\n}\n```\n\nThe worker shares the composition root: `from init import init` gives it the\nsame wired services the API uses — one place to add a dependency.\n\n!!! note \"Same process model in production\"\n API and worker are separate processes (see `docker-compose.yaml`: `api`,\n `worker`, `mongo`). Any in-memory progress bus is per-process; to surface\n live progress through the API, swap the bus adaptor for a broker-backed\n one — see [Worker & Progress](../lib/51_worker_and_progress/).\n\n## Tests\n\n- `tests/unit/test_appointment_reminder_pipeline.py` — stage units + the wired\n root against a fake service.\n- `tests/unit/test_worker.py` — dispatch, done/failed transitions, retry\n exhaustion, using an in-memory job service double and a cancelled poll task.\n",
|
||||
"docstring": "# Pipelines & Worker — the pipeline axis\n\nThe sample proves the full loop: **API creates a job document → worker polls →\npipeline family runs → job reaches a terminal status.**\n\n## The jobs slice\n\n`domain/jobs/` is an ordinary CRUD slice; its model is the durable state\nmachine the worker drives:\n\n```python\nclass Job(BaseModel):\n name: str\n kind: str # dispatch key\n status: Literal[\"pending\", \"processing\", \"done\", \"failed\"] = \"pending\"\n payload: dict | None = None\n retry_count: int = 0\n error_message: str | None = None\n```\n\n`POST /jobs` with `{\"name\": \"...\", \"kind\": \"appointment_reminder\",\n\"payload\": {\"appointment_id\": 1}}` enqueues work; `GET /jobs?status=...`\ninspects the queue.\n\n## The appointment_reminder family\n\n```\npipelines/appointment_reminder/\n abc.py # LoadPort / ComposePort / DeliverPort + root interface\n impl.py # concrete stages, wired root, register(\"appointment_reminder\", ...)\n pipeline.yaml # graph spec (nodes, edges, roots)\n resources/\n pipeline.yaml # runtime spec consumed by the loader\n patterns/reminder.yaml # message template fragments\npipelines/shared/\n ports.py loader.py registry.py # reused by every family\n```\n\nEach stage is a small, independently testable unit. The loader\n(`pipelines/shared/loader.py`) wires the root through `hexa.build` and merges\n`resources/patterns/*.yaml` into the stage-injected registry:\n\n```python\npipeline = get_appointment_reminder_pipeline(appointment_svc)\nreminder = await pipeline.run(appointment_id)\n```\n\nTo add a family: copy `appointment_reminder/`, replace the stages, call\n`register(kind, variant, root_cls)` — see\n[Pipeline Scaffold](../lib/50_pipeline_scaffold/).\n\n## The worker\n\n`worker.py` is the process that turns\ndocuments into execution:\n\n1. poll `job_svc.search({\"status\": \"pending\"})` on `WORKER_POLL_SECONDS`\n2. claim a job: `status → processing`\n3. dispatch through the `HANDLERS` table — `HANDLERS[job.kind](job, resources)`\n4. on success `status → done`; on exception `status → failed`,\n `retry_count += 1`, `error_message` captured; jobs at\n `WORKER_MAX_RETRIES` are skipped\n\n```python\nHANDLERS: dict[str, Callable] = {\n \"appointment_reminder\": run_appointment_reminder,\n # add yours here\n}\n```\n\nThe worker shares the composition root: `from init import init` gives it the\nsame wired services the API uses — one place to add a dependency.\n\n!!! note \"Same process model in production\"\n API and worker are separate processes (see `docker-compose.yaml`: `api`,\n `worker`, `mongo`). Any in-memory progress bus is per-process; to surface\n live progress through the API, swap the bus adaptor for a broker-backed\n one — see [Worker & Progress](../lib/51_worker_and_progress/).\n\n## Tests\n\n- `tests/unit/test_appointment_reminder_pipeline.py` — stage units + the wired\n root against a fake service.\n- `tests/unit/test_worker.py` — dispatch, done/failed transitions, retry\n exhaustion, using an in-memory job service double and a cancelled poll task.\n",
|
||||
"objects": {}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
# Reference App — Overview
|
||||
# Reference App — Overview
|
||||
|
||||
The standards ship a **runnable sample** of the whole blueprint: the **vet**
|
||||
veterinary-clinic application. It exists to be copied — it is the reference
|
||||
veterinary-clinic application. It exists to be copied — it is the reference
|
||||
for how a real domain sits on the architecture, and the code an agent pulls
|
||||
through the MCP tools (`get_app_template`, `get_app_component`).
|
||||
|
||||
@@ -17,12 +17,12 @@ through the MCP tools (`get_app_template`, `get_app_component`).
|
||||
|
||||
| Aggregate | What it demonstrates |
|
||||
|---------------|---------------------------------------------------------------|
|
||||
| **parents** | The simplest CRUD slice (no foreign keys) — copy this first |
|
||||
| **parents** | The simplest CRUD slice (no foreign keys) — copy this first |
|
||||
| **pets** | Embedded list-of-refs (`parents[]`), file upload, enum fields |
|
||||
| **treatments**| Plain resource with pricing fields |
|
||||
| **vets** | Plain resource; target of prefetch FKs |
|
||||
| **appointments** | Single FKs (`pet`, `vet`, `treatment`) expanded on read, filtered list |
|
||||
| **jobs** | The worker's queue — durable status machine |
|
||||
| **jobs** | The worker's queue — durable status machine |
|
||||
|
||||
The pipeline axis ships one worked family, **`appointment_reminder`**, plus the
|
||||
reusable `pipelines/shared/` scaffold and a `worker.py` that dispatches jobs by
|
||||
@@ -41,7 +41,7 @@ python -m scripts.seed # optional: seed demo data (mongo backend)
|
||||
```
|
||||
|
||||
The default `REPO_BACKEND=memory` means the API boots with **no external
|
||||
services** — repositories are process-local dictionaries. Switch to
|
||||
services** — repositories are process-local dictionaries. Switch to
|
||||
`REPO_BACKEND=mongo` for persistence (see `.env.example`).
|
||||
|
||||
The client pairs with it:
|
||||
@@ -60,11 +60,11 @@ npm run dev # Vite dev server
|
||||
(`{AUTH_SERVER}` introspection). Without a reachable auth-server the API
|
||||
answers `401` on resource routes; `/health` stays public. For a local
|
||||
no-auth demo, remove the `security` block from `openapi.yaml` and the
|
||||
global security entry — see
|
||||
global security entry — see
|
||||
[Auth Integration](../lib/22_auth_integration/).
|
||||
|
||||
## Next
|
||||
|
||||
- [Domain & API](02_domain_and_api.md) — walking the resource axis
|
||||
- [Pipelines & Worker](03_pipelines_and_worker.md) — the pipeline axis
|
||||
- [Client Admin](04_client_admin.md) — the spec-driven frontend
|
||||
- [Domain & API](02_domain_and_api.md) — walking the resource axis
|
||||
- [Pipelines & Worker](03_pipelines_and_worker.md) — the pipeline axis
|
||||
- [Client Admin](04_client_admin.md) — the spec-driven frontend
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Domain & API — the resource axis
|
||||
# Domain & API — the resource axis
|
||||
|
||||
Every aggregate follows the same four-file slice
|
||||
([CRUD Template](../lib/21_crud_template/)); walk `parents` first, then
|
||||
@@ -13,10 +13,10 @@ domain/parents/
|
||||
db.py # ParentDocument(Parent, BaseDocument) + create_parent_repo()
|
||||
service.py # ParentCrudsService(CrudsServiceAdaptor[...]) + get_parent_service()
|
||||
interfaces/parents/
|
||||
routes.py # list/create/get/update/delete — names == operationIds
|
||||
routes.py # list/create/get/update/delete — names == operationIds
|
||||
```
|
||||
|
||||
Wiring happens in exactly one place — the composition root
|
||||
Wiring happens in exactly one place — the composition root
|
||||
`init.py`:
|
||||
|
||||
```python
|
||||
@@ -41,7 +41,7 @@ single source of truth: `main.py` builds an `OpenAPIFirstApp` that binds each
|
||||
re-export of every routes module). There are no decorator routers.
|
||||
|
||||
The x- extension fields on schemas and properties are what make the admin
|
||||
client render itself — see [Auto-Rendered Admin](../lib/40_ui_admin/) and
|
||||
client render itself — see [Auto-Rendered Admin](../lib/40_ui_admin/) and
|
||||
the worked `Job` schema (enum selects, hidden fields, filterable columns).
|
||||
|
||||
## Foreign keys (appointments)
|
||||
@@ -54,7 +54,7 @@ fully expanded objects:
|
||||
pet = await expand_ref(svc(request, "pet"), body.get("pet"), "pet")
|
||||
```
|
||||
|
||||
The list endpoint additionally filters by date prefix and embedded fk ids —
|
||||
The list endpoint additionally filters by date prefix and embedded fk ids —
|
||||
client-side over the repository result, the pattern for small reference data.
|
||||
|
||||
## Ids on the wire
|
||||
@@ -67,7 +67,7 @@ Mongo persistence layer consistent.
|
||||
|
||||
## Tests
|
||||
|
||||
- `tests/integration/test_openapi_app_builds.py` — generic gate: every
|
||||
- `tests/integration/test_openapi_app_builds.py` — generic gate: every
|
||||
`operationId` resolves and the app builds. Copy it verbatim.
|
||||
- `tests/unit/test_jobs_routes.py` — the pattern for route unit tests with a
|
||||
- `tests/unit/test_jobs_routes.py` — the pattern for route unit tests with a
|
||||
service double (no HTTP client needed).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Pipelines & Worker — the pipeline axis
|
||||
# Pipelines & Worker — the pipeline axis
|
||||
|
||||
The sample proves the full loop: **API creates a job document → worker polls →
|
||||
pipeline family runs → job reaches a terminal status.**
|
||||
@@ -46,7 +46,7 @@ reminder = await pipeline.run(appointment_id)
|
||||
```
|
||||
|
||||
To add a family: copy `appointment_reminder/`, replace the stages, call
|
||||
`register(kind, variant, root_cls)` — see
|
||||
`register(kind, variant, root_cls)` — see
|
||||
[Pipeline Scaffold](../lib/50_pipeline_scaffold/).
|
||||
|
||||
## The worker
|
||||
@@ -56,7 +56,7 @@ documents into execution:
|
||||
|
||||
1. poll `job_svc.search({"status": "pending"})` on `WORKER_POLL_SECONDS`
|
||||
2. claim a job: `status → processing`
|
||||
3. dispatch through the `HANDLERS` table — `HANDLERS[job.kind](job, resources)`
|
||||
3. dispatch through the `HANDLERS` table — `HANDLERS[job.kind](job, resources)`
|
||||
4. on success `status → done`; on exception `status → failed`,
|
||||
`retry_count += 1`, `error_message` captured; jobs at
|
||||
`WORKER_MAX_RETRIES` are skipped
|
||||
@@ -69,17 +69,17 @@ HANDLERS: dict[str, Callable] = {
|
||||
```
|
||||
|
||||
The worker shares the composition root: `from init import init` gives it the
|
||||
same wired services the API uses — one place to add a dependency.
|
||||
same wired services the API uses — one place to add a dependency.
|
||||
|
||||
!!! note "Same process model in production"
|
||||
API and worker are separate processes (see `docker-compose.yaml`: `api`,
|
||||
`worker`, `mongo`). Any in-memory progress bus is per-process; to surface
|
||||
live progress through the API, swap the bus adaptor for a broker-backed
|
||||
one — see [Worker & Progress](../lib/51_worker_and_progress/).
|
||||
one — see [Worker & Progress](../lib/51_worker_and_progress/).
|
||||
|
||||
## Tests
|
||||
|
||||
- `tests/unit/test_appointment_reminder_pipeline.py` — stage units + the wired
|
||||
- `tests/unit/test_appointment_reminder_pipeline.py` — stage units + the wired
|
||||
root against a fake service.
|
||||
- `tests/unit/test_worker.py` — dispatch, done/failed transitions, retry
|
||||
- `tests/unit/test_worker.py` — dispatch, done/failed transitions, retry
|
||||
exhaustion, using an in-memory job service double and a cancelled poll task.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Client Admin — the spec-driven frontend
|
||||
# Client Admin — the spec-driven frontend
|
||||
|
||||
The client sample (`standards/src/templates/client/`) renders a full admin —
|
||||
auth pages, resource lists, forms, detail views — **from the server's
|
||||
The client sample (`standards/src/templates/client/`) renders a full admin —
|
||||
auth pages, resource lists, forms, detail views — **from the server's
|
||||
`openapi.yaml`**, driven entirely by the x- extension fields. There are no
|
||||
hand-written screens per resource.
|
||||
|
||||
@@ -9,10 +9,10 @@ hand-written screens per resource.
|
||||
|
||||
A Vite + React + MUI app wiring the two Aetos packages:
|
||||
|
||||
- **`@aetoskia/react-openapi`** — fetches `${VITE_API_BASE_URL}/openapi.json`,
|
||||
- **`@aetoskia/react-openapi`** — fetches `${VITE_API_BASE_URL}/openapi.json`,
|
||||
builds the router and the resource admin from the spec's schemas and x-
|
||||
fields (list columns, autocomplete, fk pickers, enum selects, hidden fields…)
|
||||
- **`@aetoskia/react-auth`** — login/register/logout against the auth-server
|
||||
fields (list columns, autocomplete, fk pickers, enum selects, hidden fields…)
|
||||
- **`@aetoskia/react-auth`** — login/register/logout against the auth-server
|
||||
paths declared in the spec's `securitySchemes.bearerAuth`, and attaches the
|
||||
JWT to every API call
|
||||
- a shared MUI theme (`src/shared-theme/`) and the shell
|
||||
@@ -40,7 +40,7 @@ npm run dev
|
||||
|
||||
Open the dev-server URL; the admin lists Parents, Pets, Treatments, Vets,
|
||||
Appointments and Jobs straight from the vet spec. Edit `openapi.yaml` on the
|
||||
server and the client follows on reload — the spec is the contract on both
|
||||
server and the client follows on reload — the spec is the contract on both
|
||||
sides.
|
||||
|
||||
## Where the x- extensions come from
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<div data-md-component="skip">
|
||||
|
||||
|
||||
<a href="#reference-app-a-overview" class="md-skip">
|
||||
<a href="#reference-app-overview" class="md-skip">
|
||||
Skip to content
|
||||
</a>
|
||||
|
||||
@@ -842,9 +842,9 @@
|
||||
|
||||
|
||||
|
||||
<h1 id="reference-app-a-overview">Reference App — Overview</h1>
|
||||
<h1 id="reference-app-overview">Reference App — Overview</h1>
|
||||
<p>The standards ship a <strong>runnable sample</strong> of the whole blueprint: the <strong>vet</strong>
|
||||
veterinary-clinic application. It exists to be copied — it is the reference
|
||||
veterinary-clinic application. It exists to be copied — it is the reference
|
||||
for how a real domain sits on the architecture, and the code an agent pulls
|
||||
through the MCP tools (<code>get_app_template</code>, <code>get_app_component</code>).</p>
|
||||
<div class="admonition note">
|
||||
@@ -866,7 +866,7 @@ at <code>standards/src/templates/client/</code> (Vite + MUI + <code>@aetoskia/re
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>parents</strong></td>
|
||||
<td>The simplest CRUD slice (no foreign keys) — copy this first</td>
|
||||
<td>The simplest CRUD slice (no foreign keys) — copy this first</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>pets</strong></td>
|
||||
@@ -886,7 +886,7 @@ at <code>standards/src/templates/client/</code> (Vite + MUI + <code>@aetoskia/re
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>jobs</strong></td>
|
||||
<td>The worker's queue — durable status machine</td>
|
||||
<td>The worker's queue — durable status machine</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -909,7 +909,7 @@ reusable <code>pipelines/shared/</code> scaffold and a <code>worker.py</code> th
|
||||
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7"></a>python<span class="w"> </span>-m<span class="w"> </span>scripts.seed<span class="w"> </span><span class="c1"># optional: seed demo data (mongo backend)</span>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>The default <code>REPO_BACKEND=memory</code> means the API boots with <strong>no external
|
||||
services</strong> — repositories are process-local dictionaries. Switch to
|
||||
services</strong> — repositories are process-local dictionaries. Switch to
|
||||
<code>REPO_BACKEND=mongo</code> for persistence (see <code>.env.example</code>).</p>
|
||||
<p>The client pairs with it:</p>
|
||||
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span>
|
||||
@@ -930,14 +930,14 @@ services</strong> — repositories are process-local dictionaries. Switch t
|
||||
(<code>{AUTH_SERVER}</code> introspection). Without a reachable auth-server the API
|
||||
answers <code>401</code> on resource routes; <code>/health</code> stays public. For a local
|
||||
no-auth demo, remove the <code>security</code> block from <code>openapi.yaml</code> and the
|
||||
global security entry — see
|
||||
global security entry — see
|
||||
<a href="../../../lib/22_auth_integration/">Auth Integration</a>.</p>
|
||||
</div>
|
||||
<h2 id="next">Next</h2>
|
||||
<ul>
|
||||
<li><a href="../02_domain_and_api/">Domain & API</a> — walking the resource axis</li>
|
||||
<li><a href="../03_pipelines_and_worker/">Pipelines & Worker</a> — the pipeline axis</li>
|
||||
<li><a href="../04_client_admin/">Client Admin</a> — the spec-driven frontend</li>
|
||||
<li><a href="../02_domain_and_api/">Domain & API</a> — walking the resource axis</li>
|
||||
<li><a href="../03_pipelines_and_worker/">Pipelines & Worker</a> — the pipeline axis</li>
|
||||
<li><a href="../04_client_admin/">Client Admin</a> — the spec-driven frontend</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<div data-md-component="skip">
|
||||
|
||||
|
||||
<a href="#domain-api-a-the-resource-axis" class="md-skip">
|
||||
<a href="#domain-api-the-resource-axis" class="md-skip">
|
||||
Skip to content
|
||||
</a>
|
||||
|
||||
@@ -860,7 +860,7 @@
|
||||
|
||||
|
||||
|
||||
<h1 id="domain-api-a-the-resource-axis">Domain & API — the resource axis</h1>
|
||||
<h1 id="domain-api-the-resource-axis">Domain & API — the resource axis</h1>
|
||||
<p>Every aggregate follows the same four-file slice
|
||||
(<a href="../../../lib/21_crud_template/">CRUD Template</a>); walk <code>parents</code> first, then
|
||||
<code>appointments</code> when you need foreign keys.</p>
|
||||
@@ -877,9 +877,9 @@
|
||||
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4"></a> db.py # ParentDocument(Parent, BaseDocument) + create_parent_repo()
|
||||
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5"></a> service.py # ParentCrudsService(CrudsServiceAdaptor[...]) + get_parent_service()
|
||||
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6"></a>interfaces/parents/
|
||||
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7"></a> routes.py # list/create/get/update/delete — names == operationIds
|
||||
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7"></a> routes.py # list/create/get/update/delete — names == operationIds
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>Wiring happens in exactly one place — the composition root
|
||||
<p>Wiring happens in exactly one place — the composition root
|
||||
<code>init.py</code>:</p>
|
||||
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span>
|
||||
<span class="normal"><a href="#__codelineno-1-2">2</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a><span class="n">parent_repo</span> <span class="o">=</span> <span class="n">create_parent_repo</span><span class="p">()</span>
|
||||
@@ -899,7 +899,7 @@ single source of truth: <code>main.py</code> builds an <code>OpenAPIFirstApp</co
|
||||
<code>operationId</code> to a function of the same name in <code>interfaces/openapi.py</code> (a flat
|
||||
re-export of every routes module). There are no decorator routers.</p>
|
||||
<p>The x- extension fields on schemas and properties are what make the admin
|
||||
client render itself — see <a href="../../../lib/40_ui_admin/">Auto-Rendered Admin</a> and
|
||||
client render itself — see <a href="../../../lib/40_ui_admin/">Auto-Rendered Admin</a> and
|
||||
the worked <code>Job</code> schema (enum selects, hidden fields, filterable columns).</p>
|
||||
<h2 id="foreign-keys-appointments">Foreign keys (appointments)</h2>
|
||||
<p><code>Appointment</code> declares <code>pet</code>, <code>vet</code>, <code>treatment</code> as <code>dict | int | None</code>. The
|
||||
@@ -907,7 +907,7 @@ write path normalizes bare ids through <code>expand_ref</code>; the read path re
|
||||
fully expanded objects:</p>
|
||||
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-3-1">1</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1"></a><span class="n">pet</span> <span class="o">=</span> <span class="k">await</span> <span class="n">expand_ref</span><span class="p">(</span><span class="n">svc</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s2">"pet"</span><span class="p">),</span> <span class="n">body</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"pet"</span><span class="p">),</span> <span class="s2">"pet"</span><span class="p">)</span>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>The list endpoint additionally filters by date prefix and embedded fk ids —
|
||||
<p>The list endpoint additionally filters by date prefix and embedded fk ids —
|
||||
client-side over the repository result, the pattern for small reference data.</p>
|
||||
<h2 id="ids-on-the-wire">Ids on the wire</h2>
|
||||
<p>The domain models declare <code>id: int | None = None</code> and documents use the
|
||||
@@ -917,9 +917,9 @@ keeps the OpenAPI <code>id: integer</code> contract, the in-memory demo store, a
|
||||
Mongo persistence layer consistent.</p>
|
||||
<h2 id="tests">Tests</h2>
|
||||
<ul>
|
||||
<li><code>tests/integration/test_openapi_app_builds.py</code> — generic gate: every
|
||||
<li><code>tests/integration/test_openapi_app_builds.py</code> — generic gate: every
|
||||
<code>operationId</code> resolves and the app builds. Copy it verbatim.</li>
|
||||
<li><code>tests/unit/test_jobs_routes.py</code> — the pattern for route unit tests with a
|
||||
<li><code>tests/unit/test_jobs_routes.py</code> — the pattern for route unit tests with a
|
||||
service double (no HTTP client needed).</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<div data-md-component="skip">
|
||||
|
||||
|
||||
<a href="#pipelines-worker-a-the-pipeline-axis" class="md-skip">
|
||||
<a href="#pipelines-worker-the-pipeline-axis" class="md-skip">
|
||||
Skip to content
|
||||
</a>
|
||||
|
||||
@@ -851,7 +851,7 @@
|
||||
|
||||
|
||||
|
||||
<h1 id="pipelines-worker-a-the-pipeline-axis">Pipelines & Worker — the pipeline axis</h1>
|
||||
<h1 id="pipelines-worker-the-pipeline-axis">Pipelines & Worker — the pipeline axis</h1>
|
||||
<p>The sample proves the full loop: <strong>API creates a job document → worker polls →
|
||||
pipeline family runs → job reaches a terminal status.</strong></p>
|
||||
<h2 id="the-jobs-slice">The jobs slice</h2>
|
||||
@@ -901,7 +901,7 @@ inspects the queue.</p>
|
||||
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a><span class="n">reminder</span> <span class="o">=</span> <span class="k">await</span> <span class="n">pipeline</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">appointment_id</span><span class="p">)</span>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>To add a family: copy <code>appointment_reminder/</code>, replace the stages, call
|
||||
<code>register(kind, variant, root_cls)</code> — see
|
||||
<code>register(kind, variant, root_cls)</code> — see
|
||||
<a href="../../../lib/50_pipeline_scaffold/">Pipeline Scaffold</a>.</p>
|
||||
<h2 id="the-worker">The worker</h2>
|
||||
<p><code>worker.py</code> is the process that turns
|
||||
@@ -909,7 +909,7 @@ documents into execution:</p>
|
||||
<ol>
|
||||
<li>poll <code>job_svc.search({"status": "pending"})</code> on <code>WORKER_POLL_SECONDS</code></li>
|
||||
<li>claim a job: <code>status → processing</code></li>
|
||||
<li>dispatch through the <code>HANDLERS</code> table — <code>HANDLERS[job.kind](job, resources)</code></li>
|
||||
<li>dispatch through the <code>HANDLERS</code> table — <code>HANDLERS[job.kind](job, resources)</code></li>
|
||||
<li>on success <code>status → done</code>; on exception <code>status → failed</code>,
|
||||
<code>retry_count += 1</code>, <code>error_message</code> captured; jobs at
|
||||
<code>WORKER_MAX_RETRIES</code> are skipped</li>
|
||||
@@ -923,19 +923,19 @@ documents into execution:</p>
|
||||
</span><span id="__span-3-4"><a id="__codelineno-3-4" name="__codelineno-3-4"></a><span class="p">}</span>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>The worker shares the composition root: <code>from init import init</code> gives it the
|
||||
same wired services the API uses — one place to add a dependency.</p>
|
||||
same wired services the API uses — one place to add a dependency.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Same process model in production</p>
|
||||
<p>API and worker are separate processes (see <code>docker-compose.yaml</code>: <code>api</code>,
|
||||
<code>worker</code>, <code>mongo</code>). Any in-memory progress bus is per-process; to surface
|
||||
live progress through the API, swap the bus adaptor for a broker-backed
|
||||
one — see <a href="../../../lib/51_worker_and_progress/">Worker & Progress</a>.</p>
|
||||
one — see <a href="../../../lib/51_worker_and_progress/">Worker & Progress</a>.</p>
|
||||
</div>
|
||||
<h2 id="tests">Tests</h2>
|
||||
<ul>
|
||||
<li><code>tests/unit/test_appointment_reminder_pipeline.py</code> — stage units + the wired
|
||||
<li><code>tests/unit/test_appointment_reminder_pipeline.py</code> — stage units + the wired
|
||||
root against a fake service.</li>
|
||||
<li><code>tests/unit/test_worker.py</code> — dispatch, done/failed transitions, retry
|
||||
<li><code>tests/unit/test_worker.py</code> — dispatch, done/failed transitions, retry
|
||||
exhaustion, using an in-memory job service double and a cancelled poll task.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<div data-md-component="skip">
|
||||
|
||||
|
||||
<a href="#client-admin-a-the-spec-driven-frontend" class="md-skip">
|
||||
<a href="#client-admin-the-spec-driven-frontend" class="md-skip">
|
||||
Skip to content
|
||||
</a>
|
||||
|
||||
@@ -851,18 +851,18 @@
|
||||
|
||||
|
||||
|
||||
<h1 id="client-admin-a-the-spec-driven-frontend">Client Admin — the spec-driven frontend</h1>
|
||||
<p>The client sample (<code>standards/src/templates/client/</code>) renders a full admin —
|
||||
auth pages, resource lists, forms, detail views — <strong>from the server's
|
||||
<h1 id="client-admin-the-spec-driven-frontend">Client Admin — the spec-driven frontend</h1>
|
||||
<p>The client sample (<code>standards/src/templates/client/</code>) renders a full admin —
|
||||
auth pages, resource lists, forms, detail views — <strong>from the server's
|
||||
<code>openapi.yaml</code></strong>, driven entirely by the x- extension fields. There are no
|
||||
hand-written screens per resource.</p>
|
||||
<h2 id="what-ships">What ships</h2>
|
||||
<p>A Vite + React + MUI app wiring the two Aetos packages:</p>
|
||||
<ul>
|
||||
<li><strong><code>@aetoskia/react-openapi</code></strong> — fetches <code>${VITE_API_BASE_URL}/openapi.json</code>,
|
||||
<li><strong><code>@aetoskia/react-openapi</code></strong> — fetches <code>${VITE_API_BASE_URL}/openapi.json</code>,
|
||||
builds the router and the resource admin from the spec's schemas and x-
|
||||
fields (list columns, autocomplete, fk pickers, enum selects, hidden fields…)</li>
|
||||
<li><strong><code>@aetoskia/react-auth</code></strong> — login/register/logout against the auth-server
|
||||
fields (list columns, autocomplete, fk pickers, enum selects, hidden fields…)</li>
|
||||
<li><strong><code>@aetoskia/react-auth</code></strong> — login/register/logout against the auth-server
|
||||
paths declared in the spec's <code>securitySchemes.bearerAuth</code>, and attaches the
|
||||
JWT to every API call</li>
|
||||
<li>a shared MUI theme (<code>src/shared-theme/</code>) and the shell
|
||||
@@ -892,7 +892,7 @@ hand-written screens per resource.</p>
|
||||
</span></code></pre></div></td></tr></table></div>
|
||||
<p>Open the dev-server URL; the admin lists Parents, Pets, Treatments, Vets,
|
||||
Appointments and Jobs straight from the vet spec. Edit <code>openapi.yaml</code> on the
|
||||
server and the client follows on reload — the spec is the contract on both
|
||||
server and the client follows on reload — the spec is the contract on both
|
||||
sides.</p>
|
||||
<h2 id="where-the-x-extensions-come-from">Where the x- extensions come from</h2>
|
||||
<p>The canonical catalog lives in the react-openapi package
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user