Rayfin

Writing a function

The rayfin/functions project layout, registering functions with udf.func in function_app.ts, typed data access, and the auto-generated types.ts schema.

This page covers the rayfin/functions/ project itself — its layout, how to register a function, how to read and write your entities from inside one, and how its generated types work.

Warning

Functions are experimental and are not available in every Fabric region or tenant. See Functions before you depend on them.

Project layout

After npx rayfin functions init, the functions project lives at rayfin/functions/:

rayfin/
  data/                 ← entity classes (shared via TS project references)
  functions/
    src/
      function_app.ts   ← register functions here
      types.ts          ← auto-generated by typegen — never hand-edit
    tsconfig.json        ← references: [{ "path": ".." }]
    package.json
    host.json
    local.settings.json

rayfin/functions/tsconfig.json uses composite: true with a project reference to rayfin/, so functions can import type from your data entities without duplicating type definitions.

Registering a function

Every function is registered with udf.func(name, handler, []) from @microsoft/fabric-user-data-functions:

rayfin/functions/src/function_app.ts
import { UserDataFunctions } from '@microsoft/fabric-user-data-functions';

const udf = new UserDataFunctions();

/**
 * A simple greeting function.
 *
 * The input/output types for this function are autogenerated in ./types.ts so that
 * RayfinClient can invoke it with full type-safety from your frontend app.
 */
udf.func('helloWorld', (firstName: string, lastName: string): string => {
  console.log(`helloWorld invoked for ${firstName} ${lastName}`);
  return `Hello ${firstName} ${lastName}!`;
}, []);
  • The first argument is the function name — it must match the key typegen produces in AppFunctionsSchema.
  • The second argument is the handler: typed parameters plus a return type, both extracted by typegen into types.ts.
  • The third argument is reserved for future middleware — pass an empty array, [].
  • Always register functions with udf.func(...). Don't export bare functions instead — typegen only sees registrations made through udf.func.

Typed data access with RayfinContext

Add a RayfinContext parameter to reach the same data client client.data.<Entity> uses on the frontend — .select().where().execute(), the same chain throughout:

rayfin/functions/src/function_app.ts
import { UserDataFunctions, type RayfinContext } from '@microsoft/fabric-user-data-functions';

type AppSchema = {
  Entry: { id: string; message: string; createdAt: string };
};

const udf = new UserDataFunctions();

udf.func('getEntries', async (
  ctx: RayfinContext<AppSchema>
): Promise<{ id: string; message: string }[]> => {
  console.log('getEntries invoked');
  const data = ctx.getDataClient();
  return data.Entry.select(['id', 'message', 'createdAt']).execute();
}, []);

udf.func('addEntry', async (
  message: string,
  ctx: RayfinContext<AppSchema>
): Promise<void> => {
  console.log('addEntry invoked');
  const data = ctx.getDataClient();
  await data.Entry.create({ message });
}, []);

Pass your AppSchema type — the same schema type you already pass to RayfinClient<AppSchema> on the frontend — as the generic parameter, so getDataClient() only allows valid entity names and fields. A bare RayfinContext (no generic) still works, but getDataClient() returns untyped (Record<string, any>) access instead.

RayfinContext is a runtime-injected parameter, so typegen automatically strips it from the generated input type — addEntry's generated input is { message: string }, not { message: string; ctx: RayfinContext }.

RayfinContext API:

MemberDescription
ctx.getDataClient()Returns the entity data client — typed when using RayfinContext<AppSchema>.
ctx.baseUrlThe Rayfin endpoint URL (readonly).
ctx.accessTokenThe auth token for the current request (readonly).
ctx.publishableKeyThe Rayfin publishable key (readonly).
ctx.getSecret(name)Returns a secret — see below.
ctx.getToken(audienceType)Returns a delegated token for an external resource — see Connections.
  • Import RayfinContext from @microsoft/fabric-user-data-functionsnot from @microsoft/rayfin-functions.
  • Use console.log(...) / console.error(...) for logging, not ctx.log.
  • Import data entity classes with import type, not a runtime import — a runtime import of @microsoft/rayfin-core decorators would pull unnecessary dependencies into the functions bundle. Use the .js extension on relative imports, matching ESM resolution:
import type { TodoItem } from '../../data/TodoItem.js';

This works because rayfin/functions/tsconfig.json has "references": [{ "path": ".." }] pointing at rayfin/tsconfig.json.

Function secrets

Use ctx.getSecret(name) when a function needs a secret value. The runtime checks host-provided invocation secrets first, then falls back to process.env[name]:

rayfin/functions/src/function_app.ts
udf.func('readApiKey', async (ctx: RayfinContext): Promise<string> => {
  const apiKey = ctx.getSecret('THIRD_PARTY_API_KEY');
  if (!apiKey) {
    throw new Error('Missing THIRD_PARTY_API_KEY secret.');
  }
  return apiKey;
}, []);

Set project secrets with rayfin secret set <NAME>.

Generated types

The CLI parses every udf.func() call under rayfin/functions/src/ and generates types.ts:

rayfin/functions/src/types.ts
export type AppFunctionsSchema = {
  helloWorld: {
    input: { firstName: string; lastName: string };
    output: string;
  };
  getEntries: {
    input: void; // RayfinContext was stripped
    output: Entry[];
  };
};
  • rayfin functions init runs typegen once after scaffolding, to seed types.ts.
  • Never hand-edit types.ts — it's regenerated from your udf.func() calls and any manual changes are overwritten.
  • Promise<T> return types are unwrapped to T in the generated schema.

See Calling functions from your app for importing AppFunctionsSchema on the frontend.

PromptWrite a Rayfin function with typed data access
In my Rayfin project, add a new function to rayfin/functions/src/function_app.ts (running `npx rayfin functions init` first if that directory doesn't exist yet). Register it with udf.func('<name>', handler, []) and give the handler a RayfinContext<AppSchema> parameter so ctx.getDataClient() is fully typed against my entities — use the same AppSchema type my frontend already passes to RayfinClient<AppSchema>. Import RayfinContext from @microsoft/fabric-user-data-functions, not @microsoft/rayfin-functions, and use import type for any data entity classes. After adding it, confirm it appears in the regenerated rayfin/functions/src/types.ts — never hand-edit that file — then deploy with `npx rayfin up` (or `npx rayfin up functions deploy`) and tell me how to call it from the frontend.
Something wrong on this page?Report an issueEdit this page

On this page