Rules for coding agents
The condensed set of rules and anti-patterns for writing Rayfin code — read this before generating entities, queries, permissions, or deployment commands.
This is the short version of everything on this site that changes whether generated Rayfin code works. If you are an agent and you read only one page, read this one.
Full detail lives in Known limitations, Modeling entities, and Permissions.
Platform
- Rayfin uses TC39 Stage 3 decorators. Never enable
experimentalDecoratorsoremitDecoratorMetadata. - Include
ESNext.Decoratorsin the tsconfiglibarray. - Rayfin has one deployment target: a managed Fabric app (MSSQL only).
- Prefer
npm create @microsoft/rayfin@latestfor new projects — it generates a correct tsconfig and schema boilerplate.
Data modeling
- Define entities with
@entity()inrayfin/data/, and register each one inrayfin/data/schema.tsastype AppSchema = { Todo: Todo }. - Every field needs exactly one type decorator:
@uuid,@text,@int,@decimal,@boolean,@date,@email,@set. - Fields are required by default. For a nullable field use
{ optional: true }and?together. @text()'s length option ismax, notmaxLength—@text({ max: 200 }). Always set it on MSSQL; see Known limitations.- Use
@one(() => Target)with a lazy arrow function for relationships. Rayfin auto-generates the foreign key column, named{property}_id— see Known limitations. - Use
import, notimport type, for entity classes referenced inside@one()/@many()arrow functions — the decorator needs the runtime class value. - A foreign key column referencing another entity (
{property}_id) must be@uuid()to match the primary key type. An auth-derived field such asuser_idfromclaims.subis@text(), not a foreign key. - Many-to-many is not supported. Use an explicit join entity with two
@one()fields. See Known limitations.
Security
- Every entity needs an explicit permission decorator (
@role,@anonymous,@authenticated). Omitting one silently appliesauthenticated: *— full CRUD for any signed-in user — which is almost never what you want in production. - Add a policy for row-level filtering on user-scoped data:
policy: (claims, item) => claims.sub.eq(item.user_id). - Use
excludein role options to hide sensitive fields, e.g.exclude: ['secret']. - Publishable keys (
pk-*) are safe in client-side code. Never expose service secrets or connection strings. - Keep
allowedRedirectUrisinrayfin.ymlscoped to your app's own origins. - Fabric SSO (Entra ID) is the only supported authentication method. Its popup flow works
from any registered origin, including a local Vite dev server (
http://localhost:5173); only the embedded (iframe) flow requires running inside the Fabric portal itself.
Querying
- The query chain is
.select()→.where()→.orderBy()→.execute(). - Fetch a single record with
client.data.Entity.findById('uuid')— there is nofindByPk. - Filter by foreign key columns using
{property}_id(customer_id), not a dot-path (customer.id). Dot-paths are for.select()only. - Sort directions are lowercase:
'asc'or'desc'. - Use
.first(n).executePaginated()with.after(endCursor)for pagination. Collection queries return a single page otherwise; the default page size is 100. count()does not exist on the fluent client.countis an aggregation operation and, likesum/avg/min/max, accepts numeric fields only — so it counts non-null numeric values, not rows. For a row count, select minimal fields and useresults.length. See Known limitations.- Grouped aggregation cannot be combined with
.select(),.orderBy(),.first()or.after()— Data API Builder rejectsgroupByalongsideitems. Sort the returned array in TypeScript instead.
Auth
- The session change callback is
onSessionChange.onAuthStateChangedoes not exist. See Known limitations. - Session objects are opaque. Gate UI on
isAuthenticatedor the presence of auserproperty — do not introspect the session. - After enabling or disabling auth in
rayfin.yml, restart the backend so the updated endpoints are exposed.
Configuration
- Always declare
services.authandservices.dataexplicitly inrayfin.yml, even asenabled: false. The CLI reads those keys without guarding and does not apply defaults. - If
services.data.enabledistrue,dialectis required —mssqlis the only supported value. See Known limitations.
Connectors
Connectors reach data that already exists in Fabric. They are in private preview and behind a feature flag — read Connectors before writing any connector code.
- The
rayfin connectorcommand group is not registered until the project setsservices.connectors.enabled: trueinrayfin.yml, has a non-emptyconnectors:block, or runs withRAYFIN_FEATURE_FLAGS=connectors. - The
connector addflag is--type, not--connector. The five type literals arefabric-sqlanalytics,fabric-warehouse,fabric-sqldatabase,fabric-semanticmodel, andkusto. There is nofabric-sql. connector addscaffolds files but installs nothing. Run the version-pinnednpm installit prints, verbatim — never drop the version.connector adddoes not emit entity.tsfiles. Generate them yourself fromrayfin/connectors/<name>/metadata.json, following Generating entity files.metadata.jsonis the only source of truth for primary keys and relationships — absent metadata means keyless, not permission to infer a key from column names.- In
rayfin/connectors/<name>/schema.ts, import and re-export entity classes withimport type/export typeonly. A value import ships decorated classes into the browser bundle; the build and deploy both succeed and the deployed page renders blank. GraphQLBackedConnectoris the marker for all three Category A types. Per-type names likeFabricWarehousedo not exist.- Import
ConnectorsRayfinClientfrom@microsoft/rayfin-client/experimental, never the stable@microsoft/rayfin-cliententry. - Category B connectors need the runtime map as the client's second constructor argument —
kusto()injects cluster routing andfabricSemanticModel()decodes the response. Omit it and calls do not work. auth.typeis lowercase,delegatedorapplication.applicationis rejected onfabric-semanticmodelandkusto. See Connector authentication.
Deployment
rayfin upis the canonical command for "deploy this change", "apply this schema change", or "push my entity update". It builds and deploys the static app and applies pending schema migrations in one step.rayfin up db applyis a narrow advanced subcommand that applies schema only. Use it when you explicitly want to skip the static deploy.- After adding or changing an entity, verify with
rayfin up status. A deploy can report success while a newly added entity is not yet readable. --forcepermits destructive changes (drop table, drop column, alter type). Never use it without review.- When a user asks you to "build and deploy" or "make it live", run the workflow —
rayfin login→rayfin up→rayfin up status— rather than printing steps for them to run.
Anti-patterns
| Don't | Do |
|---|---|
Raw fetch() or hand-built GraphQL for data | client.data.<Entity> — typed queries with automatic auth |
| Entities with no permission decorator | An explicit @anonymous(), @authenticated(), or @role() |
@text() with no max on MSSQL | @text({ max: N }) — NVARCHAR(MAX) breaks GraphQL schema generation |
@text() for a foreign key column | @uuid(), to match the referenced primary key |
onAuthStateChange | onSessionChange |
findByPk | findById |
| Guessing at an API surface | Fetch the relevant <route>.md page, or use the MCP server |
@microsoft/rayfin-lib
The shared ApiClient, error classes, and small utilities every other Rayfin SDK package builds on — an internal dependency most builders never import directly.
Known limitations
Current constraints in the Rayfin data client, Data API Builder, relationships, auth, and schema apply — organized by area, each with a workaround.