Skyframe Documentation
Domain Definition
Domain Definition
Define a simple entity
@Entity()export class Customer { @PrimaryKey() id: number;
@Field() email: string;
@Field() firstName: string;
@Field() lastName: string;}Here you have a Customer class with four attributes: id, email, firstName and lastName.
The Customer class is annotated with @Entity(), indicating that it is a Skyframe entity. A Skyframe entity can map to a database table schema in a backend server, a form group in a web app, or an entity that handles business logic in the domain layer.
The id attribute of Customer class is annotated with @PrimaryKey() so Skyframe will recognize it as a primary key field that identifies a single record from a collection of Customers (similar to a PK of a database table).
The other three properties, email, firstName and lastName, are annotated with @Field(), which means that they are simple fields that map to database columns, form fields, attributes inside the JSON response of a REST API endpoint, and so on. (Note that a primary key is just a special case of field)
Field datatypes
Each field has its datatype, in the example above, id has number type, while firstName and lastName have string type. You can change a field's datatype by modifying its TypeScript type annotation.
Here is a list of supported primary datatypes:
stringnumberbooleanDate
Uniqueness check
We can also add an additional uniqueness check to a field by adding a @Unique() decorator above.
This ensures that there will not be two Customers with a same email in our application:
@Entity()export class Customer { @PrimaryKey() id: number;
@Unique() @Field() email: string;
@Field() firstName: string;
@Field() lastName: string;}Adding methods
You can add methods to a Skyframe entity class just like a common TypeScript class:
@Entity()export class Customer { @PrimaryKey() id: number;
@Unique() @Field() email: string;
@Field() firstName: string;
@Field() lastName: string;
public getFullName(): string { return `${this.firstName} ${this.lastName}`; }}Referencing other entities
Sometimes we need to reference another entity by its primary key. We can accomplish this by using @ForeignKey() fields. The use of foreign keys is really important when we are trying to create relations. We will explain that in the next section.
@Entity()export class Order { @PrimaryKey() id: number;
@Field() orderDate: Date;
@ForeignKey() customerId: number;}Connecting entities with relations
There are three kinds of relations: Composition, Aggregation and Knows. Each kind of relation results in different behaviors of CRUD when interacting with the database, different auto-generated endpoints, and different user experiences in the frontend app.
Composition
A composition relation is typically used to connect a parent with a child or children.
This relation denotes a strong dependency link from the child towards the parent.
Remember: A child in a composition relationship has one and only one parent and cannot exist without one. If the parent gets erased, the child will get erased too.
When you try to define a composition relationship, there are two new field decorators that come into play:
@Owns(() => ChildEntity): indicates that the current field is a reference to a child/children of classChildEntity.@OwnedBy(() => ParentEntity): indicates that the current field is a reference to a parent of classParentEntity.
There are some examples:
One to One 👨👦
export class Parent { @PrimaryKey() id: number;
@Field() name: string;
@Owns(() => Child) child: Child;}
export class Child { @PrimaryKey() id: number;
@Field() name: string;
@OwnedBy(() => Parent) parent: Parent;}One (parent) to Many (children) 👨👦👦
By changing the type annotation of the relation field to array type (Child[]), we are indicating that we can have many children instead of one single child.
export class Parent { @PrimaryKey() id: number;
@Field() name: string;
@Owns(() => Child) children: Child[]; // ← Here}
export class Child { @PrimaryKey() id: number;
@Field() name: string;
@OwnedBy(() => Parent) parent: Parent;}How about Many parents to One child 👨👨👦 or Many children 👨👨👦👦?
No, you can't 🤦. Because "A child in a composition relationship has one and only one parent".
Aggregation
This relation denotes a strong dependency link from the aggregator towards the aggregatee. An aggregator has at least one aggregatee and cannot exist without one. The aggregatee cannot be erased if there are aggregators depending on it.
We use @Needs(() => Aggregatee) to indicate that the field is a targeting to an aggregatee class Aggregatee.
We also use @NeededBy(() => Aggregator) to indicate that the field is a targeting to an aggregator class Aggregator.
There goes some examples:
One (aggregator) to Many (aggregatees)
export class Aggregatee { @PrimaryKey() id: number;
@NeededBy(() => Aggregator) aggregators: Aggregator[];}
export class Aggregator { @PrimaryKey() id: number;
@ForeignKey(() => Aggregatee) aggregateeId: number; // ← This will reference the `Aggregatee#id` field.
@Needs(() => Aggregatee) aggregatee: Aggregatee;}One to One
export class Aggregatee { @PrimaryKey() id: number;
@NeededBy(() => Aggregator) aggregators: Aggregator[];}
export class Aggregator { @PrimaryKey() id: number;
@Unique() // ← This says that there cannot be two aggregators with the same aggregatee. @ForeignKey(() => Aggregatee) aggregateeId: number; // ← This will reference the `Aggregatee#id` field.
@Needs(() => Aggregatee) aggregatee: Aggregatee;}Many (aggregators) to One (aggregatee)
class Aggregatee { @PrimaryKey() id: number;
@NeededBy(() => Aggregator) aggregator: Aggregator;}
class Aggregator { @PrimaryKey() id: number;
@Needs(() => Aggregatee) aggregatees: Aggregatee[];}Knows
This kind of relationship defines a simple awareness situation where no entity relies on each other and can exist separately.
In contrast to Composition and Aggregation, this relation does not denote a dependency link between entities.
The knows relationship makes use of @Knows(() => OtherEntity) decorator to indicate the relation field that points to the other side of the relation.
There goes some examples again:
One to One
export class A { @PrimaryKey() id: number;
@ForeignKey(() => B) bId: number;
@Knows(() => B) b: B;}
export class B { @PrimaryKey() id: number;
@Knows(() => A) a: A;}One to Many
export class A { @PrimaryKey() id: number;
@ForeignKey(() => B) bId: number;
@Knows(() => B) b: B;}
export class B { @PrimaryKey() id: number;
@Knows(() => A) a: A[];}