Rayfin

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.

Something wrong on this page?Report an issueEdit this page

On this page