Build / Structs

Structs

A struct names the shape of an object: its fields and the type of each. An organization declares it once, and anyone on the network can use it as a type in contracts, event types and resource functions, so values are checked wherever they travel instead of passing as an unchecked object.

Language
RequestScript 1.7+
Declare
POST /v1/structs
TypeScript
defineStruct
Versions
Immutable, never deleted

01

What is a struct?

Without structs, a contract that takes a customer takes an object, and every caller has to guess its fields. A struct gives that shape a name and a version that every node agrees on:

  • One owner. An organization declares a struct on one of its nodes, in RequestScript or in TypeScript, and owns its path.
  • Anyone can use it. Once a node knows a struct, any script on it may name the struct as a type. Nodes learn structs from their peers.
  • Pinned versions. Every declaration and every reference names a version. Versions are immutable and never deleted, so a reference always means the same fields.

02

Declare a struct

A struct is a script of its own. It declares its fields and has no body. Fields are name: type pairs of any RequestScript type, including other structs and lists of them. Unlike contracts and event types, a struct must name its version.

Customer.req
struct com.example.Address@1(street: string, city: string, postcode: string)

struct com.example.Customer@2(
  name: string,
  addresses: []com.example.Address@1,
  credit: decimal
)

Declare it on a node of your organization with its API key, or a run token from one of its own API clients. The node's organization owns the struct.

Terminal
curl -X POST https://node.example/v1/structs \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $API_KEY" \
  -d '{"source": "struct com.example.Address@1(street: string, city: string, postcode: string)"}'
  • The first organization to declare a path keeps it. No other organization can declare any version of it.
  • Versions don't change. Declaring the same version again with the same fields does nothing; with different fields it's refused. To change a shape, declare a new version.
  • References are checked once. Every struct a declaration names must name its version and be known to the node, and a struct can't refer to itself, directly or through others.
  • There's no DELETE. Contracts and event types on other nodes may name any version, so a version stays declared for good.

GET /v1/structs lists the structs a node knows, its own and those it learned from peers. GET /v1/structs/com.example.Address@1 returns one, or the latest version when none is named.

03

Use structs as types

A struct type is the struct's qualified name and its version. It can appear wherever a type can: contract parameters and return types, var annotations including state variables, event type fields, and resource function types declared in TypeScript.

Ship.req
contract com.example.Ship(customer: com.example.Customer@2): com.example.Address@1 {
  var first: com.example.Address@1 = customer.addresses[0]
  return first
}

event com.example.OrderShipped(to: com.example.Address@1)

A struct value is written as an object literal, and checked when it's assigned, passed or returned:

Ship.req
var home: com.example.Address@1 = { street: "1 Main St", city: "Springfield", postcode: "12345" }

A reference without a version, like com.example.Address, is an error. A reference never moves to a later version: declaring @2 changes nothing that names @1, so every node checks a value against the same fields, whichever versions it has learned since.

04

How values are checked

Checking is structural and lenient, so structs from different organizations fit together:

CaseWhat happens
Same fields, other structfits: a value fits a struct if its fields fit, whatever it was built as, including a plain object
Declared fieldconverted to its type, as an assignment would be; one that doesn't fit is a runtime error naming the struct and the field
Missing fieldnull, which fills any type, so a struct can't require a field
Undeclared fieldkept unchanged
objectusable where a struct is expected, checked when it's converted, and the other way around
nullfits every struct type

Values travel as JSON objects. An int64 or decimal that can't round-trip through a JSON number is a string. Contract state stores a struct value as the object it is.

05

Structs in TypeScript

A resource function can name structs in its parameter and return types. Define them with defineStruct, which takes the version as its second argument, and register them with the node, or pass them to start as structs: [Forecast].

weather.ts
import { defineStruct, StellariumNode } from 'stellarium-ts';

const Forecast = defineStruct('com.example.Forecast', 1, {
  city: 'string',
  highs: '[]decimal',
  summary: 'string',
});

// In a resource function:
//   returnType: Forecast.type, // 'com.example.Forecast@1'

const node = new StellariumNode();
await node.registerStruct(Forecast);
await node.register(weather);
  • Field types are RequestScript type strings. A field may name another struct by its .type or as 'com.example.Address@1'.
  • The node needs an operator. It waits for its session to learn its organization, then declares each struct as that organization's. A path another organization owns, a version declared with different fields, or an unknown struct stops it from starting.
  • Parameters are checked before anything is paid. When a peer calls the function, the node checks its struct-typed parameters and answers 400 when one doesn't fit.
  • Return values are checked by the caller when they arrive, so a value that doesn't fit fails the caller's script.

06

How structs travel

Structs are learned from peers, and every announcement is signed and checked against the operator, so no node can claim another organization's types.

01

Nodes announce

Each node signs the structs its organization owns and announces them to its peers with its membership token, again whenever it declares one.

02

Starting nodes fetch first

A starting node fetches every peer's struct announcement before it learns their event types and resources, which may refer to them.

03

Peers check ownership

A receiver learns a struct only if the token says the announcing node belongs to its owner. A path another organization owns, or a version with different fields, is rejected.

04

Versions stay known

A struct a node learned stays known after the peer stops announcing it, since a version never goes away.

A node knows only the structs of its peers' organizations. A struct that refers to another organization's struct can't be used until the node learns that one from a node of its owner.

07

Unknown structs

  • Deploying a contract, event type or struct that names a struct the node doesn't know is refused.
  • Learned resources are still learned. A script calling a function that names an unknown struct fails with an Unknown struct error before any payment or network call, and the resource's other functions still work.
  • Event types whose fields name an unknown struct aren't learned until the node learns the struct and the peer announces its events again. See Events.
  • Scripts that name an unknown struct fail before they run anything.

A struct has at most 64 fields, nests at most 8 deep counting lists of structs, and its source is at most 16 KiB.

08

Reference

Read the full reference in the stellarium-ts structs guide and the documentation. Structs need RequestScript 1.7.0, which adds struct declarations and struct types; learn the language at requestscript.org.

Published a struct others can build on? Share it on Discord.