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.