Skill v1.0.0
currentTrusted Publisher100/100version: "1.0.0" name: data-modeling description: > Use when the app needs to store or read data — records, a database, entities, CRUD, lists, "save/remember X", or per-user data with row-level security. This app's data layer is Rayfin's: you declare entities as TypeScript classes with decorators in packages/data/src/, register them in the schema, and read/write them through the typed rayfin-client. Covers: defining an @entity, field decorators, registering it, per-entity access with @role, owner-only row-level security, and the client query/mutation API. Triggers: data, database, model, entity, schema, table, record, CRUD, create, read, update, delete, list, store, save, persist, per-user, row-level security, RLS, access control, ownership.
Data modeling — entities, schema, and row-level security
This app persists data with Rayfin data. You define entities as decorated TypeScript classes under packages/data/src/, register them in packages/data/src/index.ts, define their isomorphic contracts in packages/shared/src/index.ts, and read/write them with the typed client from packages/frontend/src/lib/rayfin-client.ts (await getRayfinClient()).
Read the docs first — they ship in the packages
Rayfin's data rules are owned by the installed packages, not by this skill. Decorators, field options, relationships, permissions, the query API and the platform's constraints all move between releases. Read them from disk:
| You need… | Read | |
|---|---|---|
@entity, every field decorator, and their options | node_modules/@microsoft/rayfin-core/assets/docs/decorators.md | |
Permissions end to end — @role, the policy DSL, claims, include / exclude | node_modules/@microsoft/rayfin-guide/assets/docs/data/permissions.md | |
The @anonymous / @authenticated shorthands | node_modules/@microsoft/rayfin-core/assets/docs/permissions.md | |
| Entity authoring end to end, relationships, schema registration | node_modules/@microsoft/rayfin-guide/assets/docs/data/overview.md | |
| Reading and writing with the client | node_modules/@microsoft/rayfin-guide/assets/docs/data/graphql.md | |
| Paging, cursors, text-field nullability | node_modules/@microsoft/rayfin-data/assets/docs/index.md | |
| Constraints that will bite you — text length, FK naming, many-to-many, dialect | node_modules/@microsoft/rayfin-guide/assets/docs/known-limitations.md |
Read known-limitations.md before you design entities. It is short, and it is where the platform tells you what it will not do.
Everything below is about this template — what it turns on, where files go, and how its build is wired. For anything about Rayfin itself, use the table.
Step 1 — turn the data service on
The base template keeps the data service off, because a dashboard over a semantic model does not need a database. Turn it on for an app that needs persistence:
npm run pack:add -- data-modeling
That flips data.enabled in rayfin/rayfin.yml and plants a starter entity in packages/data/src/Item.ts, registered in packages/data/src/index.ts, with its isomorphic contract in packages/shared/src/index.ts. Re-applying is safe: it keeps files you have edited, and --force-seeds is the explicit way back to the pack's version.
The template authenticates before rendering app content through its dynamic auth provider and gate. Keep that boundary intact and follow the installed rayfin-guide/assets/docs/auth/ pages for additional service-specific requirements. Preserve server-side entity permissions and row-level security. Do not replace an authentication failure with anonymous access or block the entire app because it is outside the Fabric portal.
Step 2 — write entity files
One file per entity under packages/data/src/, for example packages/data/src/Note.ts. Start from the seeded Item.ts — rename it and change the fields. Editing a working entity is more reliable than writing one from nothing. When the real model replaces the example, remove the unused starter Item runtime entity, its packages/data/src/index.ts registration, and its packages/shared/src/index.ts contract so it does not become user-visible. @microsoft/rayfin-core, @microsoft/rayfin-data and @microsoft/rayfin-client are already pinned in the base app — nothing to install.
For the decorator set, field options, relationship syntax, FK naming and the many-to-many rule, read rayfin-core/assets/docs/decorators.md and rayfin-guide/assets/docs/data/overview.md, then known-limitations.md.
Step 3 — register entities in this template's schema
packages/data/src/index.ts starts with the seeded Item registration, while packages/shared/src/index.ts owns its isomorphic record contract and schema type map. Add every entity, including join entities, to both packages:
// packages/shared/src/index.tsexport interface NoteRecord {id: number;title: string;}export type UniversalAppSchema = {Note: NoteRecord;};// packages/data/src/index.tsimport { Note } from './Note.js';export const schema = [Note];
The client's generic type comes fromUniversalAppSchema(already referenced bypackages/frontend/src/lib/rayfin-client.ts), so add every entity to both theschemaarray: one registered in the array but missing from the type isreachable at runtime and invisible to the compiler.Type-check with `tsc -b`, never `tsc --noEmit`. The workspace packages arereferenced TypeScript projects, and the frontend client imports the sharedschema type, soa baretsc --noEmitfails withTS6305until the referenced project has beenbuilt. Runnpm run typecheckfor the real referenced-project check.npm run buildusestsc -b --noCheckfor speed and does not prove theentity types are correct.
Step 4 — read and write
The query chain, mutations, findById, filtering and paging are documented in rayfin-guide/assets/docs/data/graphql.md and rayfin-data/assets/docs/index.md. Use await getRayfinClient() from packages/frontend/src/lib/rayfin-client.ts inside an async query or mutation; that helper is this template's; the API it returns is the package's.
For the interface over your entities — optimistic updates, forms, and the loading / empty / error states — read `crud-ui`, which ships the kit.
Step 5 — per-user data
"Each person sees only their own …" is a server-side @role policy, never a filter in the UI. A client-side filter still leaves the rows fetchable.
Read rayfin-guide/assets/docs/data/permissions.md for the decorator, the typed policy DSL, the supported claims, logical composition, and field visibility — it is the fullest treatment. rayfin-core/assets/docs/permissions.md adds the @anonymous / @authenticated shorthands, owner-field typing, and owner stamping. Keep authentication and authorization on the data operations that require them.
Notes
- Stable features only. Use
@authenticated/@role('authenticated', …).
Anonymous data access on Fabric requires a tenant admin switch — see rayfin-guide/assets/docs/data/overview.md. If asked for public data, say so and use authenticated access.
- Deploying ships the data model. A schema change migrates on the next
deploy. Read app-deployment and follow the installed, version-matched CLI guide.
<!-- MAINTAINERS: this skill owns this template — the data service toggle, the workspace schema shape, the tsc -b build note, and the auth pairing. It does not own Rayfin.
Rayfin's own rules are read at runtime from the installed packages' docs (rayfinDocs.dir, today assets/docs), so they cannot drift from the version a generated app actually has. That replaces the previous arrangement, where this file hand-mirrored rules from the CLI's bundled skill:rayfin and had to be diffed against it on every CLI bump. Drift under that arrangement had already shipped once: bare @text() appeared in every example here while the authoritative rule required an explicit max on MSSQL. -->