Field types
Complete reference for Rayfin's field decorators — @uuid, @text, @int, @decimal, @boolean, @date, @email, @set, and @blob — and the options each accepts.
Every property on a @entity() class needs exactly one field
decorator. The decorator determines the database column type, the GraphQL scalar, and the
constraints Rayfin enforces for you. This page lists every decorator, the options it
accepts, and the patterns that trip people up — especially the MSSQL text length rule.
Decorator reference
| Decorator | Logical type | Extra options beyond the common set | Notes |
|---|---|---|---|
@uuid() | UUID | — | Conventionally the primary key (id). Also used for foreign key columns. |
@text() | string | max, min, regex | Always set max on MSSQL — see below. |
@int() | integer | max, min | Whole numbers. |
@decimal() | decimal / numeric | max, min, precision, scale | Defaults to DECIMAL(18,2). |
@boolean() | boolean | — | True/false. |
@date() | datetime | — | Accepts a Date, an ISO string, or a numeric timestamp on write; serializes as ISO on read. |
@email() | string | max, min, regex | Text field with email-shaped validation. Same option set as @text(). |
@set(...values) | string enum | — | A fixed list of allowed string literals. |
@blob() | — | — | A class decorator for storage folders, not a field type. See below. |
Every field decorator except @blob() also accepts the common options in the next
section.
Common options
These apply to @uuid(), @text(), @int(), @decimal(), @boolean(), @date(),
@email(), and @set():
| Option | Type | Default | Effect |
|---|---|---|---|
optional | boolean | false | Allows NULL in the database. Pair with a ? on the TypeScript property — see below. |
unique | boolean | false | Adds a unique constraint on the column. |
default | matches the field's type | — | Default value used when the field is omitted on create. |
column | string | property name | SQL column name to create or map for this field. The GraphQL field stays the TypeScript property name. New in 1.36 |
description | string | — | Free-text note attached to the field's metadata. Does not change validation or the database column. |
import { entity, authenticated, uuid, text, boolean, date } from '@microsoft/rayfin-core';
@entity()
@authenticated('*', {
policy: (claims, item) => claims.sub.eq(item.user_id),
})
export class Todo {
@uuid() id!: string;
@text({ max: 200 }) title!: string;
@boolean({ default: false }) isCompleted!: boolean;
@date() createdAt!: Date;
@text({ max: 128, description: 'Owning user, set from claims.sub on create' })
user_id!: string;
}The nullable pattern
Fields are required by default. To make a field nullable, you must do two things
together — set { optional: true } in the decorator and mark the TypeScript property
with ?. Either one alone is not enough for consistent behavior between the database
constraint and the generated types.
@text() title!: string; // required
@text({ optional: true }) notes?: string; // nullable — both the option and `?` are presentText length and MSSQL
Warning
On MSSQL, a @text() field without max generates an NVARCHAR(MAX) column. Rayfin's
metadata provider can fail to build a GraphQL schema from NVARCHAR(MAX) columns,
producing an "Internal server error" at runtime — after rayfin up has already reported
success. Always set max on every @text() field: @text({ max: 200 }). This applies
to @email() too, since it shares @text()'s option set.
There is no safe default length to omit — pick a max that fits the data (50 for a
short name, 2000 for a description, and so on). If a deploy succeeds but GraphQL queries
against a new or changed entity start failing, check every @text() field on that entity
for a missing max first.
@decimal() precision and scale
precision is the total number of digits (before and after the decimal point); scale is
the number of digits after it. They must be provided together — if you set one, set the
other. Omit both to get the default, DECIMAL(18,2). Maximum precision is 28, a limit
imposed by the Data API Builder runtime.
import { entity, authenticated, uuid, decimal } from '@microsoft/rayfin-core';
@entity()
@authenticated('*')
export class Product {
@uuid() id!: string;
@decimal() price!: number; // DECIMAL(18,2) by default
@decimal({ precision: 10, scale: 4 }) weight!: number; // DECIMAL(10,4)
}@set() string enums
@set() takes the allowed values as separate string arguments, and the TypeScript union
type should match:
@set('low', 'medium', 'high') priority!: 'low' | 'medium' | 'high';To add common options (optional, unique, default) to a set field, pass an options
object as the first argument instead, followed by the allowed values:
@set({ optional: true }, 'low', 'medium', 'high') priority?: 'low' | 'medium' | 'high';@blob() storage folders
Warning
Storage is experimental and is not available in every Fabric region or tenant. See Storage before you depend on it.
@blob() is a class-level decorator for experimental Rayfin Storage folders. Import it from @microsoft/rayfin-core/experimental, not from the stable package root.
import { role } from '@microsoft/rayfin-core';
import {
blob,
ContentTypes,
mb,
StorageObject,
} from '@microsoft/rayfin-core/experimental';
@blob({
name: 'profile-images',
onConflict: 'overwrite',
maxSize: mb(5),
allowedContentTypes: [ContentTypes.AnyImage],
})
@role('authenticated', '*', {
policy: (claims, item) => claims.sub.eq(item.owner_id),
})
export class ProfileImage extends StorageObject {}StorageObject declares server-managed intrinsic fields such as owner_id, id, path, size, and etag; do not pass those through upload metadata. Add decorated fields to the class only for application metadata you will send with upload({ fields }).
@blob() option | Type | Default | Description |
|---|---|---|---|
name | string | normalized class name | Storage folder name. |
onConflict | 'error' | 'overwrite' | 'error' | Folder-level conflict behavior. overwrite requires authenticated update permission. |
maxSize | number | ByteSize | service limit | Maximum object size. Use bytes(), kb(), mb(), or gb() helpers. |
allowedContentTypes | MimeGlob[] | any type | MIME globs such as 'image/*' or ContentTypes.Pdf; unsupported uploads fail with UnsupportedContentType. |
Every storage folder needs at least one authenticated permission rule. Anonymous storage permissions are not supported. File upload, download, listing, and delete operations are part of @microsoft/rayfin-storage, not client.data.
In my Rayfin project, add an "email" field to the User entity in rayfin/data/User.ts
using the @email() decorator with max: 254 and unique: true. Make sure the TypeScript
property is required (no `?`), since the field should not be nullable.Modeling entities
Define Rayfin entities as decorated TypeScript classes in rayfin/data/ and register them in schema.ts to get a database table and a typed API.
Relationships
Model one-to-many associations between Rayfin entities with @one and @many, and work around the lack of native many-to-many support.