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
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
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
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
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.
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.
Deprecations
APIs, settings, and flags deprecated or removed in recent Rayfin releases with their replacements, and how to silence Rayfin's deprecation warnings.