@microsoft/rayfin-guide 1.36.0-alpha.1593 → 1.36.0-alpha.1601

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,89 @@
1
+ ---
2
+ sidebar_position: 5
3
+ ---
4
+
5
+ # Connecting to external resources
6
+
7
+ Functions can call external Azure and Fabric resources **as the calling user** using delegated authentication.
8
+ You declare a connection on the function, and at invocation time the runtime exchanges the user's identity for a resource-scoped on-behalf-of (OBO) token.
9
+
10
+ ## The pattern
11
+
12
+ Declare an audience-scoped connection in the third argument of `udf.func()`, then read its token with `ctx.getToken()`:
13
+
14
+ ```ts
15
+ import {
16
+ UserDataFunctions,
17
+ AudienceType,
18
+ type RayfinContext,
19
+ } from "@microsoft/fabric-user-data-functions";
20
+
21
+ const udf = new UserDataFunctions();
22
+
23
+ udf.func(
24
+ "myFunction",
25
+ async (ctx: RayfinContext): Promise<string> => {
26
+ const token = ctx.getToken(AudienceType.KeyVault);
27
+ // Use `token` with the resource's SDK or REST API.
28
+ return "ok";
29
+ },
30
+ [udf.connection({ audienceType: AudienceType.KeyVault })],
31
+ );
32
+ ```
33
+
34
+ - `udf.connection({ audienceType })` declares the delegated connection.
35
+ - `ctx.getToken(audienceType)` returns the OBO access token as a `string`. It throws if no connection was declared for that audience.
36
+ - The connection declaration is what makes the token available — declaring the audience and calling `getToken` with the same audience always go together.
37
+
38
+ ## Wrapping the token for Azure SDK clients
39
+
40
+ Many Azure SDK clients expect a `TokenCredential` rather than a raw token string.
41
+ Define a small adapter that returns the token from the context:
42
+
43
+ ```ts
44
+ import type { TokenCredential, AccessToken } from "@azure/identity";
45
+
46
+ class ContextTokenCredential implements TokenCredential {
47
+ constructor(private readonly token: string) {}
48
+ async getToken(): Promise<AccessToken> {
49
+ return { token: this.token, expiresOnTimestamp: Date.now() + 3600_000 };
50
+ }
51
+ }
52
+ ```
53
+
54
+ Pass `new ContextTokenCredential(ctx.getToken(AudienceType.X))` wherever an Azure SDK client asks for a credential.
55
+ The per-resource guides below use this helper.
56
+
57
+ ## Choose a recipe
58
+
59
+ Find what you want to connect to. Each row links to a task-oriented walkthrough; the `AudienceType` is the value you pass to `udf.connection()` and `ctx.getToken()`. A single audience can back more than one use case (for example `Storage` covers both Fabric OneLake and Azure Blob).
60
+
61
+ | I want to connect to… | `AudienceType` | Recipe |
62
+ | ------------------------------------------------ | -------------- | -------------------------------------------------------------------- |
63
+ | Fabric Lakehouse / Warehouse / SQL DB / Mirrored | `Sql` | [Add a Fabric resource](./add-fabric-resource.md#sql-databases) |
64
+ | Azure SQL Database | `Sql` | [Add a Fabric resource](./add-fabric-resource.md#sql-databases) |
65
+ | Fabric OneLake files | `Storage` | [Add a Fabric resource](./add-fabric-resource.md#onelake-files) |
66
+ | Azure Blob / Table / Queue | `Storage` | [Add an Azure resource](./add-azure-resource.md#blob-storage) |
67
+ | Fabric Eventhouse (KQL) | `Kusto` | [Add a Fabric resource](./add-fabric-resource.md#eventhouse-and-kql) |
68
+ | Azure Data Explorer | `Kusto` | [Add a Fabric resource](./add-fabric-resource.md#eventhouse-and-kql) |
69
+ | Microsoft Fabric REST API | `Fabric` | [Add a Fabric resource](./add-fabric-resource.md#fabric-rest-api) |
70
+ | Azure Key Vault | `KeyVault` | [Add an Azure resource](./add-azure-resource.md#key-vault) |
71
+ | Azure Cosmos DB | `CosmosDB` | [Add an Azure resource](./add-azure-resource.md#cosmos-db) |
72
+ | Azure Event Grid | `EventGrid` | [Add an Azure resource](./add-azure-resource.md#event-grid) |
73
+ | Azure AI Foundry | `AzureAI` | [Add Azure AI Foundry](./add-foundry.md) |
74
+ | Azure DevOps | `ADO` | [Add Azure DevOps](./add-ado.md) |
75
+ | WorkIQ | `WorkIQ` | [Add WorkIQ](./add-work-iq.md) |
76
+
77
+ To look up a Fabric item's connection coordinates (SQL endpoint, OneLake path, Kusto query URI), see [Get Fabric info](./get-fabric-info.md).
78
+
79
+ ## Rules
80
+
81
+ - **Use real endpoint URLs** — don't hardcode a guess or read them from `process.env`. For Fabric items, [Get Fabric info](./get-fabric-info.md) shows how to look them up.
82
+ - Declare every connection in the third argument to `udf.func()`.
83
+ - Use `ctx.getToken(AudienceType.X)` — never acquire tokens manually.
84
+ - Install resource SDK packages in `rayfin/functions/package.json`, not the project root.
85
+
86
+ ## Local development
87
+
88
+ When you run `npx rayfin dev functions apply`, the local host acquires delegated tokens interactively for debugging, so `ctx.getToken()` works against your declared audiences without a deployment.
89
+ See [`npx rayfin dev functions apply`](../../cli/functions/dev-apply.md).
@@ -0,0 +1,91 @@
1
+ ---
2
+ sidebar_position: 35
3
+ ---
4
+
5
+ # Functions
6
+
7
+ Rayfin functions are server-side user-defined functions (UDFs) that run in the Fabric runtime and are invocable from your frontend through `RayfinClient`.
8
+ Write trusted backend logic once, get a type-safe client call on the frontend.
9
+
10
+ ## When to use functions
11
+
12
+ Reach for a function whenever logic should run on the backend rather than in the browser:
13
+
14
+ - **Sensitive operations** — code that touches secrets, API keys, or privileged data access.
15
+ - **Business logic that must not be tampered with** — anything you would not want a user to inspect or modify in client code.
16
+ - **Server-side validation** — enforce rules that a malicious client could otherwise bypass.
17
+ - **Aggregation and transformation** — shape or combine data before returning it to the client.
18
+
19
+ If a frontend feature needs trusted server-side behavior, implement it as a function.
20
+
21
+ ## Getting started
22
+
23
+ Scaffold the functions project with the CLI:
24
+
25
+ ```bash
26
+ npx rayfin functions init
27
+ ```
28
+
29
+ This creates `rayfin/functions/`, installs dependencies, and generates the initial `types.ts`.
30
+ See [`npx rayfin functions init`](../cli/functions/init.md) for the full command reference.
31
+
32
+ ## Project structure
33
+
34
+ After `npx rayfin functions init`, the functions project lives at `rayfin/functions/`:
35
+
36
+ ```text
37
+ rayfin/
38
+ ├── data/ ← entity classes (shared via TS project references)
39
+ └── functions/
40
+ ├── src/
41
+ │ ├── function_app.ts ← register your functions here
42
+ │ └── types.ts ← auto-generated schema (do not edit)
43
+ ├── package.json
44
+ ├── tsconfig.json ← references: [{ "path": ".." }]
45
+ ├── host.json
46
+ └── local.settings.json ← local-only settings (git-ignored)
47
+ ```
48
+
49
+ `tsconfig.json` uses `composite: true` with a project reference to `rayfin/`, so functions can `import type` from your data entities without duplicating definitions.
50
+
51
+ ## Your first function
52
+
53
+ The scaffold seeds `src/function_app.ts` with a simple example:
54
+
55
+ ```ts
56
+ import { UserDataFunctions } from "@microsoft/fabric-user-data-functions";
57
+
58
+ const udf = new UserDataFunctions();
59
+
60
+ udf.func(
61
+ "helloWorld",
62
+ (firstName: string, lastName: string): string => {
63
+ console.log(`helloWorld invoked for ${firstName} ${lastName}`);
64
+ return `Hello ${firstName} ${lastName}!`;
65
+ },
66
+ [],
67
+ );
68
+ ```
69
+
70
+ The handler's parameter and return types are read by typegen and written into `types.ts` so the frontend can call `client.functions.helloWorld.invoke({ firstName, lastName })` with full type safety.
71
+
72
+ ## The development loop
73
+
74
+ For local development, prefer `npx rayfin dev`; it starts the frontend and functions together automatically.
75
+ To iterate on functions **without** starting the frontend static app, run only the function host:
76
+
77
+ ```bash
78
+ npx rayfin dev functions apply
79
+ ```
80
+
81
+ This starts a local function host, keeps a typegen watcher running, and publishes its URL for local frontend routing.
82
+ Bundled Vite apps use the same-origin `/.rayfin/api/<name>` route when you run the frontend separately.
83
+ See [`npx rayfin dev functions apply`](../cli/functions/dev-apply.md) for details.
84
+
85
+ ## Next steps
86
+
87
+ - [Writing functions](./writing-functions.md) — the `udf.func()` API, `RayfinContext`, and data access.
88
+ - [Type generation](./typegen.md) — how `types.ts` is generated and kept in sync.
89
+ - [Connecting to external resources](./connections/index.md) — delegated access to SQL, Storage, Key Vault, and more.
90
+ - [Secrets](./secrets.md) — reading secrets from inside a function.
91
+ - [Invoking functions from the frontend](./invoking-from-frontend.md) — calling functions with `RayfinClient`.
@@ -0,0 +1,94 @@
1
+ ---
2
+ sidebar_position: 4
3
+ ---
4
+
5
+ # Invoking functions from the frontend
6
+
7
+ Functions are called through `RayfinClient`. Pass your generated `AppFunctionsSchema` as the client's second type argument so every call is type-checked.
8
+
9
+ ```ts
10
+ import { RayfinClient } from "@microsoft/rayfin-client";
11
+ import type { AppSchema } from "../rayfin/data/schema";
12
+ import type { AppFunctionsSchema } from "../rayfin/functions/src/types.js";
13
+
14
+ const client = new RayfinClient<AppSchema, AppFunctionsSchema>({
15
+ publishableKey: import.meta.env.VITE_RAYFIN_PUBLISHABLE_KEY,
16
+ });
17
+
18
+ // Type-safe: parameters and return type are checked against the schema.
19
+ const greeting = await client.functions.greet.invoke({
20
+ firstName: "Jane",
21
+ lastName: "Doe",
22
+ });
23
+
24
+ // For functions whose only parameter is RayfinContext, the input is `Record<string, never>`:
25
+ const entries = await client.functions.getEntries.invoke();
26
+ ```
27
+
28
+ The exact import path for `AppFunctionsSchema` depends on where your frontend lives relative to `rayfin/functions/src/types.ts`.
29
+
30
+ ## Invocation behavior
31
+
32
+ - `invoke()` resolves to the function's output directly (typed as the schema's `output`) — not an envelope. By the time it resolves you can use the value without checking for `undefined`.
33
+ - On failure it **throws**: a non-empty `errors` array or a non-success status becomes a `FunctionsError`; network problems surface as a `NetworkError`.
34
+ - The server-side `invocationId` is emitted via `console.debug` for correlation with backend telemetry.
35
+
36
+ ### Per-call options
37
+
38
+ Options always go in the **second** argument slot; the first argument is always the input:
39
+
40
+ ```ts
41
+ // Input function with a per-call timeout:
42
+ await client.functions.longRunning.invoke(params, { timeoutMs: 5_000 });
43
+
44
+ // No-input function — pass `undefined` first, then options:
45
+ await client.functions.ping.invoke(undefined, { timeoutMs: 5_000 });
46
+ ```
47
+
48
+ `timeoutMs` is clamped to the Fabric UDF host ceiling of 250 seconds (`FUNCTIONS_INVOKE_TIMEOUT_MS`), since the host aborts any invocation at that limit regardless of the client value.
49
+
50
+ ## Local development
51
+
52
+ By default, invocations route to your deployed backend.
53
+ Bundled Vite templates use `@microsoft/rayfin-local-dev` to keep local function calls on the frontend origin.
54
+ During `vite serve`, the client calls `/.rayfin/api/<name>` and the adapter forwards the request to the exact Functions host selected by `npx rayfin dev`.
55
+
56
+ For a custom or older Vite project, install the adapter:
57
+
58
+ ```bash
59
+ npm install @microsoft/rayfin-local-dev
60
+ ```
61
+
62
+ Register it in `vite.config.ts`:
63
+
64
+ ```ts
65
+ import { rayfinLocalDev } from "@microsoft/rayfin-local-dev/vite";
66
+ import { defineConfig } from "vite";
67
+
68
+ export default defineConfig({
69
+ plugins: [rayfinLocalDev()],
70
+ });
71
+ ```
72
+
73
+ Then resolve the local base URL when you create the client:
74
+
75
+ ```ts
76
+ import { resolveRayfinFunctionsBaseUrl } from "@microsoft/rayfin-local-dev";
77
+
78
+ const client = new RayfinClient<AppSchema, AppFunctionsSchema>({
79
+ publishableKey: import.meta.env.VITE_RAYFIN_PUBLISHABLE_KEY,
80
+ functionsBaseUrl: resolveRayfinFunctionsBaseUrl(),
81
+ });
82
+ ```
83
+
84
+ The helper returns an absolute `/.rayfin` URL on the current browser origin only when the Vite adapter has registered local Functions routing.
85
+ In production it returns `undefined`, so calls use the deployed Fabric backend.
86
+ If the local Functions host becomes unavailable while the adapter is active, the proxy returns HTTP 502 instead of falling back to deployed function code.
87
+ The proxy is reachable wherever the Vite development server is reachable, so keep Vite bound to loopback or restrict its `host` and `allowedHosts` settings to a trusted development network.
88
+
89
+ `npx rayfin dev` starts the frontend and Functions host together and supplies the selected Functions URL to the adapter.
90
+ `npx rayfin dev functions apply` starts only the Functions host; start your Vite frontend separately to use the same-origin route.
91
+ See [`npx rayfin dev functions apply`](../cli/functions/dev-apply.md) for the local debugging workflow.
92
+
93
+ For frameworks other than Vite, pass the framework-mapped Functions URL directly during development and leave `functionsBaseUrl` undefined in production.
94
+ For example, use `NEXT_PUBLIC_RAYFIN_FUNCTIONS_URL` with an explicit development guard in Next.js.
@@ -0,0 +1,70 @@
1
+ ---
2
+ sidebar_position: 3
3
+ ---
4
+
5
+ # Secrets
6
+
7
+ Functions read secret values through `ctx.getSecret(name)` on [`RayfinContext`](./writing-functions.md#rayfincontext-api).
8
+
9
+ ```ts
10
+ import {
11
+ UserDataFunctions,
12
+ type RayfinContext,
13
+ } from "@microsoft/fabric-user-data-functions";
14
+
15
+ const udf = new UserDataFunctions();
16
+
17
+ udf.func(
18
+ "readApiKey",
19
+ async (ctx: RayfinContext): Promise<string> => {
20
+ const apiKey = ctx.getSecret("THIRD_PARTY_API_KEY");
21
+ if (!apiKey) {
22
+ throw new Error("Missing THIRD_PARTY_API_KEY secret.");
23
+ }
24
+ return apiKey;
25
+ },
26
+ [],
27
+ );
28
+ ```
29
+
30
+ ## How resolution works
31
+
32
+ `ctx.getSecret(name)` returns `string | undefined`. It resolves in this order:
33
+
34
+ 1. The host-provided secret bag delivered with the invocation.
35
+ 2. `process.env[name]` as a fallback.
36
+
37
+ ## Setting secrets
38
+
39
+ Manage project secrets with the CLI:
40
+
41
+ ```bash
42
+ npx rayfin secret set THIRD_PARTY_API_KEY
43
+ ```
44
+
45
+ These are stored against your deployment and delivered to the function at invocation time.
46
+ See [Managing secrets](../cli/secrets.md) for the full `npx rayfin secret` command group.
47
+
48
+ ## Secrets in local development
49
+
50
+ The host-provided secret bag is only present for deployed invocations.
51
+ When you debug locally with `npx rayfin dev functions apply`, add the same key under `Values` in `rayfin/functions/local.settings.json` so it flows into `process.env` and is picked up by the `getSecret` fallback:
52
+
53
+ ```jsonc
54
+ {
55
+ "IsEncrypted": false,
56
+ "Values": {
57
+ "AzureWebJobsStorage": "",
58
+ "FUNCTIONS_WORKER_RUNTIME": "node",
59
+ "THIRD_PARTY_API_KEY": "local-development-value",
60
+ },
61
+ }
62
+ ```
63
+
64
+ `local.settings.json` is git-ignored, so these values stay on your machine.
65
+ See [Secrets in local development](../cli/secrets.md#using-secrets-in-local-development) for more.
66
+
67
+ ## Best practices
68
+
69
+ - Prefer secrets set with `npx rayfin secret set <NAME>` as the source of truth for deployed functions.
70
+ - Use the `process.env` fallback only for local debugging — do not make it your primary production secret source.
@@ -0,0 +1,44 @@
1
+ ---
2
+ sidebar_position: 2
3
+ ---
4
+
5
+ # Type generation
6
+
7
+ The Rayfin CLI parses every `udf.func()` call in `rayfin/functions/src/` and generates `rayfin/functions/src/types.ts`.
8
+ That file exports an `AppFunctionsSchema` type mapping each function name to its input and output types.
9
+
10
+ ## The generated schema
11
+
12
+ For the two functions shown in [Writing functions](./writing-functions.md), typegen emits:
13
+
14
+ ```ts
15
+ export type AppFunctionsSchema = {
16
+ greet: {
17
+ input: { firstName: string; lastName: string };
18
+ output: string;
19
+ };
20
+ getEntries: {
21
+ input: Record<string, never>; // RayfinContext was stripped
22
+ output: { id: string; message: string }[];
23
+ };
24
+ };
25
+ ```
26
+
27
+ - Runtime-injected parameters such as `RayfinContext` are excluded from `input`.
28
+ - `Promise<T>` return types are unwrapped to `T` in `output`.
29
+ - The schema is a **closed** object type — only the listed function names are accepted by `client.functions.<name>.invoke(...)`.
30
+
31
+ ## When typegen runs
32
+
33
+ | Command | Behavior |
34
+ | -------------------------------- | ---------------------------------------------------------------------------------------------------- |
35
+ | `npx rayfin functions init` | Runs typegen once after scaffolding to seed `types.ts`. |
36
+ | `npx rayfin dev functions apply` | Runs typegen once at startup, then keeps a watcher running (prefixed `[typegen]`) until you stop it. |
37
+
38
+ There is no standalone `typegen` command — it is driven by the commands above.
39
+ See [`npx rayfin dev functions apply`](../cli/functions/dev-apply.md).
40
+
41
+ ## Rules
42
+
43
+ - **Never hand-edit `types.ts`.** It is regenerated by `npx rayfin functions init` and by the watcher inside `npx rayfin dev functions apply`; your edits will be overwritten.
44
+ - **Never import Node.js packages into `types.ts`.** It is resolved by the frontend app's TypeScript compiler, which cannot resolve Node built-ins.
@@ -0,0 +1,140 @@
1
+ ---
2
+ sidebar_position: 1
3
+ ---
4
+
5
+ # Writing functions
6
+
7
+ Functions are declared with the `UserDataFunctions` class from `@microsoft/fabric-user-data-functions` and registered with `udf.func()`.
8
+
9
+ ## Registering a function
10
+
11
+ Create a single `UserDataFunctions` instance and register each function against it:
12
+
13
+ ```ts
14
+ import { UserDataFunctions } from "@microsoft/fabric-user-data-functions";
15
+
16
+ const udf = new UserDataFunctions();
17
+
18
+ udf.func(
19
+ "greet",
20
+ (firstName: string, lastName: string): string => {
21
+ return `Hello ${firstName} ${lastName}!`;
22
+ },
23
+ [],
24
+ );
25
+ ```
26
+
27
+ `udf.func()` takes three arguments:
28
+
29
+ | Argument | Description |
30
+ | ------------- | --------------------------------------------------------------------------------------------- |
31
+ | `name` | The function name. Becomes the invocation route and the key in your generated schema. |
32
+ | `fn` | The handler. Its parameter and return types are read by [typegen](./typegen.md). |
33
+ | `connections` | Optional array of connection bindings. Pass `[]` when the function does not use a connection. |
34
+
35
+ The handler's typed parameters and return type are extracted into `types.ts` so the frontend can call the function type-safely.
36
+ `Promise<T>` return types are unwrapped to `T` in the generated schema.
37
+
38
+ ## Accessing data and request context
39
+
40
+ To read your data, tokens, or logging from inside a function, declare a `RayfinContext` parameter.
41
+ Pass your app schema as a generic (`RayfinContext<AppSchema>`) — the same schema you use with `RayfinClient<AppSchema>` — for a fully typed data client:
42
+
43
+ ```ts
44
+ import {
45
+ UserDataFunctions,
46
+ type RayfinContext,
47
+ } from "@microsoft/fabric-user-data-functions";
48
+
49
+ type AppSchema = {
50
+ Entry: { id: string; message: string; createdAt: string };
51
+ };
52
+
53
+ const udf = new UserDataFunctions();
54
+
55
+ udf.func(
56
+ "getEntries",
57
+ async (
58
+ ctx: RayfinContext<AppSchema>,
59
+ ): Promise<{ id: string; message: string }[]> => {
60
+ console.log("getEntries invoked");
61
+ const data = ctx.getDataClient();
62
+ return await data.Entry.select(["id", "message", "createdAt"]).execute();
63
+ },
64
+ [],
65
+ );
66
+ ```
67
+
68
+ ### `RayfinContext` API
69
+
70
+ | Member | Description |
71
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
72
+ | `ctx.getDataClient()` | Returns the typed entity data client. Query with `.select([...]).where(...).execute()`, the same chain as `client.data.<Entity>` on the frontend. Typed when a generic is supplied; untyped (`Record<string, any>`) otherwise. |
73
+ | `ctx.getSecret(name)` | Returns a secret value, or `undefined` when unset. See [Secrets](./secrets.md). |
74
+ | `ctx.getToken(audienceType)` | Returns the delegated OBO access token (a `string`) for an external resource. Only works for an audience you declared with `udf.connection({ audienceType })` in the third argument — throws otherwise. See [Connecting to external resources](./connections/index.md). |
75
+ | `ctx.baseUrl` | The Rayfin endpoint URL (readonly). |
76
+ | `ctx.accessToken` | The auth token for the current request (readonly). |
77
+ | `ctx.publishableKey` | The Rayfin publishable key (readonly). |
78
+
79
+ Use `console.log(...)` / `console.error(...)` for logging.
80
+
81
+ > **Import `RayfinContext` from `@microsoft/fabric-user-data-functions`** — not from `@microsoft/rayfin-functions`.
82
+
83
+ ## Mixing parameters and context
84
+
85
+ A handler can take normal input parameters alongside a `RayfinContext`:
86
+
87
+ ```ts
88
+ udf.func(
89
+ "addEntry",
90
+ async (message: string, ctx: RayfinContext<AppSchema>): Promise<void> => {
91
+ console.log("addEntry invoked");
92
+ const data = ctx.getDataClient();
93
+ await data.Entry.create({ message });
94
+ },
95
+ [],
96
+ );
97
+ ```
98
+
99
+ `RayfinContext` is injected by the runtime, so typegen strips it from the generated input type — only `message: string` appears in the schema for `addEntry`.
100
+
101
+ ## Connecting to external resources
102
+
103
+ To call external Azure or Fabric resources (SQL, Storage, Key Vault, Cosmos DB, and more) from inside a function, declare a connection in the third argument of `udf.func()` and read a delegated token from the context:
104
+
105
+ ```ts
106
+ import { AudienceType } from "@microsoft/fabric-user-data-functions";
107
+
108
+ udf.func(
109
+ "readSecret",
110
+ async (ctx: RayfinContext, secretName: string): Promise<string> => {
111
+ const token = ctx.getToken(AudienceType.KeyVault);
112
+ // Use `token` with the resource's SDK or REST API.
113
+ return secretName;
114
+ },
115
+ [udf.connection({ audienceType: AudienceType.KeyVault })],
116
+ );
117
+ ```
118
+
119
+ The runtime exchanges the caller's identity for a resource-scoped token, so the function acts **as the calling user**.
120
+ See [Connecting to external resources](./connections/index.md) for the full model, the list of supported audiences, and per-resource guides.
121
+
122
+ ## Importing data entities
123
+
124
+ Share entity types between your data models and functions with TypeScript project references:
125
+
126
+ ```ts
127
+ import type { Entry } from "../../data/Entry.js";
128
+ ```
129
+
130
+ - Use `import type` — entity classes are only needed for their type shape, not their runtime value. A non-type import would pull the decorator runtime into the functions bundle.
131
+ - The `../../data/` path resolves because `tsconfig.json` sets `"references": [{ "path": ".." }]`.
132
+ - Always use the `.js` extension on relative imports for correct ESM resolution.
133
+
134
+ ## Rules
135
+
136
+ - Always register functions with `udf.func(name, handler, [])` — do not export bare functions.
137
+ - Use `RayfinContext<AppSchema>` for type-safe data access. Bare `RayfinContext` still works but gives no type safety on `getDataClient()`.
138
+ - Only call `ctx.getToken(audienceType)` for an audience you declared with `udf.connection({ audienceType })` in the third argument of `udf.func()` — calling it for an undeclared audience throws.
139
+ - A function must return a value or `void`. Throwing an error surfaces to the caller as an invocation failure carrying the error message.
140
+ - Use `import type` for data entity imports; never a runtime `import`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/rayfin-guide",
3
- "version": "1.36.0-alpha.1593",
3
+ "version": "1.36.0-alpha.1601",
4
4
  "description": "Cross-cutting Builder guides for the Rayfin platform — discovered by `@microsoft/rayfin-docs` via the `rayfinDocs` package.json field convention.",
5
5
  "type": "module",
6
6
  "files": [