Rayfin

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.

This page lists the current constraints Rayfin builders run into most often, grouped by area. Each entry names the limitation and the workaround to use instead — if you're an agent generating Rayfin code, check the relevant section here before you write entities, queries, permission decorators, functions, or connectors.

Service availability

Functions are not available in every region or tenant

Functions are generally available as of Rayfin 1.36 — no feature flag is needed — but the service is not available in every Fabric region or tenant.

Workaround: deploy a minimal app with services.functions.enabled: true and confirm npx rayfin up succeeds in your target workspace before you design around it. Keep data access in client.data.<Entity> where a function is not strictly required.

Storage is experimental

Storage is experimental. It is enabled only with RAYFIN_FEATURE_FLAGS=storage or services.storage.enabled: true, it is not available in every Fabric region or tenant, and @microsoft/rayfin-storage may change substantially between releases.

Workaround: confirm a storage-enabled deploy succeeds in your target workspace before you depend on it, and pin every Rayfin package to one exact version.

Deep linking is rolling out per tenant

Deep linking depends on the Fabric portal, and it is not available in every tenant yet.

Workaround: call isSupported() before you show a share button or rely on the URL carrying state, and keep the app fully usable when it resolves to undefined.

Functions

Function connections run only as the app identity

Changed in 1.36

A deployed function's ctx.Tokens.<Audience> token belongs to the app identity — for current Fabric apps, the owner of the Fabric app item — not to the signed-in user. services.functions.auth.type accepts only application, so a function cannot call an external resource as the caller.

Workaround: grant the app identity access to each resource the function calls. When each user must see only their own data at the source, query it through a SQL or semantic model connector with auth.type: delegated instead of a function. Data your app owns stays caller-scoped through ctx.getDataClient(). See Delegated and application access.

Local function connections use your own identity

When rayfin dev runs Functions locally, ctx.Tokens holds tokens for the account you signed in with, not for the app identity. A call that works locally can fail after rayfin up with an authorization error.

Workaround: grant the app identity access before you deploy, and test the deployed function once with npx rayfin up.

Five audiences only

Changed in 1.36

AudienceType has five members: Sql, Storage, Fabric, AzureAI, and ADO. CosmosDB, KeyVault, EventGrid, Kusto, and WorkIQ were removed in 1.36.

Workaround: for a service that accepts key-based access, store the credential with npx rayfin secret set and read it from ctx.Secrets in the function. See Secrets in functions.

Connectors

Connectors do not run under rayfin dev

rayfin dev reads the connectors: block but does not wire it, so connector calls do not work in a development session until the connector is deployed.

Workaround: deploy with npx rayfin up (or npx rayfin up connector apply) and test against the deployed app. Prove the connector returns rows with rayfin connector invoke or rayfin connector inspect before you build UI on it.

New Kusto connectors cannot be added

Changed in 1.36

Rayfin 1.36 holds the kusto (Eventhouse) connector type back. rayfin connector add, types, and search reject it with Connector type "kusto" is not available in this release. A project that already declares a Kusto connector keeps validating and deploying.

Workaround: keep existing Kusto connectors as they are. See KQL databases for operating one on 1.36.

rayfin connector inspect does not support Kusto

connector inspect works for fabric-sqlanalytics, fabric-warehouse, fabric-sqldatabase, and fabric-semanticmodel. A kusto connector fails with Unsupported connector type: kusto.

Workaround: use npx rayfin connector invoke <name> executeQuery instead. That transport posts to the deployed item, so it needs a prior npx rayfin up. See KQL databases.

Install connector packages at the pinned version

An unversioned install of a connector package can resolve to a different release than the rest of your Rayfin packages. The connector then pulls its own @microsoft/rayfin-data, leaving two Rayfin version lines in one app.

Workaround: run the version-pinned npm install that rayfin connector add prints, verbatim, so every @microsoft/rayfin-* package stays on one version.

Decorated entity classes can break a browser bundle

connectorConfig.entities accepts either an entity's class or a string array of its field names. The class form is required to select a relationship — selecting one with the array form throws ENTITIES_REQUIRED_FOR_RELATIONSHIP_SELECT. But a bundler that lowers the decorators on an entity class produces an invalid class expression, and the deployed page fails to parse at runtime.

