Known limitations
Current constraints in the Rayfin data client, Data API Builder, relationships, auth, and schema apply — organized by area, 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, or permission decorators.
Service availability
Functions and storage are experimental
services.functions and services.storage are experimental services. They are not
available in every Fabric region or tenant, and the @microsoft/rayfin-functions and
@microsoft/rayfin-storage packages may change substantially between releases.
Workaround: deploy a minimal app with the service enabled 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.
Connectors are in private preview
Connectors are behind a feature flag: rayfin connector is not
registered until the project sets services.connectors.enabled: true, has a non-empty
connectors: block, or runs with RAYFIN_FEATURE_FLAGS=connectors. ConnectorsRayfinClient
ships from the @microsoft/rayfin-client/experimental subpath, and the connector packages
may change between releases.
Connector packages version in lockstep with the CLI, but their npm latest and preview
tags lag the published release. An unversioned install resolves to an older connector that
hard-pins 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, and confirm a connector deploys in your own workspace before designing around it.
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.
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 the backend (npx rayfin up) after any services.auth change in
rayfin.yml so the updated endpoints are exposed.
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 expects the backend services it's applying schema to already be
running and healthy.
Workaround: run npx rayfin up first and wait for services to report healthy, then
run the db apply subcommand.
unsupported UUID errors
This usually means another service is already running on the default port, backed by a different database dialect than your current project expects.
Workaround: stop the conflicting service (or free the port) and retry. See Errors and troubleshooting.
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, or deployment commands.
Deprecation warnings
How to silence Rayfin's deprecation warnings in application code, in Node.js scripts, and in browser apps.