Skip to main content

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.