Workaround: in code the browser loads, list each entity's fields as a string array unless you need relationship selects. If you use the class form, confirm the production build runs before you deploy. See Client setup.

Data client

count() counts numeric values, not rows

The fluent GraphQL client has no count() method on the query chain. count exists only as an aggregation operation, and Data API Builder types every aggregation's field argument as the entity's numeric fields — so count can only target a numeric column, and returns the number of rows where that column is non-null.

Workaround: point count at a non-nullable numeric column when the entity has one. Otherwise select the minimal set of fields you need and use results.length, or page through results with .executePaginated() and sum items.length per page if the total exceeds one page.

Grouped aggregation cannot be combined with row selection

Data API Builder rejects a query that requests groupBy and items together, so .aggregate(...) is mutually exclusive with .select(), .orderBy(), .first(), and .after(). Each combination is a compile-time error backed by a runtime guard.

Workaround: run the aggregation on its own and sort or slice the returned array in TypeScript. One row per group is normally a small result.

Many-to-many relationships are not supported

@one() and @many() model one-to-many and many-to-one relationships only — there is no decorator for many-to-many.

Workaround: create an explicit join entity with two @one() fields, one pointing at each side of the relationship. See Relationship decorators.

Data API Builder (DAB)

Rayfin's data layer generates configuration for Data API Builder, so some limitations come from DAB itself rather than from Rayfin's client or decorators.

Collection queries return only one page unless you paginate

.execute() returns a single page — 100 records by default, up to a maximum page size of 100,000 — with no signal that more records exist. A query that matches more rows than the page size is silently truncated.

Workaround: use .first(n) with .executePaginated() and .after(endCursor) for any list that can exceed one page. See Pagination and Errors and troubleshooting.

Total record counts are unsupported

