Bloodbank SDK Generation
Deterministic recipe for generating typed bindings from bloodbank/schemas/bloodbank/v1/**. Outputs are plain artifacts the consumer commits or vendors — this skill produces code, not an installable package.
Prereqs
- Run from the Bloodbank repo root (the directory containing
schemas/ and docs/event-naming.md), or pass the schema path explicitly.
uvx (from uv) for Python tooling, npx for TypeScript tooling — both are project-less, no install required.
- Validate the tree is well-formed first:
mise run validate:schemas (or bash scripts/validate_schemas.sh). Skip generation if it fails — fix schemas before generating.
Python (Pydantic v2 models)
uvx --from 'datamodel-code-generator>=0.25' datamodel-codegen \
--input schemas/bloodbank/v1 \
--input-file-type jsonschema \
--output-model-type pydantic_v2.BaseModel \
--target-python-version 3.12 \
--use-schema-description \
--use-field-description \
--use-double-quotes \
--enum-field-as-literal one \
--output bloodbank_contracts/models.py
Flag rationale:
--input-file-type jsonschema — required; the default auto mis-detects when the tree has multiple files.
--enum-field-as-literal one — properties.type.const becomes Literal["bloodbank.v1.<...>"], matching the 5-token contract.
--use-schema-description / --use-field-description — preserves the human-readable descriptions from the JSON Schemas as Pydantic field docstrings.
--target-python-version 3.12 — matches the project floor; bump if the project floor moves.
The generator resolves $ref: "../../../_common/cloudevent_base.v1.json" correctly because all schemas live under one input root.
TypeScript (interfaces + type unions)
npx json-schema-to-typescript \
'schemas/bloodbank/v1/**/*.v1.json' \
--cwd schemas \
--bannerComment '' \
--unreachableDefinitions \
--additionalProperties false \
> bloodbank_contracts/types.ts
Flag rationale:
--cwd schemas — sets the resolver root so $ref: "../../../_common/..." resolves from each file's directory through the same root the schemas were authored against.
--bannerComment '' — drops the autogenerated header that fights with linters.
--unreachableDefinitions — emits the _common types even if no top-level schema references them by name, so consumers can import CloudEventBase directly.
--additionalProperties false — matches the strict-shape default of the runtime validator.
Verify the output
After generation, run a one-shot import smoke:
# Python
python3 -c "from bloodbank_contracts.models import *; import bloodbank_contracts.models as m; print(f'{sum(1 for n in dir(m) if not n.startswith(\"_\")):>3} symbols')"
# TypeScript (requires tsc)
npx tsc --noEmit --strict bloodbank_contracts/types.ts
If either fails, regenerate from a clean validate:schemas pass before debugging the generator output.
Common combinations
- Refresh + commit on schema change: re-run both generators whenever you edit anything under
bloodbank/schemas/bloodbank/v1/. Treat the generated files as committed artifacts the consumer holds — git diff is the drift signal.
- One-off consumer scaffold: a downstream project that needs the types runs both commands once, vendors the outputs, and pins via the schema-tree commit hash recorded in their changelog.
- Future packaging: when a second external consumer materializes, promote
bloodbank_contracts/ into a real package (uv build for Python, tsc --declaration + package.json for TS). See RECOMMENDATION.md step 5 in the Bloodbank repo.
Gotchas
Read references/gotchas.md only when generation fails with a non-obvious error, output looks wrong (missing fields, broken refs, unexpected types), or you're integrating the generated code into an existing project with its own Pydantic/Zod setup.
Out of scope
This skill produces SDK artifacts from the canonical schema tree. It does NOT cover:
- Authoring or modifying schemas. Edit JSON files under
bloodbank/schemas/ directly per bloodbank/docs/event-naming.md §12.
- Validating envelopes at runtime. Set
BLOODBANK_HOOK_VALIDATE=1 and rely on services/agent-hooks/core/validate.py, which uses a pre-loaded referencing.Registry.
- Checking schema-tree consistency. Run
mise run validate:schemas (well-formedness + ref resolution) and mise run smoketest:schema-contract-consistency (validator allowlist drift).
- The deprecated Holyfields generators (
tools/generators/generate_pydantic.py, tools/generators/generate_typescript.sh). Those are hand-rolled, locked to Holyfields' packages/python workspace, and superseded by the third-party tools above. Do not port them.
- Apicurio registry uploads. The
apicurio://holyfields/<type>/versions/1 dataschema URI is a registry artifact id, separate from this skill's filesystem generation path.
- Publishing the SDK (PyPI, npm, GitHub Packages). This skill writes files; distribution is a separate decision.
- Zod schemas.
json-schema-to-typescript emits TypeScript types + interfaces, not Zod validators. If runtime TS validation is needed, layer a tool like json-schema-to-zod on top — but evaluate whether the type-only output is sufficient first.
1---2name: bloodbank-sdk-generation3description: Generate typed SDK bindings (Pydantic v2 models, TypeScript types) from the Bloodbank JSON Schema tree at `bloodbank/schemas/bloodbank/v1/**` (Draft 2020-12). Uses `datamodel-code-generator` and `json-schema-to-typescript` — NOT the deprecated hand-rolled Holyfields generators. Use when generating event-contract bindings for a Bloodbank consumer, importing typed CloudEvents envelopes into another project, or scaffolding a `bloodbank-contracts` SDK package. Trigger keywords — "bloodbank SDK", "bloodbank contracts", "Pydantic from bloodbank schemas", "TypeScript types for bloodbank events", "bloodbank.v1.* types", "event contract bindings", "datamodel-code-generator", "json-schema-to-typescript", "regenerate SDK", "CloudEvents bindings". Do NOT use for schema authoring (edit `bloodbank/schemas/` directly per `docs/event-naming.md` §12), runtime envelope validation (use `BLOODBANK_HOOK_VALIDATE=1`), schema-tree consistency (`mise run smoketest:schemas`), or generic JSON Schema codegen unrelated to Bloodbank.4---56# Bloodbank SDK Generation78Deterministic recipe for generating typed bindings from `bloodbank/schemas/bloodbank/v1/**`. Outputs are plain artifacts the consumer commits or vendors — this skill produces code, not an installable package.910## Prereqs1112- Run from the Bloodbank repo root (the directory containing `schemas/` and `docs/event-naming.md`), or pass the schema path explicitly.13- `uvx` (from `uv`) for Python tooling, `npx` for TypeScript tooling — both are project-less, no install required.14- Validate the tree is well-formed first: `mise run validate:schemas` (or `bash scripts/validate_schemas.sh`). Skip generation if it fails — fix schemas before generating.1516## Python (Pydantic v2 models)1718```bash19uvx --from 'datamodel-code-generator>=0.25' datamodel-codegen \20 --input schemas/bloodbank/v1 \21 --input-file-type jsonschema \22 --output-model-type pydantic_v2.BaseModel \23 --target-python-version 3.12 \24 --use-schema-description \25 --use-field-description \26 --use-double-quotes \27 --enum-field-as-literal one \28 --output bloodbank_contracts/models.py29```3031Flag rationale:3233- `--input-file-type jsonschema` — required; the default `auto` mis-detects when the tree has multiple files.34- `--enum-field-as-literal one` — `properties.type.const` becomes `Literal["bloodbank.v1.<...>"]`, matching the 5-token contract.35- `--use-schema-description` / `--use-field-description` — preserves the human-readable descriptions from the JSON Schemas as Pydantic field docstrings.36- `--target-python-version 3.12` — matches the project floor; bump if the project floor moves.3738The generator resolves `$ref: "../../../_common/cloudevent_base.v1.json"` correctly because all schemas live under one input root.3940## TypeScript (interfaces + type unions)4142```bash43npx json-schema-to-typescript \44 'schemas/bloodbank/v1/**/*.v1.json' \45 --cwd schemas \46 --bannerComment '' \47 --unreachableDefinitions \48 --additionalProperties false \49 > bloodbank_contracts/types.ts50```5152Flag rationale:5354- `--cwd schemas` — sets the resolver root so `$ref: "../../../_common/..."` resolves from each file's directory through the same root the schemas were authored against.55- `--bannerComment ''` — drops the autogenerated header that fights with linters.56- `--unreachableDefinitions` — emits the `_common` types even if no top-level schema references them by name, so consumers can import `CloudEventBase` directly.57- `--additionalProperties false` — matches the strict-shape default of the runtime validator.5859## Verify the output6061After generation, run a one-shot import smoke:6263```bash64# Python65python3 -c "from bloodbank_contracts.models import *; import bloodbank_contracts.models as m; print(f'{sum(1 for n in dir(m) if not n.startswith(\"_\")):>3} symbols')"6667# TypeScript (requires tsc)68npx tsc --noEmit --strict bloodbank_contracts/types.ts69```7071If either fails, regenerate from a clean `validate:schemas` pass before debugging the generator output.7273## Common combinations7475- **Refresh + commit on schema change**: re-run both generators whenever you edit anything under `bloodbank/schemas/bloodbank/v1/`. Treat the generated files as committed artifacts the consumer holds — git diff is the drift signal.76- **One-off consumer scaffold**: a downstream project that needs the types runs both commands once, vendors the outputs, and pins via the schema-tree commit hash recorded in their changelog.77- **Future packaging**: when a second external consumer materializes, promote `bloodbank_contracts/` into a real package (`uv build` for Python, `tsc --declaration` + `package.json` for TS). See `RECOMMENDATION.md` step 5 in the Bloodbank repo.7879## Gotchas8081Read `references/gotchas.md` only when generation fails with a non-obvious error, output looks wrong (missing fields, broken refs, unexpected types), or you're integrating the generated code into an existing project with its own Pydantic/Zod setup.8283## Out of scope8485This skill produces SDK artifacts from the canonical schema tree. It does NOT cover:8687- **Authoring or modifying schemas.** Edit JSON files under `bloodbank/schemas/` directly per `bloodbank/docs/event-naming.md` §12.88- **Validating envelopes at runtime.** Set `BLOODBANK_HOOK_VALIDATE=1` and rely on `services/agent-hooks/core/validate.py`, which uses a pre-loaded `referencing.Registry`.89- **Checking schema-tree consistency.** Run `mise run validate:schemas` (well-formedness + ref resolution) and `mise run smoketest:schema-contract-consistency` (validator allowlist drift).90- **The deprecated Holyfields generators** (`tools/generators/generate_pydantic.py`, `tools/generators/generate_typescript.sh`). Those are hand-rolled, locked to Holyfields' `packages/python` workspace, and superseded by the third-party tools above. Do not port them.91- **Apicurio registry uploads.** The `apicurio://holyfields/<type>/versions/1` `dataschema` URI is a registry artifact id, separate from this skill's filesystem generation path.92- **Publishing the SDK** (PyPI, npm, GitHub Packages). This skill writes files; distribution is a separate decision.93- **Zod schemas.** `json-schema-to-typescript` emits TypeScript types + interfaces, not Zod validators. If runtime TS validation is needed, layer a tool like `json-schema-to-zod` on top — but evaluate whether the type-only output is sufficient first.