Rayfin

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 example 1.36.2). Mixed versions produce type errors or two copies of @microsoft/rayfin-data.
  • Read the project's package.json before 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 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, schema boilerplate, and agent files.

Development loop

  • Sign in before anything that touches Fabric: npx rayfin login. Add --encryption-fallback-enabled only on machines with no OS keychain (dev containers, Codespaces), because it stores tokens in plaintext.
  • npm run dev runs rayfin dev. It provisions or reuses the app's backend in Fabric, applies runtime settings and the data schema, writes rayfin/.env and .env.local, then starts the frontend — and Functions, when enabled — on the local machine.
  • rayfin dev is 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 dev after editing rayfin.yml or files in rayfin/data/.
  • rayfin dev never publishes the frontend and does not wire connectors. Use rayfin up to publish, and to test connectors.
  • Leave rayfin dev --provider at its fabric default. The managed backend always runs in Fabric.

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.
  • Never name an entity after a GraphQL built-in type such as Date, String, Int, UUID, Query, or Mutation, or with a __ prefix. The CLI rejects it (for example Entity 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, 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, connection strings, or tokens from ctx.Tokens.
  • Keep allowedRedirectUris in rayfin.yml scoped 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. Use signInWithEntraToken() when code already holds a delegated Entra token, and the embed host when your own portal embeds the app.
  • Set services.staticHosting.assetAccess explicitly: 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 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.
  • 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 an await, or the popup is blocked.
  • After changing services.auth in rayfin.yml, restart rayfin dev or redeploy with rayfin up so the backend picks up the new settings.

Configuration

  • Always declare services.auth and services.data explicitly in rayfin.yml, even as enabled: false.
  • If services.data.enabled is true, dialect is required — mssql is the only supported value. See Known limitations.
  • If services.functions.enabled is true, services.functions.auth.type: application is required.
  • RAYFIN_FEATURE_FLAGS is 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, and AudienceType from @microsoft/fabric-user-data-functions. @microsoft/rayfin-functions is 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 type for entity types inside the functions package — a value import pulls the decorator runtime into the functions bundle.
  • Read secrets as ctx.Secrets.NAME after declaring them with npx rayfin secret set NAME --describe="...". Read resource tokens as ctx.Tokens.<Audience> after declaring the audience in RayfinContext<Schema, AudienceType.X>. ctx.getSecret() and ctx.getToken() are deprecated.
  • AudienceType has five members: Sql, Storage, Fabric, AzureAI, ADO.
  • Deployed functions use ctx.Tokens as 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 no ctx.log.
  • Leave functionsBaseUrl unset 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 connector command group is always available as of 1.36. Do not set RAYFIN_FEATURE_FLAGS=connectors or services.connectors.enabled.
  • The connector add flag is --type, not --connector. The type literals are fabric-sqlanalytics, fabric-warehouse, fabric-sqldatabase, and fabric-semanticmodel. There is no fabric-sql. New kusto connectors cannot be added in 1.36; existing ones keep working.
  • connector add lowercases connector names, scaffolds files, and installs nothing. Run the version-pinned npm install it prints, verbatim — never drop the version.
  • connector add discovers schema only from literal --workspace-id and --item-id values; ${VAR} placeholders are rejected.
  • connector add does not emit entity .ts files. Generate them 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.
  • Never hand-edit a file with a // @generated — do not edit. banner, or metadata.json. Re-add the connector to regenerate them.
  • GraphQLBackedConnector is the marker for all three Category A types. Per-type names like FabricWarehouse do not exist.
  • Import ConnectorsRayfinClient from @microsoft/rayfin-client, and Source / AutoGenerated from @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 throws SELECTION_REQUIRED.
  • auth.type is lowercase, delegated or application. connector add writes application for the three SQL types; set delegated for per-user access, and set it again after re-adding. fabric-semanticmodel accepts only delegated. See Connector authentication.
  • Pass DAX to rayfin connector invoke with --file pointing at a JSON payload such as {"query":"EVALUATE ..."}, not a raw .dax file and not inline --input.
  • Prove a connector returns rows with rayfin connector invoke or rayfin connector inspect before you build UI on it.

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, deploys Functions, and applies pending schema migrations in one step.
  • rayfin up db apply, rayfin up staticapp deploy, and rayfin up functions deploy are narrow subcommands. Use them only when you explicitly want to update one part.
  • In automation, pass -y and 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.
  • --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 login → rayfin up → rayfin 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
An entity named Date, Query, or another GraphQL built-inA distinct name such as EventDate
onAuthStateChangeonSessionChange
findByPkfindById
rayfin up --exclude-services staticHosting plus a separate Vite server as the dev loopnpm run dev, which runs rayfin dev
ctx.getToken(AudienceType.Sql) / ctx.getSecret('KEY')ctx.Tokens.Sql / ctx.Secrets.KEY
RAYFIN_FEATURE_FLAGS=functions,connectorsNothing — both are generally available in 1.36
Mixing Rayfin package versionsOne exact version for every @microsoft/rayfin-* package
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