Schema directives

Shinzo schemas use four SDL directives. Two (@index and @relation) appear in the primitive schemas, @crdt appears on the attestation record, and @materialized is the materialization switch on View SDL.

DirectiveWhere it appearsWhat it does
@relation(name: "...")Primitive schemasConnects two types so queries can nest across them.
@index and @index(unique: true)Primitive schemasIndexes a field for faster filtering; unique additionally enforces one document per value.
@crdt(type: pcounter)AttestationRecord.vote_countMakes the field a positive-counter CRDT.
@materialized(if: true)View SDLTells DefraDB to pre-compute and store the View's output documents.

@relation

Connects two types into a relation. Both sides declare the same name, and queries nest across the relation in one round trip:

type <Chain>__<Network>__Block {
    transactions: [<Chain>__<Network>__Transaction] @relation(name: "block_transactions")
}

type <Chain>__<Network>__Transaction {
    block: <Chain>__<Network>__Block @relation(name: "block_transactions")
}

Primitives lists all four relation names and the fields they connect.

@index

Marks a field as indexed. Filtering on an indexed field avoids scanning every document in the collection, so the schemas put it on the fields you filter by most: blockNumber, attested_doc, doc_type. With unique: true, DefraDB enforces one document per field value. The Host's copy of the schema declares unique indexes on block and transaction hashes, so the same block cannot be stored twice on a Host:

hash: String @index(unique: true)

@crdt

Tells DefraDB to merge the field as a conflict-free replicated data type instead of last-write-wins. The one use today is vote_count on AttestationRecord, a positive counter (pcounter): each node tracks its own increments, and merges sum them deterministically. See AttestationRecord for the merge example.

@materialized

The View SDL's materialization switch, applied to the View's output type. @materialized(if: true) tells DefraDB to pre-compute and store the output documents as the Host processes data. if: false leaves them virtual: the View is computed per query instead, which trades query speed for not storing a second copy of the data.

type EventView @materialized(if: false) { transactionHash: String }

Choose materialized vs on-query covers the trade-off, and Viewkit covers the rest of the View bundle format.