Rayfin

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 experimentalDecorators or emitDecoratorMetadata.
  • Include ESNext.Decorators in the tsconfig lib array.
  • Rayfin has one deployment target: a managed Fabric app (MSSQL only).
  • Prefer npm create @microsoft/rayfin@latest for new projects — it generates a correct tsconfig and schema boilerplate.

Data modeling

  • Define entities with @entity() in rayfin/data/, and register each one in rayfin/data/schema.ts as type 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 is max, not maxLength@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, not import 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 as user_id from claims.sub is @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 applies authenticated: * — 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 exclude in 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 allowedRedirectUris in rayfin.yml scoped 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 no findByPk.
  • 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. count is an aggregation operation and, like sum/avg/min/max, accepts numeric fields only — so it counts non-null numeric values, not rows. For a row count, select minimal fields and use results.length. See Known limitations.
  • Grouped aggregation cannot be combined with .select(), .orderBy(), .first() or .after() — Data API Builder rejects groupBy alongside items. Sort the returned array in TypeScript instead.

Auth

  • The session change callback is onSessionChange. onAuthStateChange does not exist. See Known limitations.
  • Session objects are opaque. Gate UI on isAuthenticated or the presence of a user property — 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.auth and services.data explicitly in rayfin.yml, even as enabled: false. The CLI reads those keys without guarding and does not apply defaults.
  • If services.data.enabled is true, dialect is required — mssql is 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 connector command group is not registered until the project sets services.connectors.enabled: true in rayfin.yml, has a non-empty connectors: block, or runs with RAYFIN_FEATURE_FLAGS=connectors.
  • The connector add flag is --type, not --connector. The five type literals are fabric-sqlanalytics, fabric-warehouse, fabric-sqldatabase, fabric-semanticmodel, and kusto. There is no fabric-sql.
  • connector add scaffolds files but installs nothing. Run the version-pinned npm install it prints, verbatim — never drop the version.
  • connector add does not emit entity .ts files. Generate them yourself from rayfin/connectors/<name>/metadata.json, following Generating entity files. metadata.json is 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 with import type / export type only. A value import ships decorated classes into the browser bundle; the build and deploy both succeed and the deployed page renders blank.
  • GraphQLBackedConnector is the marker for all three Category A types. Per-type names like FabricWarehouse do not exist.
  • Import ConnectorsRayfinClient from @microsoft/rayfin-client/experimental, never the stable @microsoft/rayfin-client entry.
  • Category B connectors need the runtime map as the client's second constructor argument — kusto() injects cluster routing and fabricSemanticModel() decodes the response. Omit it and calls do not work.
  • auth.type is lowercase, delegated or application. application is rejected on fabric-semanticmodel and kusto. See Connector authentication.

Deployment

  • rayfin up is 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 apply is 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.
  • --force permits 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 loginrayfin uprayfin up status — rather than printing steps for them to run.

Anti-patterns

Don'tDo
Raw fetch() or hand-built GraphQL for dataclient.data.<Entity> — typed queries with automatic auth
Entities with no permission decoratorAn 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
onAuthStateChangeonSessionChange
findByPkfindById
Guessing at an API surfaceFetch the relevant <route>.md page, or use the MCP server
Something wrong on this page?Report an issueEdit this page

On this page