Code Generation
Each target — SQL, TypeScript, Go, JSON Schema, GraphQL, OpenAPI.
The gen command turns a schema into source files for a chosen target.
openschema gen <schema> --target <target> --out <dir> [options]
| Flag | Meaning | Default |
|---|---|---|
--target, -t |
sql, ts, go, json-schema, graphql, or openapi (required) |
— |
--out, -o |
output directory (created if missing) | . |
--company <id> |
include this company's overlay fields | none |
--include-private |
include the schema's own private fields |
off |
The command parses the entry file, follows its imports, resolves the whole
graph, reports any semantic errors (and stops on them), then writes the output.
Each target writes a single schema.<ext> file.
The examples below all use this source:
namespace shop
enum Status { 1 pending 2 shipped }
@table("orders")
model Order {
@primaryKey @default(gen_uuid())
1 id: uuid
2 status: Status
@minValue(0)
3 total: decimal(12, 2)
4 tags: [string]
5 note: string?
}
SQL (PostgreSQL)
openschema gen order.schema --target sql --out ./out
CREATE TABLE orders (
id UUID NOT NULL PRIMARY KEY DEFAULT gen_uuid(),
status TEXT NOT NULL,
total NUMERIC(12, 2) NOT NULL,
tags JSONB NOT NULL,
note TEXT
);
Type mapping:
| OpenSchema | SQL |
|---|---|
bool |
BOOLEAN |
i8 i16 |
SMALLINT |
i32 u16 |
INTEGER |
i64 u32 |
BIGINT |
u64 |
NUMERIC(20) |
f32 |
REAL |
f64 |
DOUBLE PRECISION |
decimal(p, s) |
NUMERIC(p, s) |
string uuid date time timestamp duration |
TEXT/UUID/DATE/TIME/TIMESTAMPTZ/INTERVAL |
bytes |
BYTEA |
an enum type |
TEXT |
[T], {K: V}, unions, oneof |
JSONB |
- A non-nullable field becomes
NOT NULL; aT?field omits it. @table,@sql.type,@sql.column,@primaryKey,@unique,@default,@check, and@referencesall shape the output — see Decorators.- Table and column names are snake-cased (
UserAccount→user_account) unless overridden.
TypeScript
openschema gen order.schema --target ts --out ./out
export enum Status {
pending = 1,
shipped = 2,
}
export interface Order {
id: string;
status: Status;
total: number;
tags: string[];
note?: string | null;
}
Type mapping:
| OpenSchema | TypeScript |
|---|---|
bool |
boolean |
all integers, floats, decimal |
number |
string uuid date time timestamp duration |
string |
bytes |
Uint8Array |
[T] |
T[] |
{K: V} |
Model<K, V> |
T? |
field?: T | null |
union A | B |
A | B |
oneof { 1 a: A 2 b: B } |
{ kind: "a"; a: A } | { kind: "b"; b: B } |
enum |
export enum with the ordinal as the value |
Enum values preserve the ordinal (pending = 1), so the TypeScript enum and the
wire representation agree. Generic models emit as generic interfaces
(export interface Page<T>).
Go
openschema gen order.schema --target go --out ./out
package schema
type Status int32
const (
StatusPending Status = 1
StatusShipped Status = 2
)
type Order struct {
Id string `json:"id"`
Status Status `json:"status"`
Total string `json:"total"`
Tags []string `json:"tags"`
Note *string `json:"note,omitempty"`
}
Type mapping:
| OpenSchema | Go |
|---|---|
bool |
bool |
i8..i64, u8..u64 |
int8..int64, uint8..uint64 |
f32 f64 |
float32 float64 |
decimal |
string (no native decimal) |
string and friends |
string |
bytes |
[]byte |
[T] |
[]T |
{K: V} |
map[K]V |
T? |
*T with json:"...,omitempty" |
enum |
type X int32 + const block |
Field names are exported (capitalized) and carry a json tag with the original
field name. Set the package name with --out plus a generator option if needed;
the default package is schema.
JSON Schema
openschema gen order.schema --target json-schema --out ./out
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"Status": { "type": "string", "enum": ["pending", "shipped"] },
"Order": {
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"status": { "$ref": "#/$defs/Status" },
"total": { "type": "string", "minimum": 0 },
"tags": { "type": "array", "items": { "type": "string" } },
"note": { "type": "string" }
},
"required": ["id", "status", "total", "tags"]
}
}
}
- Every model and enum becomes an entry under
$defs; references between them use$ref. - A non-nullable field appears in
required; aT?field does not. - Validation decorators map to standard keywords:
@format→format,@minValue/@maxValue→minimum/maximum,@minLength/@maxLength→minLength/maxLength,@pattern→pattern. - Private fields are excluded by default (this is a public artifact). Pass
--include-privateto keep them.
GraphQL
openschema gen order.schema --target graphql --out ./out
scalar UUID
scalar DateTime
scalar Date
scalar Time
scalar Duration
scalar Decimal
scalar Bytes
scalar JSON
enum Status {
pending
shipped
}
type Order {
id: UUID!
status: Status!
total: Decimal!
tags: [String!]!
note: String
}
GraphQL is nullable by default, the opposite of OpenSchema. So the generator
inverts nullability: a required field gains a trailing !, and a T? field
does not. note: String is optional; everything else is !.
Type mapping highlights:
| OpenSchema | GraphQL |
|---|---|
bool |
Boolean |
| integers | Int |
f32 f64 |
Float |
decimal |
Decimal (custom scalar) |
uuid |
UUID (custom scalar) |
timestamp |
DateTime (custom scalar) |
[T] (non-null) |
[T!]! |
{K: V}, unions, oneof |
JSON (custom scalar) |
enum |
enum (ordinals dropped — GraphQL enums are name-only) |
Operations become Query and Mutation
If your schema declares operations, they generate root types:
@query op getOrder(id: uuid): Order
@mutation op placeOrder(order: Order): Order
type Query {
getOrder(id: UUID!): Order!
}
type Mutation {
placeOrder(order: OrderInput!): Order!
}
Notice OrderInput. GraphQL forbids using an output type where an input is
expected, so any model used as an operation parameter automatically gets a
companion input type — and so does any model it references, transitively.
Models used only as return values stay as type. Enums work in both positions
and are never suffixed.
OpenAPI
openschema gen api.schema --target openapi --out ./out
The OpenAPI generator turns operations
into an OpenAPI 3.0 document: paths from the operations and components.schemas
from the models and enums. It is driven by the
HTTP decorators @get/@post/...
and @route.
Given:
namespace shop
model User {
@visibility("read")
1 id: uuid
@visibility("create", "read")
2 name: string
@visibility("create", "update")
3 password: string
}
@get @route("/users/{id}")
op getUser(id: uuid): User
@post @route("/users")
op createUser(user: User): User
the generator produces (abridged):
{
"openapi": "3.0.3",
"info": { "title": "shop API", "version": "1.0.0" },
"paths": {
"/users/{id}": {
"get": {
"operationId": "getUser",
"parameters": [
{ "name": "id", "in": "path", "required": true,
"schema": { "type": "string", "format": "uuid" } }
],
"responses": {
"200": { "description": "Success",
"content": { "application/json": {
"schema": { "$ref": "#/components/schemas/User" } } } }
}
}
},
"/users": {
"post": {
"operationId": "createUser",
"requestBody": { "required": true,
"content": { "application/json": {
"schema": { "$ref": "#/components/schemas/UserInput" } } } },
"responses": { "200": { "description": "Success",
"content": { "application/json": {
"schema": { "$ref": "#/components/schemas/User" } } } } }
}
}
},
"components": {
"schemas": {
"User": { "type": "object",
"properties": { "id": { "type": "string", "format": "uuid" },
"name": { "type": "string" } },
"required": ["id", "name"] },
"UserInput": { "type": "object",
"properties": { "name": { "type": "string" },
"password": { "type": "string" } },
"required": ["name", "password"] }
}
}
}
Key behaviors:
- Method and path come from
@get/@post/... and@route(...). Defaults:GET(orPOSTwith@mutation) and/opName. - Path parameters are the
{...}placeholders in the route; remaining parameters are query parameters (GET) or the request body (POST/PUT/PATCH). - Visibility splits the schemas. A model has an output schema (
User, theread-visible fields) and, when used as a request body, an input schema (UserInput, thecreate/update/query-visible fields). Soidis read-only andpasswordis write-only — exactly as the@visibilitydecorators specify. - Field-level validation decorators (
@format,@minValue, ...) carry into the schema properties, just like the JSON Schema target.
Multi-file projects
Point gen at the entry file; it follows imports automatically:
openschema gen orders.schema --target sql --out ./out
If orders.schema imports Money from common.schema, both the orders and
money tables appear in the output. See
Language Reference → Imports.
Per-company output
When a company overlay exists, generate that company's view:
openschema gen acme-overlay.schema --target sql --out ./out --company acme
The overlay's fields are added to the base model's output, namespaced by
company (acme_loyalty_tier), so different companies' generated artifacts never
collide.