Types
In this section, we describe the typing system of Eidos. Types are the central building block used throughout the Eidos framework. In a general setting types just describe properties of values. For example, if a
has type number
, written as a: number
, we are just stating certain properties of a
: for example that you can perform addition: a + a
.
The Eidos types are described using TypeScript. This has many benefits, for example that you don't need to learn yet another syntax, that many development tools are available, that we can make use of many features of the language and most importantly, that you can use the same types for Eidos as you use in the rest of your codebase. It is highly recommended to use the Eidos typings as the central typing source for you main domain entities.
Even though we use the same syntax as TypeScript, our semantics are somewhat different. First of all, we only support a limited subset of the TypeScript concepts. More advanced types, like mapped types are not supported. In the future we will broaden this limited subset to increase the range of types supported by Eidos in addition to adding our own typing concepts.
The main difference between our typing system and that of TypeScript is that TypeScript has a structural typing system, whereas we use a nominal typing system. There are many great articles that describe this distinction in great detail. For our use case, it boils down to the following: if you name a type, you create a new type. For example, if you create a type type FirstName = string
, the types FirstName
and string
would be indistinguishable in TypeScript. Within Eidos, this is not the case. Whenever you give a type a name, for example using the type
keyword, using interface
or class
, you create a new concept. This gives us the ability to make semantic differences between these similar types. The beautiful thing is that your code does not need to change! You can still use the FirstName
type as an ordinary string
if you want, but Eidos has the power to create different effects based on these namings. For example, you might imagine that the label
of an FirstName
field is different than that of an ordinary string
field.
In the list below we describe the supported Eidos types and how to combine them.
Primitive types
Basic types
As expected we support the basic TypeScript types string
, number
and boolean
. We also support string literal types, such as "test"
. We advise against the usage of boolean
types for a few reasons. First, booleans seldom stay booleans. A client might claim that the answer to a question is either yes
xor no
, but after actual usage discover that there are many maybe
, not_applicable
or unknown
cases. We even discourage the usage of boolean
for obviously binary cases in favour of union types (see below) because they are more extendable and have better translation support. (If you are concerned about memory usage, consider that you reading this section probably costs your clients more money than a billion booleans converted to strings would cost in additional storage and that migrating a boolean into a string costs way more than that)
Composite types
Alias types
The most basic way to create new types is by giving old types a new name. By default we provide the following alias types:
// Fields generated only accept integer values
type integer = number;
// Fields generated are multi-line
type text = string;
You can create your own alias types and override default behaviour. If in doubt, we recommend to create a custom field because for example SchoolYear
is way more descriptive than string
.
Union
We support union types of string literals. Other unions are currently not supported. We recommend to use them as often as possible as they are flexible, easy to use and appear often. The different literals within the union can be given different translation labels. Below is an obvious example:
type Level = "beginner" | "intermediate" | "advanced";
Object and References
We allow you to create objects with property values. These properties might be optional (using the ?
syntax). Objects might extend the BaseObject
type. If a type is an extension of BaseObject
it signals to Eidos that it is one of the primary types, similar to mysql tables.
interface Person extends BaseObject {
name: string;
age: integer;
level?: Level;
// A referenced list of Notes
notes: List<Ref<Note>>;
// A referenced person
spouse: Ref<Person>;
// A (owned) task
goal: Task;
// A (owned) list of tasks
tasks: List<Task>;
}
Objects might contain composite objects (meaning objects within objects). They are (currently) required to also be BaseObjects
. These objects might be owned xor referenced. They are referenced if you wrap them in a Ref
type, and owned if not.
Owned composite objects
- It can only be accessed through the parent type
- The parent type cannot be deleted if it still owns objects
- The resources of these types need to be fetched explicitly (they are lazy)
Referenced composite objects
- Can be created in some other way
- Are standalone
- The reference can be deleted without the original being deleted and vice versa.
List
Eidos does not support the standard TypeScript arrays, but uses objects with a variable number of keys instead. We have the polymorphic type
type List<T extends Base> = { [id: string]: T };
and also
type OrderedList<T extends OrderedListMember> = List<T>;
Here Base
is an interface with just a property id: number
, and OrderedListMember
adds the property listIndex: number
.