Skip to content

Runtime semantics

Typra treats TypeSpec as the source of truth. Every emitted runtime may use idiomatic types and helpers, but it must preserve the same contract semantics as the other targets.

This page is the language-neutral target contract for generated load, save, JSON/YAML helpers, provider wire mapping, and polymorphic dispatch. Some current backends may still diverge while Typra is being regularized. Those gaps should be explicit in diagnostics, the conformance matrix, or a tracked known failure.

  • A required TypeSpec property must be present when loading object-shaped data. Missing required fields must fail; generated runtimes must not fabricate a value to make construction succeed.
  • Defaults do not satisfy required-field presence. Until complex defaults are supported end-to-end, a default on a model-typed or collection-typed property is a portability error rather than an implicit object factory.
  • Optional properties have distinct wire states: absent, present with a value, and present with an explicitly empty value such as "", 0, false, or []. Saving must not collapse explicit zero values into absence.
  • Optional properties without explicit defaults preserve absence. Optional properties with explicit defaults may materialize that default when the target’s generated representation cannot otherwise distinguish an omitted field from a field intentionally set to the default.
  • Required zero values are still required values. Saving a required string, number, boolean, or collection at its zero value must emit the field.
  • null is accepted only where the lowered contract explicitly permits it or where the field type is an unknown/dictionary-shaped carrier. Otherwise it is a load error.
  • Regular models are not an unbounded extension bag. Unknown fields may be ignored unless a feature explicitly preserves them.
  • Unknown discriminator carriers are different: when an open discriminated base accepts an unclaimed discriminator value, the carrier must preserve the raw payload, including unknown fields and explicit nulls, so save/reload is stable.
  • Scalar coercions are load-only expansions from an allowed scalar shorthand to the canonical object shape. Saving emits the canonical object form unless a target API explicitly asks for shorthand.
  • Record<T> preserves T. A typed record must load and save values using the same element semantics as a normal property of type T.
  • Arrays are ordered. Runtimes must preserve array element order.
  • Keyed collections are semantically maps when saved in object form. Object key order is not part of the contract. If a caller needs order, it must use an array/list representation and the target API must make that choice explicit.
  • Duplicate or empty names in a keyed collection must not silently overwrite earlier entries. The target contract is to preserve list form or reject the ambiguous value with a pathful error. Falling back to list form is acceptable only if the duplicate or empty-name entries are preserved losslessly; current gaps should stay tracked until every backend implements that behavior.
  • Discriminated load requires a present, non-blank string discriminator. Missing, blank, null, or any non-string discriminator value is a load error. These invalid states are not “unknown” discriminator values and must not route to wildcard, open, or unknown fallback carriers.
  • A discriminator value claimed by a subtype loads that subtype.
  • A discriminator value claimed by a wildcard subtype loads the wildcard subtype and must still enforce that subtype’s required fields.
  • If a payload claims a known discriminator value but fails that concrete subtype’s schema, loading fails. It must not be retried against an open or unknown fallback.
  • A well-formed but unclaimed discriminator value on an open base loads an unknown carrier that preserves the raw payload.
  • On a non-abstract base, a discriminator value permitted by a closed union but claimed by no subtype loads the base itself and preserves the base fields. A value outside the closed union is a load error.
  • Saving a known subtype emits its canonical discriminator value. Saving an unknown carrier replays the original discriminator and preserved payload.
  • Closed string unions accept only declared values and parse aliases.
  • Open string unions preserve declared known values while allowing target-native representation of unknown strings.
  • @parseAlias is parse-only. Loading accepts aliases; saving emits the canonical TypeSpec value.
  • Canonical model properties keep their TypeSpec names in normal save output.
  • Provider wire conversion emits only fields that have a mapping for the requested provider.
  • A provider with no mapping, including the empty provider string, produces an empty wire payload rather than falling back to canonical property names.
  • Provider defaults apply only in the provider-specific wire surface they are declared for. They must not change canonical model load/save behavior.
  • Invalid input must fail visibly. Generated runtimes should not silently return partial objects, default required fields, or swallow nested load errors.
  • Diagnostics should include the deepest known field path, including array indexes and keyed collection names, so downstream agents can repair the original TypeSpec or payload.
  • If a TypeSpec shape cannot be represented portably across targets, Typra should emit an actionable diagnostic instead of generating divergent behavior.
  • Native interop surfaces such as Zod, Pydantic, Jackson, serde, and Codable are adapters over Typra semantics. They must delegate to generated load and save behavior rather than defining a second serialization contract.

Every semantic rule above should have fixture evidence and, where possible, executable conformance across every supported runtime. A backend that diverges from this contract must be tracked as a known failure tied to an issue, not left as implicit target behavior.