There is no way to request a total row count independent of a page (DAB discussion #2234, DAB issue #2369) — this is also why PagedResult.totalCount exists on the type but is never populated.

Workaround: don't rely on totalCount. If you need an approximate count, select minimal fields and iterate pages with .executePaginated(), summing items.length.

Backward pagination (before) is unsupported

DAB only supports forward pagination — first and after (DAB issue #2238). There is no before / last equivalent on the fluent client.

Workaround: design paged UI around forward-only navigation (a "next page" cursor kept in state), rather than jumping backward by cursor.

Nested queries beyond two levels aren't supported

.select() supports one level of relationship dot-path ('notebook.name') but not two ('notebook.owner.email') — the query builder's field-selection logic only splits on the first ..

Workaround: issue a second query for the deeper relationship using the first query's result as the filter, or add a direct field/relationship on the entity that needs the deeper data.

Relationships

Foreign key columns are auto-generated

Defining @one() or @many() already creates the underlying foreign key column — you don't need (and normally shouldn't) declare it yourself.

Workaround: only declare a foreign key field explicitly when your application code needs to read or set the raw ID value directly.

Foreign keys must use the {property}_id naming convention

When you do declare a foreign key field, it must be named {property}_id to match the relationship. Custom key names (foreignKey, targetKey) are not supported on relationship decorators.

Workaround: name the field after the relationship property plus _id — for a @one(() => Category) category field, the FK column is category_id.

@one() / @many() only accept { optional?, unique? }

Relationship decorators don't accept the full field option set (no default, max, min, etc.) — only optional (nullable relationship) and unique (make a @one() relationship one-to-one).

Workaround: if you need constraints beyond nullability and uniqueness, apply them to the plain field decorator on a manually-declared foreign key column instead of the relationship decorator.

Auth

The callback is onSessionChange, not onAuthStateChange

onAuthStateChange does not exist on the Rayfin auth client, despite being a common name in other auth SDKs.

Workaround: subscribe with auth.onSessionChange(callback), which returns an unsubscribe function. See @microsoft/rayfin-auth.

Session objects are opaque

OpaqueSession intentionally doesn't expose tokens or internal claim structure.

Workaround: gate UI logic on session.isAuthenticated or the presence of session.user — don't try to read or decode internal session fields.

Auth config changes require a backend restart

Enabling or disabling auth (or changing its settings) in rayfin.yml doesn't take effect on a running backend.

Workaround: restart npm run dev (which runs rayfin dev) during development, or redeploy with npx rayfin up, after any services.auth change in rayfin.yml.

Data

@entity() doesn't accept composite field constraints

There's no class-level option for constraints that span multiple fields (e.g., "field A must be less than field B").

Workaround: define constraints on individual fields via their own field decorator options (min, max, regex, and so on); enforce cross-field constraints in application code or with toStandardSchema before calling create / update.

Prefer @anonymous() / @authenticated() over @role('anonymous' | 'authenticated', ...)

The shorthand decorators are clearer at the call site than the general-purpose @role() form.

Workaround: use @authenticated(actions, options) for authenticated-only access, and @anonymous(actions, options) for public access. Both are exported from the package root. Anonymous access on a deployed Fabric app additionally requires a tenant administrator to enable anonymous data access for the tenant. See Permission decorators.

Database and schema apply

Run rayfin up before rayfin up db apply

rayfin up db apply applies schema to an app that already exists in Fabric.

Workaround: run npx rayfin up once to create the app, then use the db apply subcommand for schema-only changes.

Entity names can't reuse GraphQL built-in type names

Changed in 1.36

An entity name becomes a GraphQL type name. Names such as Date, DateTime, String, Int, Decimal, UUID, Query, Mutation, and Subscription collide with types the generated schema already defines, as does any name that starts with __. Before 1.36 the deploy failed with an opaque schema-build error. Since 1.36 the CLI rejects the name up front — for example Entity name 'Date' is a built-in GraphQL scalar type reserved by the platform, or Reserved GraphQL type names detected in connector entities: for connectors. This applies to your own entities and to connector entities.

Workaround: rename the class — for example EventDate instead of Date — or keep the class name and pass a different entity name with @entity('EventDate'). For a connector entity, keep Source({ table: '...' }) pointing at the original table.

dialect is required when the Data module is enabled

Setting services.data.enabled: true in rayfin.yml without a dialect fails at deploy time with Dialect is required when Data module is enabled.

Workaround: always pair data.enabled: true with dialect: mssql — the only dialect Fabric apps support. See Errors and troubleshooting.

@text() without max breaks GraphQL schema generation on MSSQL

Omitting max on a @text() field produces an NVARCHAR(MAX) column on MSSQL. Rayfin's metadata provider can fail to build a GraphQL schema from that column, which surfaces as an "Internal server error" at runtime — after rayfin up itself reported success.

Workaround: always set @text({ max: N }) with an explicit length on every string field targeting MSSQL. See Errors and troubleshooting and the @text() field options on the rayfin-core reference.

Something wrong on this page?Report an issueEdit this page

On this page

Service availabilityFunctions are not available in every region or tenantStorage is experimentalDeep linking is rolling out per tenantFunctionsFunction connections run only as the app identityLocal function connections use your own identityFive audiences onlyConnectorsConnectors do not run under rayfin devNew Kusto connectors cannot be addedrayfin connector inspect does not support KustoInstall connector packages at the pinned versionDecorated entity classes can break a browser bundleData clientcount() counts numeric values, not rowsGrouped aggregation cannot be combined with row selectionMany-to-many relationships are not supportedData API Builder (DAB)Collection queries return only one page unless you paginateTotal record counts are unsupportedBackward pagination (before) is unsupportedNested queries beyond two levels aren't supportedRelationshipsForeign key columns are auto-generatedForeign keys must use the {property}_id naming convention@one() / @many() only accept { optional?, unique? }AuthThe callback is onSessionChange, not onAuthStateChangeSession objects are opaqueAuth config changes require a backend restartData@entity() doesn't accept composite field constraintsPrefer @anonymous() / @authenticated() over @role('anonymous' | 'authenticated', ...)Database and schema applyRun rayfin up before rayfin up db applyEntity names can't reuse GraphQL built-in type namesdialect is required when the Data module is enabled@text() without max breaks GraphQL schema generation on MSSQL