This is the ThingsDB documentation for version v0, click here for the latest version!
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.
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 and default value) |
/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 and default value). |
'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 and default value). |
'number' |
0 |
requires type float or type int. |
'datetime' |
datetime() |
requires type datetime. (defaults to the current date/time) |
'timeval' |
timeval() |
requires type timeval. (defaults to the current date/time) |
'regex' |
regex('.*') |
requires type regex. |
'closure' |
` | |
'error' |
err() |
requires type error. |
'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 (with the exception of a future). |
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(is_err(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(is_err(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"
}
A length condition may be added to a str
type definition using the following syntax:
str<min:max:default>
argument | description |
---|---|
min |
Optional minimal length (inclusive). |
max |
Optional 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.io>'
});
/*
* 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.io"
}
It is not possible to use a length condition as a nested array definition. For example, something like "[str<1:10>]"
is not allowed.
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 definition must be either nillable by adding a ?
to the definition 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 definition, thus something like "[/pattern/i]"
is not possible.
A range condition may be added to a int
or float
type definition using the following syntax:
int<min:max:default>
Or
float<min:max:default>
argument | description |
---|---|
min |
Optional minimal value (inclusive). |
max |
Optional 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 definition, thus something like "[int<1:10>]"
is not possible.
Both the min
and max
of a range and length condition are optional. For this reason you can use the “condition” syntax to configure a default value for a property on a Type.
(You may want to combine a default value with a minimum and/or maximum limit and that is fine; This example is just to make it clear that setting a min
and/or max
value is not required.)
For example:
set_type('TestDefault', {
f: 'float<::3.14>',
i: 'int<::42>',
s: 'str<::ThingsDB>',
});
TestDefault{}; // return a `TestDefault` instance with default values
Return value in JSON format
{
"f": 3.14,
"i": 42,
"s": "ThingsDB"
}
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:
user_id
exists.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. |
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
}
...