Rayfin

Seeding data

Populate @anonymous() Rayfin entities with RayfinServerClient in a Node.js script — @authenticated() entities have no scripted seeding path today.

Use RayfinServerClient from a plain Node.js script to populate development data — the same client.data.<Entity> calls you use from your frontend, just run with tsx instead of in a browser. A script has no browser, so it can only authenticate with the publishableKey your project already generates — enough to satisfy an @anonymous() grant, and nothing else. See No supported path for @authenticated() entities below if the entity you want to seed requires a signed-in user.

What a seed script can authenticate as

RayfinServerClient requires publishableKey; accessToken is optional:

new RayfinServerClient<AppSchema>({
  baseUrl: env['RAYFIN_PUBLIC_API_URL'],
  publishableKey: env['RAYFIN_PUBLIC_PUBLISHABLE_KEY'],
});

Use RayfinServerClient, not RayfinClient — it skips the browser-coupled Auth module entirely. See @microsoft/rayfin-client.

Without an accessToken, every request carries only the publishable key. That satisfies an @anonymous() grant and nothing else — it cannot satisfy @authenticated(), which requires a valid signed-in user's access token on every request (see Built-in roles).

There is no supported way for a Node.js script to acquire that token itself. Fabric SSO signs a user in through a browser popup or the Fabric portal iframe (see Fabric SSO) — flows a headless script cannot drive — and, per Sessions, Rayfin doesn't expose a way to read a token back out for reuse elsewhere. accessToken on RayfinServerClient exists for code that already holds a token issued by some other trusted flow — for example, a Rayfin function forwarding the caller's own request token — not as a way to sign a script in.

Service principal credentials (rayfin login --service-principal) don't help here either — they authenticate the CLI itself for deployment (rayfin up, rayfin up db apply), a separate concern from a signed-in user's access token that client.data.* calls check.

Seeding an anonymous entity

Reference or lookup data with no owner — content anyone can read, that only you create — is the case a seed script handles cleanly, because it needs no caller identity at all:

rayfin/data/FaqCategory.ts
import { entity, anonymous, uuid, text } from '@microsoft/rayfin-core';

@entity()
@anonymous('*')
export class FaqCategory {
  @uuid() id!: string;
  @text({ max: 100 }) name!: string;
}
rayfin/data/FaqEntry.ts
import { entity, anonymous, uuid, text, one } from '@microsoft/rayfin-core';
import { FaqCategory } from './FaqCategory.js';

@entity()
@anonymous('*')
export class FaqEntry {
  @uuid() id!: string;
  @text({ max: 200 }) question!: string;
  @text({ max: 2000 }) answer!: string;
  @uuid() category_id!: string;
  @one(() => FaqCategory) category!: FaqCategory;
}

Note

A tenant admin has to enable "Enable anonymous data access for Fabric Apps" before Fabric grants the anonymous role at all — see Built-in roles. Without it, even a correctly decorated @anonymous() entity rejects anonymous requests.

Read the backend URL and publishable key from rayfin/.env (generated by rayfin up) rather than hardcoding them — the port and key vary per project and per deployment:

scripts/seed.ts
import { readFileSync } from 'fs';
import { RayfinServerClient } from '@microsoft/rayfin-client';
import type { AppSchema } from '../rayfin/data/schema';

function loadEnv(): Record<string, string> {
  const vars: Record<string, string> = {};
  for (const line of readFileSync('rayfin/.env', 'utf-8').split('\n')) {
    const match = line.match(/^([^#=]+)=(.+)$/);
    if (match) vars[match[1].trim()] = match[2].trim();
  }
  return vars;
}

const env = loadEnv();
const client = new RayfinServerClient<AppSchema>({
  baseUrl: env['RAYFIN_PUBLIC_API_URL'],
  publishableKey: env['RAYFIN_PUBLIC_PUBLISHABLE_KEY'],
});

async function seed() {
  // Create parent records first, then children that reference their IDs.
  const category = await client.data.FaqCategory.create({ name: 'Billing' });

  await client.data.FaqEntry.create({
    question: 'How do I change my plan?',
    answer: 'Open Settings → Billing and choose a new plan.',
    category: { id: category.id },
  });
}

seed().catch(console.error);

Run it with tsx:

npx tsx scripts/seed.ts

Or wire it into package.json for a shorter command:

package.json
{
  "scripts": {
    "seed": "tsx scripts/seed.ts"
  }
}
npm run seed

Create parents before children. A @one() relationship needs the parent's id to exist first — create the FaqCategory before the FaqEntry that references it, as shown above. See Relationships for how @one() / @many() work.

Seeding a single entity

For an entity with no relationships, the script is shorter — just create:

scripts/seed-faq-categories.ts
import { readFileSync } from 'fs';
import { RayfinServerClient } from '@microsoft/rayfin-client';
import type { AppSchema } from '../rayfin/data/schema';

function loadEnv(): Record<string, string> {
  const vars: Record<string, string> = {};
  for (const line of readFileSync('rayfin/.env', 'utf-8').split('\n')) {
    const match = line.match(/^([^#=]+)=(.+)$/);
    if (match) vars[match[1].trim()] = match[2].trim();
  }
  return vars;
}

const env = loadEnv();
const client = new RayfinServerClient<AppSchema>({
  baseUrl: env['RAYFIN_PUBLIC_API_URL'],
  publishableKey: env['RAYFIN_PUBLIC_PUBLISHABLE_KEY'],
});

async function seed() {
  const names = ['Billing', 'Account', 'Troubleshooting'];
  for (const name of names) {
    await client.data.FaqCategory.create({ name });
  }
}

seed().catch(console.error);

No supported path for @authenticated() entities

Most application data is not anonymous — anything with a user_id/owner_id and a row-level policy, like the Todo entity in Build a todo app, is scoped with @authenticated(). As established above, a seed script cannot obtain a token for any user, so there is no supported way today to script seed data into an entity that requires authentication.

If you need rows in an entity like that for development or testing, create them the way a real signed-in user would — through the running app itself, signed in with Fabric SSO — rather than through a script. Do not hand-roll an Authorization header or fabricate a token shaped like a Fabric-issued JWT: there is no supported way to mint a valid one outside Fabric SSO, and Rayfin rejects anything that doesn't verify against its own signing keys.

PromptWrite a seed script for anonymous reference data
In my Rayfin project, I have an FaqCategory entity (id, name) and an FaqEntry entity (id, question, answer, category_id, with a @one(() => FaqCategory) relationship named category), both decorated @anonymous('*') as public reference data with no owner. Write a Node.js seed script at scripts/seed.ts using RayfinServerClient from @microsoft/rayfin-client. Read RAYFIN_PUBLIC_API_URL and RAYFIN_PUBLIC_PUBLISHABLE_KEY from rayfin/.env — do not pass an accessToken, since this client only needs to satisfy the anonymous role. Create one FaqCategory named "Billing", then one FaqEntry in that category. Add a "seed" script to package.json that runs it with tsx.
Something wrong on this page?Report an issueEdit this page

On this page