Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Fabric Apps uses TypeScript decorators to define data models that generate database tables and APIs. You define each entity as a class decorated with @entity(), add field decorators for data types, and map relationships between entities.
For authorization and access control guidance, see Define data permissions.
Prerequisites
- A Fabric Apps project created with
npm create @microsoft/rayfin@latestor initialized withnpx rayfin init. - Basic understanding of TypeScript classes and decorators.
Define an entity
To create a data model, add the @entity() decorator to a TypeScript class. Then import the required decorators from @microsoft/rayfin-core:
import { entity, uuid, text, date } from '@microsoft/rayfin-core';
@entity()
export class Todo {
@uuid() id!: string;
@text() title!: string;
@text({ optional: true }) description?: string;
@date() createdAt!: Date;
@date() updatedAt!: Date;
}
This entity generates a Todo table with columns for id, title, description, createdAt, and updatedAt.
Primary keys
Every entity uses a UUID string field named id as its primary key. If you don't declare id explicitly, Fabric Apps adds it to the schema automatically.
- The
idfield is optional during create operations—the server generates a UUID if you omit it. - You can supply your own UUID at creation time if you prefer client-generated identifiers.
- Composite primary keys and custom key names aren't supported.
@entity()
export class Note {
@uuid() id!: string; // UUID primary key, auto-generated when omitted
@text() title!: string;
@text() content!: string;
}
Supported data types
Use these decorators to define field types:
| Decorator | Type | Description |
|---|---|---|
@uuid() |
string | Unique identifier field. |
@text() |
string | Text field with optional length constraints. |
@int() |
number | Integer field. |
@decimal() |
number | Decimal or numeric field. |
@boolean() |
boolean | True or false field. |
@date() |
Date | Date and time field, serializes from ISO strings or Date objects. |
@email() |
string | Text field with email validation. |
@set() |
string | Enumerated set of string literals. |
Example with multiple types
import { entity, uuid, text, int, decimal, boolean, date, set } from '@microsoft/rayfin-core';
@entity()
export class Product {
@uuid() id!: string;
@text() name!: string;
@decimal() price!: number;
@int() stockQuantity!: number;
@boolean() isAvailable!: boolean;
@date() createdAt!: Date;
@set('draft', 'published', 'archived') status!: 'draft' | 'published' | 'archived';
}
Type modifiers
Add modifiers to field decorators to configure validation and constraints:
| Modifier | Description |
|---|---|
{ optional: true } |
Allow NULL values. Fields are required by default. |
{ unique: true } |
Add a unique constraint. |
{ default: value } |
Set a default value expression. |
{ max: n }, { min: n } |
String length constraints (maximum and minimum number of characters). |
{ min: n }, { max: n } |
Numeric value constraints. |
Note
The TypeScript optional marker (? after a property name) only affects the static TypeScript type. It doesn't make the database column nullable. To make a field nullable, add { optional: true } to the decorator. Use ! to assert that a required field is initialized by the framework.
Example with modifiers
@entity()
export class User {
@uuid() id!: string;
@email({ unique: true }) email!: string;
@text({ min: 3, max: 50 }) username!: string;
@text({ optional: true, max: 500 }) bio?: string;
@int({ min: 0, max: 150 }) age!: number;
@boolean({ default: false }) isVerified!: boolean;
}
Define relationships
Use @one() and @many() decorators to define navigation properties between entities. Fabric Apps auto-generates foreign key columns when you define relationships.
- One-to-many – Use
@many()on the parent and@one()on the child. - Many-to-one – Use
@one()on the child to reference the parent. - Many-to-many relationships aren't supported—use an explicit join entity instead.
Example with one-to-many relationship
import { entity, uuid, text, date, one, many } from '@microsoft/rayfin-core';
@entity()
export class Notebook {
@uuid() id!: string;
@text() name!: string;
@date() createdAt!: Date;
@many(() => Note) notes?: Note[];
}
@entity()
export class Note {
@uuid() id!: string;
@text() title!: string;
@text() content!: string;
@date() createdAt!: Date;
@text() notebook_id!: string;
@one(() => Notebook) notebook?: Notebook;
}
When you define @one(() => Notebook) on the Note entity, Fabric Apps automatically creates a notebook_id foreign key column. Declare the foreign key field explicitly only if you plan to read or set it in application code.
Foreign key naming convention
When you define a foreign key field, use the {property}_id naming convention:
@entity()
export class Note {
@uuid() id!: string;
@text() notebook_id!: string; // Foreign key field
@one(() => Notebook) notebook?: Notebook; // Navigation property
}
Custom foreign key names (foreignKey, targetKey options) aren't supported.
Reference system entities
Fabric Apps doesn't support @one() relationships that point to system entities such as the built-in USER entity. To associate a row with the signed-in user, add a plain user_id field of type @text() and populate it from the auth claims (typically claims.sub):
@entity()
export class Task {
@uuid() id!: string;
@text() title!: string;
@text() user_id!: string; // System user ID from claims.sub
}
Filter rows by user_id in your role policies to enforce per-user access.
Register entities in the schema
Add all entity classes to rayfin/data/schema.ts so the client can generate GraphQL proxies:
import type { Note } from './Note.js';
import type { Notebook } from './Notebook.js';
export type NotesAppSchema = {
Note: Note;
Notebook: Notebook;
};
Update this type whenever you create a new entity.
Apply schema changes
After defining or modifying entities, apply the changes to the database:
Deploy the updated schema to Fabric:
npx rayfin up db applyIf the schema change includes destructive operations (dropping columns, renaming tables), the CLI warns you and refuses to proceed. Use
--forceto override the safety check:npx rayfin up db apply --force
Note
Using --force can cause data loss. Review the listed operations carefully before proceeding.
Best practices
- Define foreign key fields only when you need to read or set them in code—Fabric Apps generates them automatically from navigation decorators.
- Use relative imports with
.jsextensions in entity files so the emitted ESM JavaScript resolves correctly. - For authorization patterns and role-based access, see Define data permissions.
Troubleshooting
Missing relationships
If relationships don't appear in the API, verify that:
- The navigation decorator (
@one()or@many()) is present. - The entity is registered in
rayfin/data/schema.ts.