Application schema interchange
bluep projects the logical layer of a model into JSON Schema draft 2020-12, Avro schema JSON, proto3, or TypeScript declarations. JSON Schema and Avro are also import formats. These formats describe application contracts, so database targets, governance configuration, and physical objects are reported rather than silently implied.
Export and import
Export to standard output:
bluep export json-schema devdata/models/commerce > commerce.schema.json
bluep export avro devdata/models/commerce > commerce.avsc
bluep export protobuf devdata/models/commerce > commerce.proto
bluep export typescript devdata/models/commerce > commerce.ts
Use --out <file> to write the document, --root <entity> to select one root, --nest references|identifying to control record nesting, and --report <file> to save the fidelity report as JSON. The default is reference mode.
Import creates a canonical model directory:
$ bluep import json-schema person.json --out person-model --name people
imported 1 entities into person-model
$ bluep import avro person.avsc --out avro-model --name people
imported 1 entities into avro-model
Import also accepts --root <name> when a document has exactly one root, --report <file>, and --force to replace an existing model.bluep.
Nesting and relationships
In references mode every entity is a top-level named record. Relationship fields are not invented: child foreign-key attributes already contain the reference values. JSON Schema and Avro retain relationship metadata in x-bluep-relationships; proto3 and TypeScript use comments and fidelity diagnostics.
In identifying mode an identifying child is nested under its parent. One is a required record, ZeroOrOne is nullable, Many is an array, and OneOrMany is an array with a minimum size of one where the format can express that rule. A child named line becomes line for singular cardinalities and lines for plural cardinalities. A plural child name already ending in s gains _items.
For example, an identifying order to line relationship with Many cardinality produces this JSON Schema property:
"lines": {
"items": { "$ref": "#/$defs/entity.line" },
"type": "array",
"x-bluep-relationship": "line_order"
}
The proto3 equivalent is repeated Line lines = 2;; TypeScript emits lines: ReadonlyArray<Line>;. Identifying cycles and children with multiple identifying parents are rejected because they cannot form a record tree.
Type mappings
Nullability describes the value, not field absence. Every model attribute remains present. JSON Schema wraps nullable values with anyOf: [base, null]; Avro uses a union with null; proto3 adds optional; TypeScript adds | null and never ?.
JSON Schema 2020-12
| Logical type | JSON Schema representation |
|---|---|
smallint |
integer, minimum -32768, maximum 32767 |
integer |
integer, minimum -2147483648, maximum 2147483647 |
bigint |
integer with format int64 |
decimal(p,s) |
string with format decimal, a scale-aware pattern, and x-bluep-precision/x-bluep-scale |
float, double |
number with format float or double |
boolean |
boolean |
text, text(n) |
string, with maxLength when bounded |
char(n) |
string with equal minLength and maxLength |
date |
string with format date |
time |
string with format time |
timestamp |
string with format date-time |
timestamptz |
string with format date-time and x-bluep-timezone: true |
uuid |
string with format uuid |
binary, binary(n) |
base64 string, with encoded maxLength when bounded |
json |
unconstrained schema with x-bluep-logical-type: json |
| enum | local reference to $defs/enum.<name> |
Entity schemas are closed objects (additionalProperties: false). required contains every attribute. Domain definitions retain their base type and x-bluep-domain; entity definitions retain primary keys, unique keys, relationships, descriptions, and declaration order as bluep metadata.
Avro
| Logical type | Avro representation |
|---|---|
smallint, integer |
int |
bigint |
long |
decimal(p,s) |
bytes with decimal logical type, precision, and scale |
float, double |
float, double |
boolean |
boolean |
text, text(n) |
string, with x-bluep-max-length when bounded |
char(n) |
string with x-bluep-fixed-length |
date |
int with logical type date |
time |
long with logical type time-micros |
timestamp |
long with logical type local-timestamp-micros |
timestamptz |
long with logical type timestamp-micros |
uuid |
string with logical type uuid |
binary, binary(n) |
bytes, with x-bluep-max-length when bounded |
json |
string with x-bluep-logical-type: json and a json-as-string loss item |
| enum | named enum reference |
The document is an array containing named enums followed by named records. Nullable fields without a model default use null first and default to null. A non-null default is placed before null, as required by Avro's first-branch default rule.
proto3
| Logical type | proto3 representation |
|---|---|
smallint, integer |
int32 |
bigint |
int64 |
decimal, date, time, timestamp, timestamptz, uuid, json |
string |
float, double |
float, double |
boolean |
bool |
text, char |
string |
binary |
bytes |
| enum | generated enum type |
Generated source starts with syntax = "proto3"; and a bluep.<model> package. Fields are numbered from one; reserved field numbers 19000 through 19999 are skipped. Defaults, checks, keys, domain identity, and distinctions collapsed to string are comments and fidelity items rather than custom options.
TypeScript
| Logical type | TypeScript representation |
|---|---|
smallint, integer, float, double |
number |
bigint |
bigint |
decimal |
DecimalString |
boolean |
boolean |
text, char, date, time, timestamp, timestamptz, uuid |
string |
binary |
Uint8Array |
json |
JsonValue |
| enum | generated string enum |
Every output begins with the DecimalString brand and recursive JsonValue aliases. Entities are interfaces, descriptions are documentation comments, and nested collections are ReadonlyArray<T>.
Naming
JSON Schema preserves exact UTF-8 names and applies JSON Pointer escaping (~0 for ~, ~1 for /) in references. TypeScript preserves exact property names, quoting a property when it is not a valid identifier.
Avro and proto3 names use ASCII letters, digits, and underscores. Each maximal run of other UTF-8 bytes becomes one underscore; a leading digit gains an underscore; an empty result is _. Type names are PascalCase and fields are lower snake case. Proto3 reserved words gain _type or _field. Collisions receive _2, _3, and later suffixes in deterministic source order.
Avro stores x-bluep-original-name. Changed proto3 fields use json_name plus an adjacent // bluep-name: comment. TypeScript adds a // bluep-name: comment only when type-name collision resolution changes the generated type name.
Round-trip guarantees
Exact needs only the base format. Annotated uses x-bluep-*, json_name, comments, or declared conventions. Lossy produces a fidelity item. Unsupported produces an error.
| Feature | JSON Schema | Avro | proto3 | TypeScript |
|---|---|---|---|---|
| entity/attribute names | Exact | Annotated | Annotated | Exact |
| declaration order | Exact | Exact | Exact | Exact |
| primitive logical types | Exact | Exact | Lossy | Lossy |
| decimal precision/scale | Exact | Exact | Lossy | Lossy |
| enum values | Exact | Exact | Exact | Exact |
| nullability | Exact | Exact | Annotated | Exact |
| scalar defaults | Annotated | Annotated | Lossy | Lossy |
| descriptions | Exact | Exact | Annotated | Annotated |
| domains | Annotated | Annotated | Lossy | Lossy |
| primary/unique keys | Annotated | Annotated | Lossy | Lossy |
| non-identifying relationships | Annotated | Annotated | Lossy | Lossy |
| identifying nested record | Annotated | Annotated | Annotated | Annotated |
| arrays of nested records | Annotated | Annotated | Annotated | Annotated |
| scalar arrays on import | Unsupported | Unsupported | Unsupported | Unsupported |
| physical/governance/procedural data | Lossy | Lossy | Lossy | Lossy |
Fidelity reports
Human diagnostics are written to standard error even without --report. Notes appear first in byte order, followed by loss items ordered by path, code, and message:
note: nullable default retained through Avro union ordering
lossy [json-as-string] $.entities.product.attributes: JSON is represented as a string
lossy [physical-targets] $.targets: physical targets are not represented
--report report.json writes canonical JSON with a trailing newline:
{
"lossy": [
{
"code": "json-as-string",
"message": "JSON is represented as a string",
"path": "$.entities.product.attributes"
}
],
"notes": [],
"version": 1
}
An explicitly requested empty report still contains version 1 and empty notes and lossy arrays. Without --report, an empty report writes nothing.
Import boundaries
JSON Schema import accepts object records, local $defs references, nullable anyOf, record arrays, and the mappings above. Avro import accepts named records and enums, one-value nullable unions, nested records, and record arrays. Scalar arrays, maps, fixed values, arbitrary unions, remote references, and recursive references cannot be represented by the phase-33 logical model and are rejected with a path-specific error.