Type

A Type is like a thing with pre-defined properties and/or methods. When an instance of a Type is created, all defined properties are guaranteed to exist with a value matching the Type definition.

Definable properties

definition default description
'str' "" requires type str (values of type str should contain valid UTF-8 characters).
'str<..>' depends requires type str with a certain length (see length condition).
/pattern/ depends requires type str with a math to a specified pattern (see pattern condition).
'utf8' "" requires type str and the value must contain valid UTF-8 characters.
'raw' "" requires type str or bytes.
'bytes' bytes() requires type bytes.
'bool' false requires type bool.
'int' 0 requires type int.
'int<..>' depends requires type int within a given range (see range condition).
'uint' 0 requires a non-negative integer (type int, >= 0).
'pint' 1 requires a positive integer (type int, > 0).
'nint' -1 requires a negative integer (type int, < 0).
'float' 0.0 requires type float.
'float<..>' depends requires type float within a given range (see range condition).
'number' 0 requires type float or type int.
'thing' {} requires a thing.
'X' X{} requires a instance of Type X, or a member of enumerator X. The value X should be replaced with the Type / enum name.
'[]' [] requires a list.
'{}' set() requires a set.
'any' nil any type is valid.

Each definition can be made optional by adding a question-mark ? to the definition. If a property is made optional, then the value nil is allowed instead of the given type and nil will also be the default if the property is missing.

For example

// Create a new type `User` with an optional property `name`.
set_type('User', {name: 'str?'});

// Create a instance of type `User` without a name
unknown = User{};

// ..or explicitly set name to `nil`
user_nil = User{name: nil};

// ..a `str` is also ok
iris = User{name: 'Iris'};

// ..but another type than `str` or `nil` is not allowed
assert(iserr(try(User{name: 0})));

// Return the results
[unknown, user_nil, iris];

Return value in JSON format

[
    {
        "name": null
    },
    {
        "name": null
    },
    {
        "name": "Iris"
    }
]

When using a list '[]' or set '{}' definition, it is also possible to make the list or set restricted to a certain type. In this case only items of the given definition are allowed as members. For example '[int]' requires the members of a list to be integers and '{User}' is a restricted set which only allows things of type User.

Both the list and/or the members can be made optional. For example, this '[str?]?' is a valid declaration. Since a set does not allow for nil values, it is not possible to make members of a set optional.

This is an example using a restricted list:

// Very simple `Note` type
set_type('Note', {
    text: 'str',
    timestamp: 'uint'
});

// Book type with `notes` of type `Note`
set_type('Book', {
    title: 'str',
    notes: '[Note]'
});

// Create a new book
book = Book{
    title: "hitchhiker's guide to the galaxy",
};

// Add a note to the book
book.notes.push(Note{
    text: 'the answer is 42',
    timestamp: int(now())
});

// It *must* be a `Note`, something else is not allowed
assert(iserr(try(book.notes.push({test: 'not a Note'}))));

// Return the book, 2 levels deep to see the note
return(book, 2);

Return value in JSON format

{
    "notes": [
        {
            "text": "the answer is 42",
            "timestamp": 1573894579
        }
    ],
    "title": "hitchhiker's guide to the galaxy"
}

Length condition

A length condition may be added to a str type specification using the following syntax:

str<min:max:default>
argument description
min minimal length (inclusive).
max maximum length (inclusive, this value must be equal to or greater than min).
default Optional default value. If not given, the default value will be the smallest possible string filled with the dash (-) character.

For example:

set_type('Person', {
    name: 'str<1:10>',
    email: 'str<3:50:info@thingsdb.net>'
});

/*
 *  Each Person instance will contain a name with at least 1 character
 *  and at most 10 characters and an email property with a string of at
 *  least 3 and at most 50 characters.
 */

Person{};  // return a Person with default values

Return value in JSON format

{
    "name": "-",
    "email": "info@thingsdb.net"
}

A length condition cannot be used as a nested array specification, thus something like "[str<1:10>]" is not possible.

Pattern condition

A pattern condition applies to type str and may be used using the following syntax:

/pattern/i

