The maintained compiler exposes a finite graph of declared types, without constructing application values. Use an OPTIONAL witness containing NONE, including for records with required fields and resource-owning types.
TYPE Node
Name AS STRING
Children AS LIST OF Node=[]
END TYPE
DIM witness AS OPTIONAL OF Node=NONE
graph=JsonTypeSchema(witness)
root=graph.Types[graph.Root]
PRINT root.Name
FOR EACH field IN root.Fields
PRINT field.Name; ":"; graph.Types[field.DeclaredType].Name
NEXT| Query | Mapping |
|---|---|
TypeSchema(witness) |
Physical BASIL fields/default presence, without serialization overrides |
SerializationTypeSchema(witness) |
Shared SERIALIZE rules and type/field converters |
JsonTypeSchema(witness) |
Effective declaration-default JSON rules, including shared fallbacks |
FormatMetadata(graph,node[,field]) AS OPTIONAL OF M |
Independent typed FORMAT options or NONE; invalid node fails with JsonError |
A TypeGraph has readonly public Root and Types fields. Types is a list of TypeDescriptor. References index that graph; -1 means no reference, including a unit choice alternative. Repeated/recursive references share nodes. IDs have no meaning in another graph. Opaque Identity strings distinguish nominal types and instantiated arguments; compare them as whole strings, without parsing them or using them as a persisted versioned schema identifier. Name is a display label, not a lookup key; generic records retain their base name and expose TypeArguments.
| TypeDescriptor fields | Meaning |
|---|---|
| Identity, Name, Kind | Nominal/structural identity, display name, TypeKind |
| Fields | Declaration-ordered ordinary fields, including flattened base fields |
| ElementType | List/array/set element or optional payload reference |
| KeyType, ValueType | Dictionary key/value references |
| Members | Enum Name and signed 64-bit Value |
| Alternatives | Choice Name and ValueType (-1 for a unit alternative) |
| TypeArguments | Bound ordinary-record generic argument references, including phantom arguments |
| IsOpaque, IsResource | Representation privacy and checked ownership classification |
| WireSupported, UnsupportedReason, WireRepresentation | Standard wire-shape facts (before root converters) and explicit unsupported/dynamic reasons |
| EncodeType, DecodeType, HasEncoder, HasDecoder | Declared converter representation references and presence, independently by direction |
| BitWidth, IsUnsigned | Integer/DOUBLE numeric representation; width 0 when not a numeric scalar |
TypeKind cases are Unsupported, BooleanValue, IntegerValue, DoubleValue, ByteValue, UInt32Value, UInt64Value, StringValue, RecordValue, ListValue, ArrayValue, DictionaryValue, SetValue, OptionalValue, EnumValue, ChoiceValue, DynamicJsonValue, ResourceValue, FunctionValue and PointerValue. Native resources/callables/pointers, dynamic JsonValue, and unclassified categories explicitly lack a static wire shape. Resource records/containers remain nonserializable. A NONE witness never acquires, copies, transfers or destroys an application resource.
TypeFieldInfo contains Name (physical), WireName, DeclaredType, EncodeType, DecodeType, IsOptional, HasDefault, ReadOnly, WriteOnly, Excluded, OmitNone, HasEncoder and HasDecoder. Optionality and ownership reflect bound generic arguments. HasDefault means an explicit TYPE field default; an optional without one can still decode missing input as NONE. The access flags refer to wire directions, not BASIL mutability.
To interpret a wire view, select the direction’s root converter when present, then visit its representation reference with that representation’s root converter bypassed once, matching the codec. Field HasEncoder/HasDecoder distinguishes an explicit field override, including one returning the same declared type: visit that direction’s field representation with the same bypass. Without a field override, visit DeclaredType normally. Children of a bypassed representation use ordinary selection again. A traversal’s visited key therefore includes direction and bypass state, not only node ID; same-type normalization converters terminate.
WireSupported/WireRepresentation describe standard handling before root converters. Physical Fields remain facts even when a converter changes the wire shape. WireSupported=TRUE is not a transitive validation of the graph. After selecting/bypassing root rules, traverse relevant fields/elements/keys/values/cases and reject unsupported nodes, or interpret explicit user FORMAT schema options. Opaque nodes expose no fields/field annotations, even PUBLIC fields; their standard WireSupported remains FALSE. A selected declared converter can expose a known representation without exposing opaque storage. An encoder alone does not establish a decoder. A JsonValue representation stays explicitly dynamic, rather than being inferred from a sample converter result.
BASIL set equality does not guarantee unique JSON array items: exclusions or converters can map different members to identical JSON. SetValue retains the language kind; its wire representation is array without a uniqueness promise.
Standard wire representation labels are boolean/integer/number/string, object,
array, nullable, object-encoded-keys, single-case-object and
array-or-bounds-object. Choices use exactly one case-name property (a unit case
has null). Nontext dictionary keys use the existing JSON key encoding, with the
key type’s selected mapping. Zero-based vectors use arrays; other dense arrays use
{"$bounds":[lower,upper,...],"$values":[...]}. Bounds and vector shape are value
facts, so the type describes both accepted forms. Converter presence delegates to EncodeType/DecodeType as described above. These
are codec protocols, not provider-specific schemas.
FORMAT queries use physical BASIL field names case-insensitively; empty/omitted field selects the type header. Different nominal option types stay distinct; missing annotations return NONE. Returned values and copied descriptor lists are independent. Graphs provide no mutable field access, unchecked value construction, raw addresses or runtime converter registry. Per-instance JsonCodec profiles are not arguments to these declaration-default queries; custom runtime mappings need an explicit schema supplied by their library.