Skip to main content

Wrapper Schema

Every wrapper project has a schema. The schema defines the wrapper's dependencies, methods, and custom types. In short, it's an interface describing how to use the wrapper.

The schema is a sort of contract between a wrapper's developers and its users. A Wasm wrapper will not compile unless it implements and exports all of the methods declared in the schema. A Wasm wrapper's users can be certain that all of the methods declared in a schema are callable using the declared method signatures. Plugin wrappers do not have this same guarantee. However, the client will throw an exception if a user attempts to invoke a method and the method is not found.

Declaration#

A wrapper project's schema must be declared in the project manifest (Polywrap Manifest or Plugin Manifest).

schema: ./src/schema.graphql

Codegen#

The wrapper schema tells the Polywrap compiler which types and serialization logic to generate. Generated types are updated every time you build the wrapper. The polywrap CLI also provides a codegen command to quickly update the generated wrap folder where generated types are stored.

The Polywrap compiler generates types for method arguments and custom schema types. Wrapper developers must use the generated method arguments for methods declared in the schema, and generated types for method return values when the return value is of a custom type. The generated types come with serialization logic that allows Wrappers to communicate with the client.

The following example demonstrates this practice.

type Module {
foo(
bar: String!
arg: Int!
): ReturnType!
}
type ReturnType {
prop: Int!
}

Language Syntax#

Wrapper schemas are written in Polywrap's variant of the GraphQL schema definition language (SDL). Polywrap's GraphQL variant is simpler than standard GraphQL and easy to learn.

Custom Types#

Custom types are declared with the type keyword. They can be given any number of properties. Properties can be of any supported type, including other custom types.

type CustomType {
myString: String!
myObject: AnotherType!
}
type AnotherType {
myInt: Int!
}

Nullability#

As in standard GraphQL, properties, method arguments, and method return values are declared non-nullable by appending a ! at the end of the type name. The absence of a ! indicates that a value is nullable.

type CustomType {
nullable: String # can be String or null
nonNullable: String! # must be String
}

Methods#

Methods are declared within the Module type. Methods must return a value.

type Module {
foo(
arg: CustomType!
bar: CustomType
): ReturnType!
}

Custom types and modules can be imported from other wrappers by placing an import statement at the top of the schema. Imports become available in the schema immediately and in the wrapper following codegen.

Imports#

Imported types and modules must be assigned a namespace. References to them are prepended with the namespace.

#import { Module, CustomType } into Namespace from "wrap://authority/path"
type CustomType {
prop: Namespace_CustomType!
}

Example#

The following example uses all of the elements we've discussed so far.

#import { Module, Connection } into Ethereum from "wrap://ens/ethereum.polywrap.eth"
#import { Module } into Ipfs from "wrap://ens/ipfs.polywrap.eth"
type Module { # methods are declared in type Module
getIpfsData(
address: String! # types ending with ! are non-nullable
connection: Ethereum_Connection # imported types are used like local types
): String!
setIpfsData(
options: SetIpfsDataOptions! # custom types can be arguments
connection: Ethereum_Connection
): SetIpfsDataResult! # custom types can be return values
}
type SetIpfsDataOptions { # custom types are declared with "type" keyword
address: String!
data: String!
}
type SetIpfsDataResult {
ipfsHash: String!
txReceipt: String!
}

Default Types#

Polywrap's schema definition language supports three kinds of default types:

  • Basic types like integers and strings
  • Complex types like BigInt and JSON
  • Generic types like Array and Map

These types may be implemented differently in different programming languages. The language-specific implementations are clarified in each of the following sections.

Basic Types#

Basic types include primitive types like integers and boolean values, as well as Strings and Bytes.

Schema TypeDescription
UInt32-bit unsigned integer
UInt88-bit unsigned integer
UInt1616-bit unsigned integer
UInt3232-bit unsigned integer
Int32-bit signed integer
Int88-bit signed integer
Int1616-bit signed integer
Int3232-bit signed integer
StringUTF-8 string
BooleanTrue or false stored as 1 byte
BytesArray of 8-bit unsigned integers

Complex Types#

Complex types are represented as basic types to the Polywrap client and plugin wrappers. Within Wasm wrappers, they are implemented as classes or structs with behavior.

For example, consider an application developer invoking a Wasm wrapper with the JavaScript client. If the invoked method requires an argument of type BigInt, the application developer will provide a string representation of an integer number to satisfy the argument. Within the wrapper, the string is deserialized into an instantion of the wrapper language's implementation of a BigInt. If the invoked method returns a BigInt, the wrapper will serialize the returned BigInt, which the client will then deserialize into a string before returning it to the application developer.

Schema TypeDescription
BigIntMultiple precision integer
BigNumberMultiple precision float
JSONJSON object

Generic Types#

When using a generic type, with the exception of arrays, schema writers must add a directive annotation declaring its subtype(s).

The directive takes the form @annotate(type: "TypeName<T\>"), where T is the type of the subtype. The generic type and its subtype(s) can be independently nullable or non-nullable. The following example illustrates use of the generic type directive with the Map type.

type Module {
getKey(
key: String!
map: Map! @annotate(type: "Map<String!, Int!\>!")
): Int!
returnMap(
map: Map! @annotate(type: "Map<String!, Int!\>!")
): Map! @annotate(type: "Map<String!, Int!\>!")
}

Like complex types, the implementation of generic types can differ by language.

Schema TypeDescription
[Type]Array of elements.
MapMap of key-value pairs.

Interfaces#

A module can inherit the method declarations of any other module by using the implements keyword. A module that implements an interface in this manner is required to implement and export the interface module's declared methods.

Similarly, a type can inherit the property declarations of any other type by using the implements keyword. If a type implements an interface, it inherits the interface type's property declarations as though the properties were declared explicitly.

#import { Module, InterfaceType } into Interface from "wrap://ens/interface.eth"
type Module implements Interface_Module {
# declares methods of Interface_Module
}
type ImplementationType implements Interface_InterfaceType {
# declares properties of Interface_InterfaceType
}