The i is optional and tells the pattern to be case insensitive. If left away, the pattern will thus be case sensitive.

The specification must be either nillable by adding a ? to the specification or an empty string must match with the given pattern. If this is not the case, then a default value must be given using the syntax below:

/pattern/i<default>

It is always possible to set a default value as long as the default value matches with the given pattern.

Here is an example using some pattern conditions:

set_type('Words', {
    example1: '/^(e|h|l|o)*$/',
    example2: '/thingsdb/i<ThingsDB>',
    example3: '/^[0-9]{4}[A-Z]{2}$/?',
    example4: '/^[0-9]{4}[A-Z]{2}$/<1234AB>?',
});

/*
 *  example1 - matches an empty string so a default value is not required
 *  example2 - case insensitive and requires a default value
 *  example3 - nillable and thus no default value is required
 *  example4 - exactly the same as example3 but with a default value
 */

Words{};  // return a Person with default values

Return value in JSON format

{
    "example1": "",
    "example2": "ThingsDB",
    "example3": null,
    "example4": "1234AB"
}

A pattern condition cannot be used as a nested array specification, thus something like "[/pattern/i]" is not possible.

Range condition

A range condition may be added to a int or float type specification using the following syntax:

int<min:max:default>

Or

float<min:max:default>
argument description
min minimal value (inclusive).
max maximum value (inclusive, this value must be equal to or greater than min).
default Optional default value. If not given, the default value will be the closest possible value towards zero (0).

For example:

set_type('Values', {
    a: 'int<10:20>',
    b: 'int<0:10:5>',
    c: 'float<-1:1>',
    d: 'float<0:1:0.5>',
});


Values{};  // return a `Values` instance with default values

Return value in JSON format

{
    "a": 10,
    "b": 5,
    "c": 0,
    "d": 0.5
}

A range condition cannot be used as a nested array specification, thus something like "[int<1:10>]" is not possible.

Get instance of a type

The thing(..) function may be used to get an instance of a given ID. This does not guarantee that the thing is truly of the type you expect. Instead of adding a type_assert(..) on the thing, it is also possible to call the type as a function with the ID as it’s first argument.

For example:

// Suppose we have a user_id and want to get the user
user = User(user_id);

Possible errors:

  • A lookup_err() will be raised if no thing with the given user_id exists.
  • A type_err() will be raised if a thing is found, but the thing is not of the User type,

Methods

A method is a closure attached to a type. Methods are defined when creating a type by attaching a closure instead of a definition. When a method is used on an instance of a type, the first argument will be the instance itself. It is therefore common to name the first argument this.

For example:

set_type('Person', {
    name: 'str',
    age: 'int',
    whoami: |this| `My name is {this.name} and I am {this.age} years old.`
});

.iris = Person{
    name: 'Iris',
    age: 7
};

.iris.whoami();

Return value in JSON format

"My name is Iris and I am 7 years old."
Function Description
del_type Delete a Type.
has_type Determine if the current scope has a Type.
mod_type Modify an existing Type definition.
new_type Create a new Type.
rename_type Rename the Type.
set_type Set property definitions on a Type and creates the Type if it did not exist.
type_info Return the Type definition.
types_info Return all Type definitions in the current scope.

Mutation format

A mutation format is only required to understand if you manually want to parse events when watching for mutations on things. While most values have a pretty straightforward format when packed in a mutation, a Type instance is a bit more complex to understand.

An example of a Type mutation:

{
    ".": 10,
    "#": 123,
    "": [
        "hitchhiker's guide to the galaxy"
        4.2
    ]
}
Key Description
"." The type_id of the Type.
"#" The #ID which is assigned to the Type instance.
"" Array with fields;

In order to correctly parse the mutation you require the types_info of a collection. Note that the types info is included inside an init event. Below is an example with an entry of types_info. This shows type Book which has ID 10 from the mutation example above.

...
{
    "created_at": 1581454041,
    "fields": [
        ["title", "str"]
        ["rating", "float"]
    ],
    "methods": {},
    "modified_at": 1581455876,
    "name": "Book",
    "type_id": 10,
    "wrap_only": false
}
...