Dil
Tip Sistemi
Bir schema alanının tipi üç işi birden yapar: gateway'e gönderilen JSON Schema'yı üretir, dönen yanıtı yerel olarak doğrular ve --generate-tool-types ile TypeScript declaration'ına çevrilir. Bu sayfa tiplerin tamamını ve her birinin JSON Schema karşılığını listeler.
Temel tipler
| Tip | Eş anlamlı | Kabul edilen değer | JSON Schema |
|---|---|---|---|
string | — | JS string | { "type": "string" } |
int | integer | Number.isInteger geçen değer | { "type": "integer" } |
float | number | Sonlu sayı — NaN ve Infinity reddedilir | { "type": "number" } |
bool | boolean | true / false | { "type": "boolean" } |
null | — | Yalnızca null | { "type": "null" } |
any | — | Her şey — doğrulama yapılmaz | {} |
int alanına 3.5 gelirse X.y must be an integer. hatası alırsın; 3.0 ise Number.isInteger açısından geçerlidir. float alanına NaN veya Infinity gelirse X.y must be a number. hatası alırsın — bu, bozuk model çıktısını erken yakalar.
Bileşik tipler
enum
status: enum["pending", "approved", "rejected"]
Yalnızca string değerler alır ve boş olamaz. JSON Schema karşılığı { "type": "string", "enum": [...] }. Listede olmayan bir değer X.y must be one of these values: … hatası verir.
Array
tags: string[] // unbounded list of strings items: LineItem[] // list of nested schemas grid: int[][] // nested array
JSON Schema: { "type": "array", "items": { … } }. Her eleman ayrı ayrı doğrulanır ve hata mesajında indeks görünür: Invoice.items[2].amount missing.
Elemana [] ile erişilir — rows[0], rows[i].customer.name — ve aynı biçim bir ='in solunda yazma yapar. Sonun ötesini okumak adlandırılmış hatadır, sessiz bir null değil: xs[5] is out of bounds (length 2). Sonun ötesine yazmak da reddedilir; liste indekslenerek büyümez. Sınır durumlarının tam tablosu dil sayfasındadır.
map<T> / record<T>
metrics: map<float> // { "score": 0.95, "confidence": 0.8 }
labels: record<string> // record is an alias for mapDinamik anahtarlı nesne. JSON Schema: { "type": "object", "additionalProperties": { … } }. Anahtarlar serbesttir, değerlerin tipi doğrulanır.
Değere m["key"] ile erişilir; olmayan bir anahtara yazmak onu oluşturur. Olmayan bir anahtarı okumak ise null döndürmez — eksik anahtar adlandırılmış bir hatadır: m["missing"] is not present. Bu yalnızca runtime hakkında değil, tip hakkında bir ifadedir: map<T> T tutar, T | null değil; yani lookup, programa null'ın girebileceği bir yer değildir. Emin olmadığın bir anahtar okunmadan önce kontrol edilmelidir:
import col from "std/collections"
agent Reader {
fn read(labels: map<string>, key: string) -> Out {
if contains(col.keys(labels), key) {
return { value: labels[key] }
}
return { value: "unknown" }
}
}İndekslemenin sınır durumlarının tam tablosu — listeler ve map'ler, okuma ve yazma — dil sayfasındadır.
Strict structured-output modu (strict: true) çoğu gateway'de her object'in tüm anahtarlarını properties içinde listelemesini ve additionalProperties: false olmasını bekler. Dinamik anahtarlı bir map<T> bu koşulu sağlayamaz ve provider isteği reddedebilir.
LLM çıktısı olarak dinamik metadata taşıman gerekiyorsa anahtar-değer çifti listesi kullan — bu desen strict modda güvenlidir:
schema MetaEntry {
key: string
value: string
}
// inside InvoiceExtract:
meta: MetaEntry[] @desc("Schema alanlarına girmeyen tüm bilgiler")map<T>'yi deterministik kod içinde (tool çıktısı, return ile üretilen değer) rahatça kullanabilirsin; kısıt yalnızca LLM'e gönderilen schema'lar içindir.
json<T>
import json from "std/json"
schema Envelope {
raw: string // JSON text
data: any // parsed, unvalidated value
payload: json<Payload> // parsed AND validated against Payload
}json<T> tipi, değeri T'ye göre doğrular. JSON metni tutmak istiyorsan tip string olmalıdır. JSON Schema üretiminde json<T> doğrudan T'ye açılır, yani gateway açısından T ile birebir aynıdır.
Tuple
coordinate: [float, float] // exactly two elements, positional
Sabit uzunluklu, konum-tipli array. Doğrulamada eleman sayısı ve her konumun tipi kontrol edilir. En az bir virgül içermelidir ([float] tuple değildir).
Tuple, JSON Schema'da prefixItems olarak yayılır ve ask çıktısı olarak sorunsuz kullanılabilir. Ancak sayısal ve string annotation'lar tuple'ın konumlarına uygulanamaz; parser bunları reddeder:
Error: @range can only be used on numeric fields or numeric arrays.
Konum başına kısıt gerekiyorsa adlandırılmış alanlar hem daha okunaklı hem daha ifade gücü yüksektir:
schema Point {
lat: float [-90..90]
lon: float [-180..180]
}Schema referansı
schema Address {
city: string
}
schema Customer {
address: Address // nested object
history: Address[] // list of nested objects
}JSON Schema'da { "$ref": "#/$defs/Address" } olarak üretilir; tanım $defs altında bir kez yer alır.
Bir alanın tipi doğrudan bir schema referansıysa, o alandaki @desc JSON Schema'ya aktarılmaz. Bunun sebebi bazı gateway'lerin $ref ile aynı seviyede description gördüğünde isteği reddetmesidir. Açıklamayı hedef schema'nın kendisine (schema Address @desc("…")) veya alanın içindeki alanlara yaz.
Parametrik schema — Name<T>
schema LineItem {
sku: string
amount: float
}
schema Page<T> {
items: T[]
total: int
}
schema Report {
page: Page<LineItem>
}Bir schema tip parametresi alabilir. Gövdenin içinde parametre başka herhangi bir tip adı gibi okunur; her kullanım yeri de somut bir argüman verir: Page<LineItem>, Page<Customer>.
Page<LineItem> dosya parse edilirken açılır. Parser Page'in gövdesini kopyalar, T'nin durduğu yere LineItem'ı koyar ve sonucu Page_LineItem adıyla sıradan bir schema olarak kaydeder. O noktadan sonra hiçbir şey ortada bir parametre olduğunu bilmez — JSON Schema üretimi, doğrulama, $defs, where, when, @desc ve std/schema reflection'ı, elle yazılmış bir schema'da nasıl işliyorsa açılmış schema üzerinde de aynen öyle işler; sağlayıcıya hiçbir genericlik ulaşmaz.
Gördüğün ad, açılmış addır. Hem $defs anahtarı odur, hem schema.name(…) onu döndürür, hem de o schema'ya karşı üretilen doğrulama hatasının öneki odur:
Page_LineItem.items[0].amount missing.
Schema adının kabul edildiği her pozisyon
| Pozisyon | Yazılışı |
|---|---|
| Method dönüş tipi | fn list() -> Page<LineItem> |
| Schema alanı | page: Page<LineItem> |
| Agent method parametresi | fn take(page: Page<LineItem>) -> Report |
pure fn / io fn parametresi | pure fn count(page: Page<LineItem>) -> int |
| Tool parametresi | tool inspect(page: Page<LineItem>) -> Box<int> |
| Tipli yerel değişken | local: Page<LineItem> = page |
Asıl önemli olan taşıma durumu: üreten method Page<LineItem> döndürür, tüketen method aynı şeyi parametre olarak alır; böylece sarmalayıcı ikisinin arasındaki sınırda elle açılmak zorunda kalmaz.
Birden fazla parametre, iç içe kullanım, primitive'ler
schema LineItem {
sku: string
}
schema Customer {
name: string
}
schema Page<T> {
items: T[]
}
schema Pair<A, B> {
left: A
right: B
}
schema Box<T> {
value: T
}
schema Envelope<T> {
payload: T
trace_id: string
}
schema Deep<T> {
inner: Box<T>
}
schema Catalogue {
pair: Pair<LineItem, Customer>
pages: Page<Envelope<LineItem>>
count: Box<int>
deep: Deep<LineItem>
}Tip argümanı bir primitive (Box<int>), bir schema veya başka bir açılım (Page<Envelope<LineItem>>) olabilir; generic bir gövde de ikinci bir generic'e başvurabilir — yukarıdaki Deep<T> içinde bir Box<T> taşıyor. Birbirinden farklı her açılım bir kez üretilir ve adı argümanlarından kurulur: Pair_LineItem_Customer, Page_Envelope_LineItem, Box_int, Deep_LineItem. Argüman sayısı tutmazsa sayı adıyla söylenir: Generic schema 'Pair' expects 2 type argument(s), got 1.
Parametre neyin yerine geçer, ona ne verilebilir
Yer değiştirmenin iki tarafı da yukarıdaki örneklerin düşündürdüğünden geniştir. Parametre çıplak bir alanla sınırlı değildir: her bileşik pozisyona yerleşir, yani tek bir bildirim aynı tipin array, map, nullable, tuple ve ham JSON biçimlerinin hepsini kapsar.
schema LineItem {
sku: string
}
schema Multi<T> {
one: T
many: T[]
lookup: map<T>
maybe: T | null
pair: [T, string]
raw: json<T>
}
schema Catalogue {
rows: Multi<LineItem>
}Argümanın da bir schema adı olması gerekmez. Tip gramerinin yazabildiği her şey verilebilir; her biri argümanın kendi yazılı biçiminden kurulan bir adla açılır:
| Yazılışı | Açılım |
|---|---|
Box<string[]> | Box_string_array |
Box<map<int>> | Box_map_int |
Box<string | null> | Box_union_string_or_null |
Box<enum["red", "green"]> | Box_enum_red_green |
Box<json<any>> | Box_json_any |
Box<Box<Box<LineItem>>> | Box_Box_Box_LineItem |
Type parameter yalnızca schema'ya ait bir yapıdır. Başka bir blok üzerine yazmak generic bir agent ya da generic bir fonksiyon vermez.
Composition ve generic'ler eskiden import'lar birleştirilmeden önce çözülüyordu; bu yüzden ikisi de yazıldıkları dosyada kalıyordu. Çok modüllü bir paket ortak bir taban şemayı paylaşamıyordu — User başka bir dosyadan geldiğinde schema Admin = User + Audit Unknown schema composition part: User ile düşüyordu ve import edilmiş bir Page<T> alan tipinde instantiate edilemiyordu. Çözüm artık birleştirme sonrasına ertelendi; üç biçim de sınırı geçiyor:
import "./base.luma" // Taban, Ek, Zarf<T>
schema Birlesik = Taban + Ek // composition
schema Kullanim = Zarf<Ic> // generic alias
schema Sonuc { kutu: Zarf<Ic> } // generic alan tipiAlias biçimi yalnızca yeni-modüllerarası değil — dilde hiç yoktu. schema K = Zarf<Ic> sessizce yutuluyordu: satır hiçbir şey olarak ayrıştırılıyor, K hiç kaydedilmiyor ve hata ancak sonradan No schema named 'K' was found olarak yüzeye çıkıyordu. Bilinen hiçbir biçime uymayan schema X = … satırı artık adıyla reddediliyor:
Error: Schema definition 'schema K = …' matches no known form: use 'A + B' composition, 'A | B tagged by field' union, or a 'Template<Args>' generic alias.
Erteleme alan tiplerini ve alias'ları kapsıyor. Her şeyi kapsamıyor ve sınırlar varsayılmadı, ölçüldü:
- Parametre pozisyonundaki bir generic — agent method'u ya da
pure fnparametresi — hâlâ eager çözülür; import edilmiş bir şablon orada kullanılamaz. - Bekleyen bir generic alias composition parçası olamaz (
Unknown schema composition part). O zincir gerekiyorsa alias'ı kendi modülünde somutlaştır. - Namespace'li import üzerinden erişilen şablon (
mod.Zarf<Ic>) kapsam dışıdır — namespace'li şema adlandırması ayrı bir yüzey ve ölçülmedi. - Aynı ada sahip şablonu ihraç eden iki modül
Import generic schema conflictile reddedilir. Elmas — aynı modüle iki kez ulaşmak — şemalarda olduğu gibi no-op'tur.
Açılımlar kullanım yerine göre değil, açılımın kendisine göre önbelleğe alınır. Aynı method'un hem parametresinde hem dönüş tipinde Page<LineItem> yazmak yine tek bir Page_LineItem üretir; ikinci kullanım, birincinin kaydettiği schema'ya çözülür. $defs'in her sarmalayıcı anımıyla büyümemesini sağlayan şey budur.
Bunların her biri programı çalışmadan durdurur:
Error: Unknown type argument schema 'Missing' for generic 'Page'. Error: Generic schema cycle found: Tree<int> -> Tree<int>. Error: Tagged unions cannot use generic schema members: Document. Error: Tagged union 'Document' must contain at least two members.
- Hiçbir şeye karşılık gelmeyen bir tip argümanı —
Page<Missing>. Argümanlar çözülür, öylece kabul edilmez. - Kendini açan bir generic — içinde
children: Tree<T>[]taşıyan birTree<T>. Açılımın duracağı bir nokta yok; bu yüzden döngü sonsuza kadar işletilmek yerine onu üreten zincirle birlikte bildirilir. - Etiketli union üyesi olarak kullanılan bir generic —
schema Document = Page<Invoice> | Page<Receipt> tagged by kind. Etiket üyenin schema adını taşır; bir açılımın oraya hangi adı vereceği tahmin edilmek yerine karara bağlanmadan bırakıldı. Ters yön serbest: etiketli union gayet iyi bir tip argümanıdır veEnvelope<Document>her açılım gibi açılır. - Bir alttaki kural da öyle: etiketli union en az iki üye ister, dolayısıyla
schema Document = Invoice tagged by kindde reddedilir. Tek dallı bir toplam tip yalnızca yeniden adlandırmadır.
Reflection bir değer alır, Page tek başına değer değildir; bu yüzden schema.toJsonSchema(Page<LineItem>) hiç çalışmaz:
Error: 'Page' variable is not defined yet.
Açılıma başka yoldan uzan: açılımı bir alanında tutan schema üzerinden reflection yap ya da açılmış adı doğrudan yaz — schema.toJsonSchema(Page_LineItem) — programdaki herhangi bir pozisyon onu açtığı anda o ad vardır. Hiçbir yerde açılım yoksa aynı mesaj bu kez açılmış adla geri gelir.
Union ve nullable
note: string | null value: int | float | null flexible: string | int
Union, | ile yazılır ve JSON Schema'da anyOf üretir. Doğrulama sırasında üyeler sırayla denenir; hiçbiri tutmazsa tüm alt hatalar birleştirilerek raporlanır:
Customer.tax_id does not match any union type: Customer.tax_id must be a string. | Customer.tax_id must be null.
Etiketli union — tagged by
Bir alan tipinin içine yazılan |, etiketsiz bir union'dır. = ve tagged by ile tanımlanan schema ise başka bir şeydir: bir sum type. Değer üyelerden biridir ya da diğeridir; hangisi olduğunu bir etiket alanı söyler.
schema Invoice {
date: string
total: float
invoice_number?: string
}
schema Receipt {
date: string
total: float
reference?: string
}
schema Document = Invoice | Receipt tagged by kindTanım tek satırdır, gövdesi yoktur ve en az iki üye ister; üyelerin hepsi önceden tanımlı schema olmalıdır — tanınmayan bir üyede parser Tagged union 'Document' member schema 'Voucher' was not found. diyerek durur. Bu noktadan sonra iki yazım tamamen farklı kurallarla çözümlenir:
| Yazım | Değer nasıl eşlenir |
|---|---|
A | B — alan tipi içinde | Üyeler sırayla denenir; doğrulamayı ilk geçen kazanır. |
schema D = A | B tagged by k | Yalnızca k etiketi okunur ve tam olarak bir üyeyi adlandırır. Deneme yapılmaz. |
Gerçek schema'lar örtüşür. Yukarıdaki Invoice ile Receipt hem date hem total taşır ve onları ayıran alanlar opsiyoneldir — dolayısıyla yalnızca tarih ve toplam içeren bir değer ikisini de sorunsuz doğrular. Etiketsiz bir doc: Invoice | Receipt alanında ilk üye kazanır, doğrulama orada biter ve çalıştırma başarı raporlar. Değer, sırf üyeler o sırayla yazıldığı için fatura oldu.
Aynı alanı doc: Receipt | Invoice diye yazmak tıpatıp aynı değeri makbuza çevirir. Programda başka hiçbir şey değişmez ve hiçbir uyarı çıkmaz. Etiket bu tahmini ortadan kaldırır: tagged by kind altında "kind": "Receipt" taşıyan bir değer, Invoice ne kadar iyi oturuyor olursa olsun bir Receipt'tir.
Etiket alanı enjekte edilir, yazılmaz
tagged by kind, her üyeye kind alanını ekler ve bu alanın değeri o üyenin schema adıdır: "Invoice", "Receipt". Alanı sen hiç yazmazsın ve üyeler değişmez — Invoice tek başına, alan tipi, parametre ya da ask çıktısı olarak kullanıldığında yine tam olarak kendi tanımladığı alanlara sahiptir, aralarında kind yoktur.
Etiket enjekte edildiği için, onu bir de üyenin tanımlaması çakışmadır; parser tüm programı reddeder:
Error: Tagged union 'Document' discriminator 'kind' conflicts with member schema 'Invoice'.
Etiketin okunması
Etiket her şeyden önce okunur. Tek bir üyeyi seçer ve değerin kalanı yalnızca o üyeye göre doğrulanır; böylece bütün üyeler otururken bile seçilen dal deterministiktir:
agent Router {
fn route(document: Document) -> Result {
match document {
case Invoice invoice { return { branch: "Invoice" } }
case Receipt receipt { return { branch: "Receipt" } }
}
}
}
router = Router()
out = router.route({ kind: "Receipt", date: "2026-02-01", total: 42.0 })Etiketi olmayan — ya da string olmayan — bir değer hiçbir üyeye ulaşamaz; etiketi union dışındaki bir schema'yı adlandıran değer de öyle:
Router.route.document.kind must identify a tagged union member. Router.route.document.kind has unknown tagged union member 'Voucher'.
Baştaki ön ek, doğrulanan değerin yoludur; burada Router.route metodunun document parametresi.
Opsiyonel / nullable matrisi
Bu tablo Luma'da en sık karıştırılan ayrımdır:
| Yazım | Alan eksik olabilir? | Değer null olabilir? | JSON Schema required |
|---|---|---|---|
x: string | ✗ hayır | ✗ hayır | ✓ içinde |
x?: string | ✓ evet | ✗ hayır | ✗ değil |
x: string | null | ✗ hayır | ✓ evet | ✓ içinde |
x?: string | null | ✓ evet | ✓ evet | ✗ değil |
x: string @default("a") | ✓ (eksikse doldurulur) | ✗ hayır | ✗ değil |
Modelden veri çıkarımı yaparken T | null genellikle ?'dan iyidir: alan her zaman mevcut olur, modelin "bulamadım" demesi için açık bir yol kalır ve strict schema modunda required listesinde kaldığı için model alanı atlayamaz. ? ise modelin alanı sessizce yutmasına izin verir.
// The preferred pattern in invoice extraction:
invoice_number: string | null @desc("Fatura numarası")
due_date: string | null @desc("Son ödeme tarihi")
items: InvoiceItem[] @desc("Tüm kalemler; yoksa boş liste")Array ve null
| Yazım | Anlamı |
|---|---|
items: Item[] | Zorunlu array. Boş liste [] geçerlidir. |
items: Item[] @minItems(1) | En az bir eleman zorunlu. |
items: Item[] | null | null ya da array; array boş olabilir. |
items?: Item[] | Alan hiç bulunmayabilir. |
Nullable bir array'e uygulanan @minItems, @unique gibi koleksiyon annotation'ları yalnızca değer gerçekten array olduğunda çalışır; değer null ise atlanır.
Boyut ve aralık kısayolları
| Yazım | Anlamı |
|---|---|
float[0..1] | 0–1 elemanlı float array'i (köşeli parantez tipe yapışık) |
float [0..1] | 0 ile 1 arasında tek bir float (tip ile parantez arasında boşluk) |
Bu ikisi sessizce farklı davranır ve tipik olarak confidence: float[0..1] yazan biri, modelin 0.82 yerine [0.82] döndürmesini bekleyen bir schema üretmiş olur.
| Kısayol | Anlamı | Eşdeğeri |
|---|---|---|
string[3] | Tam olarak 3 elemanlı string array'i | string[] @minItems(3) @maxItems(3) |
LineItem[1..100] | 1 ile 100 arasında eleman | LineItem[] @minItems(1) @maxItems(100) |
float [0..1] | 0 ≤ değer ≤ 1 | float @range(0, 1) |
int [1..99] | 1 ≤ değer ≤ 99 | int @range(1, 99) |
float[1..3] [0..100] | 1–3 elemanlı array; her eleman 0–100 arası | — |
schema Answer {
title: string
paragraphs: string[2] // exactly two paragraphs
people: Person[1..3] // 1-3 people
scores: float[1..3] [0..100]
confidence: float [0..1] // a single number between 0 and 1
}score: float [0..100] @range(0, 50) yazarsan parser Field range cannot be specified with both legacy syntax and @range. hatası verir. Birini seç.
JSON Schema eşlemesi
Aşağıdaki schema ve onun ürettiği gerçek JSON Schema çıktısı, schema.toJsonSchema(...) ile alınmıştır:
schema Address {
city: string
}
schema Customer @desc("Fatura müşterisi") {
name: string @length(2, 40) @title("Ad Soyad") @example("Ayşe Yılmaz")
tax_id: string | null @length(10) @desc("Vergi kimlik numarası")
email?: string @fmt("email") @alias("e_mail")
score: float [0..100]
tags: string[] @minItems(1) @unique @contains("vip")
kind: enum["individual", "company"]
metrics: map<float>
address: Address
active: bool @default(true)
where score >= 0
when kind == "company" require [tax_id]
}{
"type": "object",
"description": "Fatura müşterisi",
"properties": {
"name": { "type": "string", "minLength": 2, "maxLength": 40,
"examples": ["Ayşe Yılmaz"], "title": "Ad Soyad" },
"tax_id": { "anyOf": [ { "type": "string", "minLength": 10, "maxLength": 10 },
{ "type": "null" } ],
"description": "Vergi kimlik numarası" },
"email": { "type": "string", "format": "email",
"x-luma-aliases": ["e_mail"] },
"score": { "type": "number", "minimum": 0, "maximum": 100 },
"tags": { "type": "array", "items": { "type": "string" },
"minItems": 1, "uniqueItems": true,
"contains": { "const": "vip" } },
"kind": { "type": "string", "enum": ["individual", "company"] },
"metrics": { "type": "object", "additionalProperties": { "type": "number" } },
"address": { "$ref": "#/$defs/Address" },
"active": { "type": "boolean", "default": true }
},
"required": ["name", "tax_id", "score", "tags", "kind", "metrics", "address"],
"additionalProperties": false,
"allOf": [
{ "if": { "properties": { "kind": { "const": "company" } } },
"then": { "allOf": [ { "required": ["tax_id"] } ] } }
],
"x-luma-where": [ { "expression": "score >= 0" } ],
"x-luma-conditionals": [ … ]
}Dikkat edilecek noktalar
@length(10)tek argümanla verildiğinde hemminLengthhemmaxLength10 olur — yani tam olarak 10 karakter.- Union alanında annotation'lar ilgili branch'e uygulanır: uzunluk kısıtı
stringbranch'ine gider,nullbranch'i dokunulmadan kalır. @defaultve?alanırequireddışında bırakır.- Kök nesne her zaman
additionalProperties: falseiçerir — model schema dışı alan üretemez. whereve koşullu kurallarx-luma-*uzantılarıyla taşınır; bunlar gateway tarafından yok sayılır, doğrulamayı Luma yerel olarak yapar.
JSON Schema'da etiketli union
Her üye anyOf içinde eksiksiz yayılır, etiket her birinde bir const'a sabitlenir ve o üyenin required listesine katılır. Ayırt etme işini yapan da o const'tur: bir belge Receipt'tir çünkü orada kind yalnızca "Receipt" olabilir. Aşağıdaki, yukarıdaki Document union'ının gerçek çıktısıdır:
{
"anyOf": [
{ "type": "object",
"properties": {
"date": { "type": "string" },
"total": { "type": "number" },
"invoice_number": { "type": "string" },
"kind": { "type": "string", "const": "Invoice" }
},
"required": ["date", "total", "kind"],
"additionalProperties": false },
{ "type": "object",
"properties": {
"date": { "type": "string" },
"total": { "type": "number" },
"reference": { "type": "string" },
"kind": { "type": "string", "const": "Receipt" }
},
"required": ["date", "total", "kind"],
"additionalProperties": false }
]
}v0.5.0'a kadar bu yüzey oneOf ve yanında bir discriminator yayıyordu. Katı json_schema modu ikisini de reddediyor, yani etiketli union — v0.2.0'ın manşet özelliği — katı bir provider'a hiç ulaşamıyordu. Wire biçimi, etiketi const olarak taşıyan anyOf'a taşındı; aynı bilgiyi her provider'ın kabul ettiği bir şekilde taşıyor ve schema.toJsonSchema tam olarak wire'a gideni bildiriyor. → strict mod
Etiketsiz yazımda bunların hiçbiri yoktur. doc: Invoice | Receipt alanı düz bir referans listesine dönüşür ve schema'da hangi üyenin kastedildiğini gateway'e — ya da bir doğrulayıcıya — söyleyen hiçbir şey kalmaz:
"doc": { "anyOf": [ { "$ref": "#/$defs/Invoice" }, { "$ref": "#/$defs/Receipt" } ] }JSON Schema'da parametrik schema
Görülecek özel bir şey yok; parse zamanında açmanın bütün amacı da bu. Aşağıdaki, çıktı tipi Page<LineItem> olan bir ask için giden gerçek istek gövdesi; modelin eline dar bir object schema geçiyor ve ortada bir parametre yazıldığını hiç öğrenmiyor:
{
"type": "object",
"properties": {
"items": { "type": "array", "items": { "$ref": "#/$defs/LineItem" } },
"total": { "type": "integer" }
},
"required": ["items", "total"],
"additionalProperties": false,
"$defs": {
"Page_LineItem": {
"type": "object",
"properties": {
"items": { "type": "array", "items": { "$ref": "#/$defs/LineItem" } },
"total": { "type": "integer" }
},
"required": ["items", "total"],
"additionalProperties": false
},
"LineItem": {
"type": "object",
"properties": {
"sku": { "type": "string" },
"amount": { "type": "number" }
},
"required": ["sku", "amount"],
"additionalProperties": false
}
}
}$defs yalnızca bu kök schema'nın eriştiklerini taşır: açılımın kendisi ve onun başvurduğu LineItem. Aynı programda başka bir yerde, hiçbir isteğin uğramadığı bir yolda duran Page<Customer> gövdede yoktur — bir açılım, erişilebilir bir şey ona işaret etmedikçe istek maliyeti değildir. Parametrik schema'ların $defs budaması inene kadar bekletilmesinin sebebi buydu: budama olmadan tek bir sarmalayıcı, ödeme yapılan her çağrıda payload tipi sayısı kadar çoğalırdı.
Tam eşleme tablosu
| Luma tipi | JSON Schema |
|---|---|
string | {"type":"string"} |
int | {"type":"integer"} |
float | {"type":"number"} |
bool | {"type":"boolean"} |
null | {"type":"null"} |
any | {} (kısıt yok) |
enum[…] | {"type":"string","enum":[…]} |
T[] | {"type":"array","items":T} |
map<T> | {"type":"object","additionalProperties":T} |
json<T> | T ile aynı |
A | B | {"anyOf":[A, B]} |
schema D = A | B tagged by k | {"anyOf":[A, B]} — her üye ayrıca k'yı const olarak kazanır; ayırt etmeyi o yapar |
[A, B] (tuple) | {"type":"array","prefixItems":[A,B],"minItems":2,"maxItems":2} |
SchemaName | {"$ref":"#/$defs/SchemaName"} |
schema P<T> {…}, P<X> olarak | {"$ref":"#/$defs/P_X"} — açılım $defs altında sıradan bir object schema olarak durur |
Hangi tipi ne zaman?
schema Record {
id: string
amount: float | null
status: enum["open", "closed"]
items: LineItem[]
meta: MetaEntry[]
}schema Record {
id: any // no validation
amount: float // no "not found" path
status: string // free text
items?: LineItem[] // the model may skip it
meta: map<any> // may be rejected in strict mode
}| İhtiyaç | Tercih | Neden |
|---|---|---|
| Sabit değerler kümesi | enum[…] | Model serbest metin üretemez; enum ihlali yerel olarak yakalanır. |
| Bulunmayabilecek değer | T | null | Alan zorunlu kalır, modele açık bir 'yok' yolu verir. |
| Şemaya sığmayan ek bilgi | Entry[] (key/value) | Strict gateway'lerle uyumlu; map<T> değil. |
| Serbest JSON taşımak | any veya json<T> | any doğrulamaz; json<T> doğrular. |
| Sabit uzunluklu sayı çifti | float[2] veya iki alanlı schema | Tuple konum başına kısıt taşıyamaz. |
| Para/ölçü değeri | float + where | Tutarlılık invariant'ını where ile bağla. |
| Kimlik/kod | string + @match / @fmt | Biçimi schema seviyesinde zorla. |
Query satırı için tip seçmek
v0.4.0'dan beri aynı tip sözlüğü, hiçbir modelin üretmediği veriyi de tipler. Bir query Schema[] döndürür ve onu satır satır doğrular; bir parallel over her eleman sonucunu bildirilen eleman schema'sına göre doğrular. İkisi de düşen pozisyonu adlandırır — Orders.row[0].amount must be a number., classified[2].band must be one of these values: … — yani yukarıdaki tablo hâlâ geçerlidir, ama arkasındaki muhakeme değişir.
Model çıktısı tipi bir pazarlıktır: modelin ne söyleyebileceğini şekillendirirsin ve her tercih, katılığı modelin uyum sağlayabilmesiyle takas eder. Satır tipi ise bir tariftir: şekil zaten tablo tarafından sabitlenmiştir, seninle pazarlık eden kimse yoktur ve tek soru bildirimin köprünün geri verdiğiyle örtüşüp örtüşmediğidir. Girişte hiçbir şey dönüştürülmez — bildirilen tip, sürücünün ürettiği JS değerine karşı kontrol edilir — dolayısıyla Date olarak gelen bir kolon string'i karşılamaz ve çözüm daha gevşek bir tip değil, SQL ya da köprüdür.
| İhtiyaç | Tercih | Neden |
|---|---|---|
NOT NULL kolon | T | Tablo bunu zaten garanti eder. Bunu ihlal eden satır schema kaymasıdır; yutulmasını değil, bildirilmesini istersin. |
| Nullable kolon | T | null | SQL NULL değeri null olarak gelir ve çıplak T satırı reddeder. x?: T burada işe yaramaz — o, eksik anahtara izin verir; select'ten çıkan satır ise kolonu anahtar olarak taşır. |
Aggregate (count, sum) | int / float, çıplak Schema dönüşüyle | [] içermeyen dönüş tipi tam olarak bir satır olduğunu iddia eder: query 'Totals' expected exactly one row, got 3. |
| Tipini henüz netleştirmediğin kolon | any, o da geçici olarak | any hiçbir şey doğrulamaz; sınır sorunu adlandırmayı bırakır ve uyumsuzluk sonradan, satır indeksi olmadan geri döner. Keşif sırasında uygun, zamanlanmış bir işte risktir. |