Rules for coding agents
The condensed set of rules and anti-patterns for writing Rayfin code — read this before generating entities, queries, permissions, functions, connectors, 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, Permissions, and the Changelog.
Versions
- Every
@microsoft/rayfin-*package — SDK, connectors, CLI — shares one version number. Install them all at the same exact version (for example1.36.2). Mixed versions produce type errors or two copies of@microsoft/rayfin-data. - Read the project's
package.jsonbefore you use an API. Anything tagged "New in 1.36" on this site does not exist in older packages. - Run the CLI from the project with
npx rayfin, so it matches the installed packages.
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, schema boilerplate, and agent files.
Development loop
- Sign in before anything that touches Fabric:
npx rayfin login. Add--encryption-fallback-enabledonly on machines with no OS keychain (dev containers, Codespaces), because it stores tokens in plaintext. npm run devrunsrayfin dev. It provisions or reuses the app's backend in Fabric, applies runtime settings and the data schema, writesrayfin/.envand.env.local, then starts the frontend — and Functions, when enabled — on the local machine.rayfin devis long-running. Start it as a background process and stop it with Ctrl+C.- The schema is applied when the session starts. Restart
npm run devafter editingrayfin.ymlor files inrayfin/data/. rayfin devnever publishes the frontend and does not wire connectors. Userayfin upto publish, and to test connectors.- Leave
rayfin dev --providerat itsfabricdefault. The managed backend always runs in Fabric.
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.- Never name an entity after a GraphQL built-in type such as
Date,String,Int,UUID,Query, orMutation, or with a__prefix. The CLI rejects it (for exampleEntity name 'Date' is a built-in GraphQL scalar type reserved by the platform). Rename the class, or pass a different name with@entity('EventDate'). - 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, connection strings, or tokens fromctx.Tokens. - Keep
allowedRedirectUrisinrayfin.ymlscoped to your app's own origins. - Microsoft Entra ID is the only identity provider. Use
Fabric SSO in browser apps — its popup flow works from any
registered origin, including
http://localhost:5173; only the embedded (iframe) flow requires the Fabric portal. UsesignInWithEntraToken()when code already holds a delegated Entra token, and the embed host when your own portal embeds the app. - Set
services.staticHosting.assetAccessexplicitly:protected(requires Fabric sign-in to load the app) unless the app must be public.
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. - Await
initEmbeddedAuth()at startup before rendering authenticated content, even when a stored session already exists — in an embedded app it may belong to a different user. - Call
ensureSignedInWithFabric()directly from a click handler, never on page load or after anawait, or the popup is blocked. - After changing
services.authinrayfin.yml, restartrayfin devor redeploy withrayfin upso the backend picks up the new settings.
Configuration
- Always declare
services.authandservices.dataexplicitly inrayfin.yml, even asenabled: false. - If
services.data.enabledistrue,dialectis required —mssqlis the only supported value. See Known limitations. - If
services.functions.enabledistrue,services.functions.auth.type: applicationis required. RAYFIN_FEATURE_FLAGSis not needed for Functions or connectors. Of the documented features, only experimental storage still uses it (storage).
Functions
Read Functions before writing function code.
- Import
UserDataFunctions,RayfinContext, andAudienceTypefrom@microsoft/fabric-user-data-functions.@microsoft/rayfin-functionsis the client invocation SDK only. - Register every function with
udf.func(name, handler, []). Bare exported functions are not discovered. - Never hand-edit
rayfin/functions/src/types.ts; typegen regenerates it. - Use
import typefor entity types inside the functions package — a value import pulls the decorator runtime into the functions bundle. - Read secrets as
ctx.Secrets.NAMEafter declaring them withnpx rayfin secret set NAME --describe="...". Read resource tokens asctx.Tokens.<Audience>after declaring the audience inRayfinContext<Schema, AudienceType.X>.ctx.getSecret()andctx.getToken()are deprecated. AudienceTypehas five members:Sql,Storage,Fabric,AzureAI,ADO.- Deployed functions use
ctx.Tokensas the app identity, not the signed-in user. Grant the app identity access to each resource.ctx.getDataClient()stays scoped to the caller. See Delegated and application access. - Install resource SDKs in the functions package's
package.json, not the project root. - Log with
console.log/console.error; there is noctx.log. - Leave
functionsBaseUrlunset in production code so invocations reach the deployed backend.
Connectors
Connectors reach data that already exists in Fabric. Read Connectors before writing connector code.
- The
rayfin connectorcommand group is always available as of 1.36. Do not setRAYFIN_FEATURE_FLAGS=connectorsorservices.connectors.enabled. - The
connector addflag is--type, not--connector. The type literals arefabric-sqlanalytics,fabric-warehouse,fabric-sqldatabase, andfabric-semanticmodel. There is nofabric-sql. Newkustoconnectors cannot be added in 1.36; existing ones keep working. connector addlowercases connector names, scaffolds files, and installs nothing. Run the version-pinnednpm installit prints, verbatim — never drop the version.connector adddiscovers schema only from literal--workspace-idand--item-idvalues;${VAR}placeholders are rejected.connector adddoes not emit entity.tsfiles. Generate them 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.- Never hand-edit a file with a
// @generated — do not edit.banner, ormetadata.json. Re-add the connector to regenerate them. GraphQLBackedConnectoris the marker for all three Category A types. Per-type names likeFabricWarehousedo not exist.- Import
ConnectorsRayfinClientfrom@microsoft/rayfin-client, andSource/AutoGeneratedfrom@microsoft/rayfin-connectors. - Category B connectors need the runtime map as the client's second constructor argument —
fabricSemanticModel()decodes the response. Omit it and calls do not work. - A query chain with
.where(),.orderBy(), or.first()needs an explicit.select([...]), or it throwsSELECTION_REQUIRED. auth.typeis lowercase,delegatedorapplication.connector addwritesapplicationfor the three SQL types; setdelegatedfor per-user access, and set it again after re-adding.fabric-semanticmodelaccepts onlydelegated. See Connector authentication.- Pass DAX to
rayfin connector invokewith--filepointing at a JSON payload such as{"query":"EVALUATE ..."}, not a raw.daxfile and not inline--input. - Prove a connector returns rows with
rayfin connector invokeorrayfin connector inspectbefore you build UI on it.
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, deploys Functions, and applies pending schema migrations in one step.rayfin up db apply,rayfin up staticapp deploy, andrayfin up functions deployare narrow subcommands. Use them only when you explicitly want to update one part.- In automation, pass
-yand an explicit workspace (--workspace-id) so the deploy never depends on an interactive prompt. - 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 |
An entity named Date, Query, or another GraphQL built-in | A distinct name such as EventDate |
onAuthStateChange | onSessionChange |
findByPk | findById |
rayfin up --exclude-services staticHosting plus a separate Vite server as the dev loop | npm run dev, which runs rayfin dev |
ctx.getToken(AudienceType.Sql) / ctx.getSecret('KEY') | ctx.Tokens.Sql / ctx.Secrets.KEY |
RAYFIN_FEATURE_FLAGS=functions,connectors | Nothing — both are generally available in 1.36 |
| Mixing Rayfin package versions | One exact version for every @microsoft/rayfin-* package |
| 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 Rayfin — service availability, functions, connectors, the data client, Data API Builder, relationships, auth, and schema apply — each with a workaround.