API schema export
Ptah projects a desired schema into API-facing formats: OpenAPI 3.0 component schemas, GraphQL SDL, and Protobuf definitions. Use these artifacts when the selected database entities intentionally match your transport model, or as input to a separately designed contract.
The source can be a YAML, HCL, SQL, or DBML file named by --schema-file, or
Go annotations under --root-dir — the same desired-schema sources
ptah schema render reads.
The export does not expose database rows or create a working API. It does not generate handlers, resolvers, Protobuf services, authentication, authorization, or database access. The generated schema is a contract candidate that you must review before publishing.
Inspect the deterministic outputs generated from the canonical SQL common-subset fixture directly:
These source artifacts are primary. Ptah does not bundle Swagger UI, GraphiQL, resolvers, or an API server, so this page does not show a consumer UI and imply that one exists.
The generated OpenAPI passes redocly lint; the generated GraphQL parses and
builds with graphql-js, for the types-only default and for every operation
profile.
OpenAPI and GraphQL exports are stateless. The Protobuf target is stateful — field numbers are persistent wire identifiers, so its generated file is committed compatibility state — and has its own page: Protobuf schema export.
--to accepts seven targets in total, and the four this page does not cover
answer different questions. markdown and html write
reference documentation for people rather than a contract for
machines, and dbml writes a DBML document. hcl converts a Go
annotation tree into an HCL schema, and with --cleanup-go-annotations it also
removes the annotations from the Go files it reads.
Commands
Section titled “Commands”# OpenAPI 3.0 — components.schemas keyed by table nameptah schema export --to openapi-v3 --schema-file schema.yaml --out openapi.yaml
# GraphQL SDL — one object type per table, and no operationsptah schema export --to graphql --schema-file schema.yaml --out schema.graphql
# Add operation shapes by nameptah schema export --to graphql --schema-file schema.yaml \ --graphql-operations list,by-id,create-input --out schema.graphql
# Omit --out to write the schema to stdout (for piping into a validator)ptah schema export --to graphql --schema-file schema.yaml > schema.graphql
# Go annotations use --root-dir insteadptah schema export --to openapi-v3 --root-dir ./models --out openapi.yaml| Flag | Applies to | Meaning |
|---|---|---|
--to |
all | hcl, openapi-v3, graphql, protobuf, markdown, html, or dbml. atlas-hcl is accepted as an alias for hcl. |
--from |
all | Format of the --schema-file value: go (default), yaml, hcl, sql, or dbml. |
--root-dir |
all | Directory scanned for Go annotations. |
--schema-file |
every target except hcl |
YAML, HCL, SQL, or DBML schema file to export instead of Go annotations. Repeatable. |
--out |
all | Output file. Optional for openapi-v3, graphql, markdown, html, and dbml (stdout when omitted); required for hcl and for protobuf, where it is also the compatibility state read back on the next run. |
--include-tables |
every target except hcl |
Comma-separated allowlist of tables. |
--exclude-tables |
every target except hcl |
Comma-separated denylist, applied after the allowlist. |
--title |
openapi-v3, markdown, html |
Value for info.title (default Ptah Exported Schema), or the heading of a Markdown or HTML document (default Schema reference). |
--graphql-operations |
graphql |
Comma-separated operation shapes: none (default), list, by-id, create-input, update-input. |
Export warnings (for example an enum whose values cannot be resolved) are written to stderr, so a schema piped from stdout is never corrupted.
Sources
Section titled “Sources”--schema-file is read by the same resolver as ptah schema render, so a
desired schema is spelled the same way on both commands. Every target except
hcl reads all five sources below:
- YAML schema — a
--schema-filewhose extension is.yamlor.yml. This is--from yaml. - HCL schema — a
--schema-filewhose extension is.hcl. This is--from hcl. - SQL schema — a
--schema-filewhose extension is.sql. This is--from sql. - DBML document — a
--schema-filewhose extension is.dbml. This is--from dbml. - Go annotations — the directory named by
--root-dir, which defaults to.. This is--from go.
For semantics both sources can express, an export taken from a schema file is byte-identical to the export taken from annotated Go models describing the same tables. YAML, HCL, and Go can also carry the API metadata documented below. SQL and DBML still produce contracts, but their names and API types are derived from storage because those formats have no lossless spelling for that metadata.
--from declares the file’s format and is checked against its extension:
--from yaml --schema-file schema.sql is refused rather than parsed as YAML.
Leave --from unset and the extension decides. Naming both --root-dir and
--schema-file merges them into one
composite desired schema; --root-dir alone keeps its .
default, which a schema-file export never picks up.
Two combinations are refused rather than approximated:
--to hclwith--schema-file. That target rewrites the Go files it reads —--cleanup-go-annotationsremoves their annotations after writing HCL — so its source is--root-dir. The canonical HCL preserves the API names, API type overrides, and exposure declarations documented below. Cleanup still refuses before writing or removing annotations if any other modeled value would be lost or if the generated HCL does not parse and render back to the same canonical bytes. Converting a schema file to HCL is a different operation from migrating annotations out of Go code.--from db. An export reads a schema definition, not a live database. Runptah schema inspectto write HCL, SQL, or DBML from a database URL, review the file, then export it.
OpenAPI
Section titled “OpenAPI”Each table becomes one Schema Object under components.schemas. Columns become
properties; NOT NULL columns (and primary keys, which are NOT NULL by rule)
go in required; nullable columns get nullable: true.
openapi: 3.0.3info: title: Ptah Exported Schema version: 1.0.0servers: - url: /paths: {}components: schemas: products: type: object required: - id - name - price - status properties: id: type: integer format: int32 name: type: string maxLength: 255 price: type: number status: type: string enum: - active - inactiveThe document is minimal but valid: paths is empty and a placeholder servers
entry is included so redocly lint passes. components.schemas can be $ref’d
from, or merged into, a hand-authored specification.
GraphQL
Section titled “GraphQL”Each table becomes an object type. Enum columns become enum types, and foreign
keys become object relations alongside the scalar id column. That is the whole
default export: no Query, no inputs, no connections.
scalar DateTime
enum ProductStatus { active inactive}
type Product { id: ID! name: String! price: Float! status: ProductStatus! category_id: Int! category: Category!}A foreign key whose target table is filtered out is dropped and reported as a warning rather than producing a dangling reference.
The document declares no root operation type, so graphql-js builds it and
validateSchema reports the absent Query. That is the correct shape for a
type-system document meant to be composed into a schema you design.
Operation shapes
Section titled “Operation shapes”--graphql-operations adds operation-shaped definitions by name. Each value is
selected independently.
| Value | What it adds |
|---|---|
none |
Nothing; this is the default written out. |
list |
A Connection/Edge pair per table, a shared PageInfo, and a Query field <tables>(first: Int, after: String). |
by-id |
A Query field <table>(<key>: <KeyType>!) per table with a single-column primary key. |
create-input |
A <Type>CreateInput per table. |
update-input |
A <Type>UpdateInput per table, without the primary key and with every field optional. |
type Query { products(first: Int, after: String): ProductConnection product(id: ID!): Product}
input ProductCreateInput { name: String! price: Float! status: ProductStatus category_id: Int!}
input ProductUpdateInput { name: String price: Float status: ProductStatus category_id: Int}Inputs come from the write projection: the columns a caller may assign.
Auto-increment and SERIAL columns, GENERATED ALWAYS AS IDENTITY columns,
generated/computed columns, and columns with a MySQL ON UPDATE expression are
excluded, because the database produces their values. A column with a plain
DEFAULT stays but becomes optional on create — status above.
An operation Ptah cannot complete is omitted and reported, never emitted broken:
a composite or absent primary key gets no by-id field, a key column that the
object type did not publish gets none either, and an empty projection produces
no input type. Selecting only input shapes leaves the document without a Query;
selecting a query shape always produces a legal, non-empty one.
Names in the contract
Section titled “Names in the contract”A column or table has two identities, and they are not the same thing:
- the persistence identity — the column and table names the database uses;
- the API identity — the names the exported contract publishes.
By default the second is derived from the first, which couples them: renaming a column renames a published field, and a storage-shaped name has to become the public one.
YAML, HCL, and Go annotations can author every export-metadata attribute. SQL and DBML can still produce all three contracts, but their published identity is derived from storage. Ptah does not overload SQL comments, DBML notes, or DBML presentation settings with product metadata that those formats cannot preserve losslessly.
| Source | Support | Limitation |
|---|---|---|
| SQL file | unsupported-design |
SQL DDL cannot carry Ptah API names, type overrides, or exposure without overloading comments or unrelated storage syntax. |
| YAML file | supported |
Tables and columns carry the documented Ptah API export-metadata keys. |
| HCL file | supported |
Ptah table and column attributes carry API export metadata through canonical HCL round trips. |
| DBML file | unsupported-design |
Ptah refuses attempted API export metadata instead of overloading DBML notes or presentation settings. |
| Go annotations | supported |
Table and field annotations carry every API export-metadata attribute. |
| External program | declared-format |
YAML and HCL payloads carry API export metadata; SQL payloads do not. |
| Configured external source | declared-format |
YAML and HCL payloads carry API export metadata; SQL payloads do not. |
| OCI artifact | supported |
Canonical HCL preserves API export metadata across push and pull. |
| Live database | not-applicable |
Database catalogs do not store Ptah API export metadata. |
| Migration directory | not-applicable |
Migration replay reconstructs database state, which does not contain Ptah API export metadata. |
| Composite source | component-dependent |
YAML, HCL, and Go components can own metadata; SQL and DBML components cannot act as metadata overlays. |
An external loader inherits the expressiveness of its declared
--schema-format: YAML and HCL payloads carry the attributes below; SQL does
not. ptah schema export itself does not execute --schema-cmd or a configured
loader. Materialize that payload first, then pass it through --schema-file.
An OCI schema stores canonical HCL, so a push/pull round trip preserves the
metadata. A composite schema retains metadata on each complete YAML, HCL, or Go
definition and refuses conflicting definitions; a SQL or DBML component cannot
serve as a metadata-only overlay.
Declare api_name on a table or column when the persistence and API identities
should differ:
tables: billing_invoices: api_name: invoices columns: id: type: BIGSERIAL primary: true billing_amount_minor: type: INTEGER api_name: amounttable "billing_invoices" { api_name = "invoices"
column "id" { type = bigint }
column "billing_amount_minor" { type = integer api_name = "amount" }}//ptah:schema:table name="billing_invoices" api_name="invoices"type Invoice struct { //ptah:schema:field name="id" type="BIGSERIAL" primary="true" ID int64
//ptah:schema:field name="billing_amount_minor" api_name="amount" type="INTEGER" AmountMinor int32}The table exports as invoices in OpenAPI and as type Invoice in GraphQL —
singularized and PascalCased from the API name exactly as it would be from a
table name — and the column exports as amount in both.
Without an explicit API name, each exporter derives the published name from the table or column name.
What it does not do
Section titled “What it does not do”- It does not change a type.
api_namerenames, and it adds no runtime validation of any kind. Re-mapping the exported representation is a separate declaration — see Types in the contract. - It does not bypass a format’s naming rules. Names derived from persistence or
from the shared
api_namefollow the target’s normal normalization. An explicitgraphql_nameorproto_nameis a deliberate target identifier and an invalid value fails before output. An invalid tableopenapi_namealso fails instead of producing an invalid component key. - It does not rename the diagnostics you read. A GraphQL or Protobuf diagnostic path names the column, because that is where you go to change something. An OpenAPI diagnostic path names the published names, because that path is a coordinate inside the generated document.
One name per target, where a format requires it
Section titled “One name per target, where a format requires it”api_name answers every target, and that is what a schema normally declares.
A format’s own naming rules are the case it cannot cover: amountMinor is
idiomatic in GraphQL and fails buf lint in Protobuf, which wants
amount_minor. YAML, HCL, and Go annotations provide one shared attribute and
three target-specific attributes on both tables and columns:
| Attribute | Scope | Export target |
|---|---|---|
api_name |
table and column | Shared fallback for OpenAPI, GraphQL, and Protobuf. |
openapi_name |
table and column | OpenAPI only. |
graphql_name |
table and column | GraphQL only. |
proto_name |
table and column | Protobuf only. |
tables: billing_invoices: api_name: invoices openapi_name: invoice_documents graphql_name: invoice_records proto_name: invoice_records columns: billing_amount_minor: type: INTEGER api_name: amount openapi_name: amount_value graphql_name: amountMinor proto_name: amount_minortable "billing_invoices" { api_name = "invoices" openapi_name = "invoice_documents" graphql_name = "invoice_records" proto_name = "invoice_records"
column "billing_amount_minor" { type = integer api_name = "amount" openapi_name = "amount_value" graphql_name = "amountMinor" proto_name = "amount_minor" }}//ptah:schema:table name="billing_invoices" api_name="invoices" openapi_name="invoice_documents" graphql_name="invoice_records" proto_name="invoice_records"type Invoice struct { //ptah:schema:field name="billing_amount_minor" type="INTEGER" api_name="amount" openapi_name="amount_value" graphql_name="amountMinor" proto_name="amount_minor" AmountMinor int32}Names resolve in order — the target’s own name, api_name, then the database
name. A shared alias therefore answers every target, while a scoped name changes
one export without touching the other two. A target with no scoped declaration
is unaffected by one declared for its neighbor.
Table and column declarations do not use target names in exactly the same way:
- A table
openapi_nameis the exact OpenAPI component key. A tablegraphql_nameorproto_nameis a stem: Ptah singularizes and PascalCases it into the final GraphQL type or Protobuf message name. For example,invoice_recordsbecomesInvoiceRecord. - A column
openapi_nameis the exact OpenAPI property key. A columngraphql_nameis the exact GraphQL field identifier, and a columnproto_nameis the exact lower-snake-case Protobuf field name.
Invalid explicit target names fail before a file is written. That is different
from target normalization of a persistence name or shared api_name: explicit
target names state the intended contract spelling, so Ptah does not silently
replace them with a different one.
Collisions are checked per target, because a collision can now exist in one export and not in another. The refusal says which export refused, and names both attributes that would resolve it:
table "invoices" exports two columns as "net" in GraphQL: "net_minor" and"gross_minor"; give one of them a distinct api_name, or a distinct graphql_namefor this export onlyCollisions are refused, not resolved
Section titled “Collisions are refused, not resolved”Two columns published under one name, or two tables, fail the export before anything is written, and the message names both sources:
table "invoices" exports two columns as "amount": "amount" and"billing_amount_minor"; give one of them a distinct api_nameAn alias that shadowed another column would drop it from the contract, and the contract has nothing in it to record the loss.
The Protobuf consequence
Section titled “The Protobuf consequence”Protobuf field numbers are keyed by the published field name, so the API
identity is what carries wire compatibility: renaming a column while its
api_name stays put keeps the field number. Changing the api_name retires an
identity consumers hold and is refused unless the retirement is chosen
explicitly. See Protobuf schema export.
Types in the contract
Section titled “Types in the contract”The type mapping below decides the shape of every exported field, and it decides it from the stored type. That is the right answer nearly always, and the wrong one exactly where the stored type is not what the value means:
- an exact
DECIMALamount arrives as OpenAPInumberand GraphQLFloat, both double-precision, so the digits are not guaranteed to survive; - a
TIMESTAMPcarries no time zone, so Protobuf declines to publish it asgoogle.protobuf.Timestamp— nothing in the schema says which zone it is in, though the person who wrote the schema knows.
Declare api_type where the published representation should differ from the
stored one. It names a type Ptah already maps — the left column of the table
below, or a declared enum — so one declaration answers all three targets and no
per-format type vocabulary is introduced:
tables: invoices: columns: amount: type: DECIMAL(12,2) api_type: TEXT stored_utc_at: type: TIMESTAMP api_type: TIMESTAMPTZtable "invoices" { column "amount" { type = decimal(12, 2) api_type = "TEXT" }
column "stored_utc_at" { type = timestamp api_type = "TIMESTAMPTZ" }}//ptah:schema:field name="amount" type="DECIMAL(12,2)" api_type="TEXT"Amount string
//ptah:schema:field name="stored_utc_at" type="TIMESTAMP" api_type="TIMESTAMPTZ"StoredUTCAt time.TimeThe first publishes as a string in all three formats and keeps its digits. The
second publishes as google.protobuf.Timestamp in Protobuf, pulling in the
import that goes with it, and as a date-time string in OpenAPI and GraphQL.
Only the contract moves. type is what the migration engine plans against, and
it is unaffected: the column stays DECIMAL(12,2) in the database, and
migration planning and apply never read api_type.
Enums, in both directions
Section titled “Enums, in both directions”An override reaches enum resolution as well as the scalar mapping, so it works whichever way the pair sits:
columns: state: type: invoice_state api_type: TEXT legacy_state: type: VARCHAR(32) api_type: invoice_statecolumn "state" { type = enum.invoice_state api_type = "TEXT"}
column "legacy_state" { type = varchar(32) api_type = "invoice_state"}//ptah:schema:field name="state" type="invoice_state" api_type="TEXT"State string
//ptah:schema:field name="legacy_state" type="VARCHAR(32)" api_type="invoice_state"LegacyState stringThe first publishes an enum column as a plain string. The second publishes a text column as the declared enum — which is the case worth having, because on a dialect with no native enum type the column genuinely is text, and the contract still wants the enum.
Inline enum values are part of what the override replaces. A column declaring
its values on the annotation and an api_type beside them publishes the
api_type: the values describe what is stored, and enum resolution would
otherwise consult them first and quietly make the override do nothing.
What an override does not do
Section titled “What an override does not do”It does not convert anything. api_type changes what the contract says a
field is; it generates no marshalling, no parsing, and no validation of any
kind at runtime. A DECIMAL published as text is still a DECIMAL in the
database, and the service that serves the contract is what has to render it as
text and read it back — an override that nothing implements produces a document
that lies about the API.
Nor does it check that the two types are compatible. Publishing a DECIMAL as
BIGINT is accepted, because there are schemas where that is correct and Ptah
cannot tell them apart from the ones where it is not. Review the pair; the
export only guarantees that the type you named is one it can produce.
An override that cannot be honored is refused
Section titled “An override that cannot be honored is refused”An api_type the target cannot map fails the export, naming the column, the
declared value and the type it would otherwise have used:
column "amount" on table "invoices" declares api_type "money_ish", which theOpenAPI projection does not recognize; name a type Ptah maps, or drop theoverride to keep the column's own type "DECIMAL(12,2)"A type this refuses is one no target can produce: neither the mapping table nor the declared enums have it. This is deliberately stricter than the treatment of an unrecognized column type, which exports as a string with a diagnostic. The two are different kinds of event: an unmapped column type is a fact about the schema, and the export still has something honest to say about it, while an unmapped override is an authoring mistake whose only possible outcome is a contract the author did not ask for.
The Protobuf consequence
Section titled “The Protobuf consequence”A pinned api_type makes the wire type independent of the column: re-typing the
column underneath it changes nothing consumers can observe. Changing the
api_type itself is a wire-incompatible change like any other, and goes through
the same policy — refused unless --proto-on-incompatible-change=renumber
reserves the old number and allocates a new one.
Type mapping
Section titled “Type mapping”The lookup is dialect-agnostic: Postgres and MySQL spellings (SERIAL,
INT AUTO_INCREMENT, DOUBLE PRECISION) normalize to the same result.
| Ptah type | OpenAPI (type/format) |
GraphQL |
|---|---|---|
SMALLINT, INT, SERIAL, INT AUTO_INCREMENT |
integer / int32 |
Int |
BIGINT, BIGSERIAL |
integer / int64 |
Int |
BOOLEAN |
boolean |
Boolean |
DECIMAL(p,s), NUMERIC, REAL, DOUBLE PRECISION |
number |
Float |
VARCHAR(n), CHAR(n) |
string (maxLength: n) |
String |
TEXT, UUID, INET, … |
string |
String |
DATE, TIMESTAMP, TIME |
string / date-time (or date) |
DateTime (custom scalar) |
JSON, JSONB |
object |
JSON (custom scalar) |
| enum column | string + enum |
enum type |
| single-column primary key | as above, in required |
ID! |
An unrecognized column type maps to string/String and emits a warning, so an
unresolved custom type is visible rather than silently wrong.
What the projection does not preserve
Section titled “What the projection does not preserve”The generated contract is not a complete translation of database behavior:
- OpenAPI and GraphQL do not carry database defaults, unique constraints, check constraints, generated expressions, or transaction behavior. Enforce the API rules in the implementing service even when the database also enforces them.
- GraphQL
Intis a signed 32-bit value. A non-primary-keyBIGINTmaps toInt, so values outside that range need a separately designed scalar or contract, or anapi_typethat publishes them as text. DECIMALandNUMERICmap to OpenAPInumberand GraphQLFloat. Those representations can lose decimal precision; declareapi_typeto publish the value as text instead.- GraphQL declares
DateTimeandJSONscalar names but does not provide their parsing, serialization, or validation behavior. - The GraphQL write projection recognizes auto-increment,
SERIAL,GENERATED ALWAYS AS IDENTITY, generated/computed, and MySQLON UPDATEcolumns as server-owned. It cannot see a value supplied by a trigger or by an application layer, so review every generated input field. - Foreign-key relation fields describe a possible object shape. They do not define loading, batching, tenant checks, or authorization.
Scope and limitations
Section titled “Scope and limitations”The export describes selected table columns, primary keys, foreign keys, and enums. Non-column objects such as views, triggers, functions, row-level security (RLS) policies, and indexes are not emitted.
Database and API models solve different problems. A database model can contain tenant identifiers, audit fields, internal states, credential material, and operational columns that no external caller should see. It can also change on a different schedule from a public API.
Use direct projection for an internal contract or a deliberately isomorphic domain model. Use a curated API model when the contract crosses a trust boundary, must remain stable across storage refactors, or exposes only part of a row.
Field-level exposure
Section titled “Field-level exposure”--include-tables and --exclude-tables select whole tables. To select
columns, declare api_expose on the field:
| Value | Reaches |
|---|---|
read |
What the API returns |
write |
What the API accepts |
read-write |
Both |
none |
Neither — the column is absent from the generated document |
tables: accounts: columns: password_hash: type: TEXT api_expose: writetable "accounts" { column "password_hash" { type = text api_expose = "write" }}//ptah:schema:field name="password_hash" type="TEXT" api_expose="write"PasswordHash stringEach target expresses the two directions in its own vocabulary. OpenAPI marks
the single schema’s properties readOnly and writeOnly; GraphQL puts a read
column on the object type and a write column on the input types; Protobuf
carries one message, so a column reachable either way is in it. A column
declared none is emitted by none of them.
--api-field-policy decides what an undeclared column means:
| Value | Meaning |
|---|---|
all (default) |
An undeclared column is exported, which is how every schema behaved before this existed |
allowlist |
Only columns declaring api_expose are exported; every other one is withheld and reported |
Use allowlist when the contract crosses a trust boundary. It is what stops an
additive database migration from widening a published contract on its own: a
column added tomorrow enters nothing until somebody declares that it should.
The generated surface differs by target:
| Target | Ptah emits | Ptah does not emit |
|---|---|---|
| OpenAPI | Component schemas under components.schemas |
Paths, operations, handlers, or authorization |
| GraphQL | Object and enum types; input, connection, and Query shapes when --graphql-operations asks for them |
Resolvers, a server, data access, or authorization |
| Protobuf | Messages and enums | Services, remote procedure calls (RPCs), handlers, or authorization |
Publishing any generated schema reveals the selected entity and field names, translated types, enum values, relations, and exported source comments. Schema metadata is not an authorization boundary, but you should not disclose internal metadata that consumers do not need.
RLS omission is important: the generated schema cannot describe who may read or write a field or row. Keep authorization in the service that implements the contract. Do not infer API permissions from database constraints or from the presence or absence of a generated field.
Database identifiers become API identifiers after target-specific
normalization when no API name is declared. Use api_name to keep a shared API
identity stable across a storage rename, or a target-specific name to pin only
one generated format. Changing that published identity is itself a contract
rename.
GraphQL operation-shaped definitions do not grant access by themselves, but they can be wired unsafely. They are opt-in for that reason: request the shapes you have decided to implement, and do not pass generated input objects directly to persistence code without an explicit assignment allowlist.
Review before publishing
Section titled “Review before publishing”Before publishing or generating runtime code from an export:
- Use
--include-tablesrather than exporting the entire model by default. - Inspect every generated field, including identifiers, audit columns, and server-managed values. Review exported table and field comments too.
- If a selected table contains a field that must not cross the trust boundary, use a curated source model or a separately authored contract. Table filters cannot remove individual fields.
- Define authentication, authorization, tenant isolation, validation, and assignment rules in the implementing service.
- Review the generated diff when the database model changes. An additive database column can be an additive but unintended API change.
- Run the target linter or compiler and the consumer compatibility tests before publishing the artifact.
For OpenAPI, merge selected components into a hand-authored specification when
the public contract differs from storage. For GraphQL, start from the types-only
default and add an operation shape only once you have decided how it will be
resolved and authorized; the generated Query and input types are syntax, not
an authorization or resolver design.
For Protobuf, use generated messages behind separately designed services or
wrapper messages when the public model must differ.
Next steps
Section titled “Next steps”- Need a stateful wire contract? Use Protobuf schema export.
- Need to verify what a source declares? Review YAML, HCL, or Go annotations, and the SQL and DBML limitations.
- Publishing the artifact from a pull request? Add it to Continuous integration.