protean schema
The protean schema command group generates and inspects JSON Schema
(Draft 2020-12) documents for data-carrying domain elements: aggregates,
entities, value objects, commands, events, and projections.
All commands accept either --domain (live domain module) or --ir
(serialized IR file) as input.
Commands
| Command | Description |
|---|---|
protean schema generate |
Generate JSON Schema files for all data-carrying elements |
protean schema show |
Display the JSON Schema for a specific element |
protean schema render |
Render index DDL artifacts (with --indexes) |
protean schema generate
Generates JSON Schema files for every data-carrying element in the domain and writes them to the output directory.
# From a live domain
protean schema generate --domain=my_app.domain
# From a serialized IR file
protean schema generate --ir=domain-ir.json
# Custom output directory
protean schema generate --domain=my_app.domain --output=build
Options
| Option | Description | Default |
|---|---|---|
--domain, -d |
Path to the domain module (e.g. my_app.domain) |
|
--ir |
Path to an IR JSON file | |
--output, -o |
Root output directory | .protean |
--format, -f |
Output format: json, avro, protobuf, or all |
json |
Either --domain or --ir is required; they are mutually exclusive.
Formats. --format avro emits Avro {name}.v{version}.avsc records (null-first
unions for optionals, so optional fields default to null; uuid/date/timestamp-millis
logical types; nested value objects become named records referenced by fullname).
--format protobuf emits proto3 {name}.v{version}.proto messages with
optional/repeated labels, google.protobuf.Timestamp for dates, and
field numbers assigned deterministically in sorted field-name order — so
re-generating an unchanged schema is byte-identical. Adding a field
renumbers alphabetically-later fields, so these numbers are for emission, not
wire-compatible evolution. Both are compiler backends for schema emission;
runtime binary encode/decode is out of scope, and field-level constraints
(choices/enums, min/max) are not carried into either format.
--format all emits every format into one schemas/ tree in a single pass —
JSON, Avro, and Protobuf files coexist per element (distinct extensions) rather
than one clobbering the next. Together with protean events catalog,
this is the local schema-registry on-ramp.
Output structure
Schemas are written to <output>/schemas/, grouped by aggregate cluster:
.protean/
├── ir.json
└── schemas/
├── Order/
│ ├── aggregates/
│ │ └── Order.v1.json
│ ├── commands/
│ │ └── PlaceOrder.v1.json
│ ├── entities/
│ │ └── LineItem.v1.json
│ ├── events/
│ │ └── OrderPlaced.v2.json
│ └── value_objects/
│ └── Money.v1.json
└── projections/
└── OrderDashboard.v1.json
Filenames include the element version for events and commands. Other elements
default to v1. The schemas/ directory is cleared on each run to remove
stale files.
The ir.json written alongside the schemas is a canonical baseline (the
volatile generated_at timestamp is omitted), byte-identical to
protean ir show --canonical, so a committed .protean/ir.json only changes
when the domain contract changes.
protean schema show
Displays the JSON Schema for a single element, looked up by short name or fully qualified name (FQN).
# By short name
protean schema show OrderPlaced --domain=my_app.domain
# By FQN
protean schema show my_app.ordering.OrderPlaced --domain=my_app.domain
# Raw JSON (for piping)
protean schema show OrderPlaced --domain=my_app.domain --raw
# From an IR file
protean schema show OrderPlaced --ir=domain-ir.json --raw
With --raw, the JSON is written to stdout while logs go to stderr, so the
output is safe to pipe (protean schema show ... --raw | jq).
Options
| Option | Description | Default |
|---|---|---|
--domain, -d |
Path to the domain module | |
--ir |
Path to an IR JSON file | |
--raw |
Output plain JSON without syntax highlighting | false |
Arguments
| Argument | Description |
|---|---|
element |
Element short name (e.g. OrderPlaced) or FQN (e.g. app.OrderPlaced) |
Disambiguation
If multiple elements share the same short name, the command lists all matching FQNs and asks you to use the full FQN:
Multiple elements match 'Order':
ecommerce.ordering.Order
ecommerce.shipping.Order
Use the full FQN to disambiguate.
protean schema render
Renders the index declarations on a domain's aggregates and entities to
per-dialect CREATE INDEX DDL files. It never executes DDL — it writes .sql
artifacts for review or for application through your own migration tooling.
# Render index DDL for the default dialects
protean schema render --indexes --domain=my_app.domain
# Restrict to specific dialects
protean schema render --indexes --domain=my_app.domain --dialects=postgresql,sqlite
# Custom output directory
protean schema render --indexes --domain=my_app.domain --output=build
Options
| Option | Description | Default |
|---|---|---|
--indexes |
Render index DDL (required; nothing is applied without it) | false |
--domain, -d |
Path to the domain module (e.g. my_app.domain) |
|
--dialects |
Comma-separated dialects to render | postgresql,sqlite,mssql |
--output, -o |
Root output directory | .protean |
--indexes requires --domain: rendering partial-index predicates needs the
live Index declarations (the Q objects), which an IR file does not carry.
Output structure
One .sql file per element per dialect is written under
<output>/schemas/<cluster>/:
.protean/
└── schemas/
└── Order/
├── order.indexes.postgresql.sql
└── order.indexes.sqlite.sql
Each file is headed with the element FQN and dialect, followed by the
CREATE INDEX statements. Dialects that do not support a partial where= or
covering include= emit a full index instead. See
Indexes for the declaration syntax and dialect
support matrix.
Schema format
Generated schemas follow JSON Schema Draft 2020-12 with x-protean-*
extension fields for domain metadata. See the
Schema Generation guide
for the full schema structure reference, including extension fields, nested
$defs/$ref resolution, and optional field handling.
Error handling
| Condition | Behavior |
|---|---|
Neither --domain nor --ir provided |
Aborts with error message |
Both --domain and --ir provided |
Aborts with "mutually exclusive" error |
| Invalid domain path | Aborts with "Error loading Protean domain" |
| IR file not found or invalid JSON | Aborts with descriptive error |
Element not found (for show) |
Lists all available elements and aborts |
Domain discovery
The --domain option uses the same domain discovery mechanism as other
Protean commands. See Domain Discovery for the full
resolution logic.