@microsoft/rayfin-guide 1.1.0 → 1.33.0-beta.1

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,172 @@
1
+ ---
2
+ sidebar_position: 3
3
+ ---
4
+
5
+ # Data permissions
6
+
7
+ Rayfin uses the `@role` decorator to attach authorization rules directly to your data models.
8
+ Permissions are type-safe, refactor-friendly, and compiled into Data API Builder (DAB) configuration automatically.
9
+
10
+ ## Built-in roles
11
+
12
+ Rayfin recognizes two built-in roles:
13
+
14
+ - **anonymous** — public access without authentication.
15
+ - **authenticated** — requires a valid user session.
16
+
17
+ ## The `@role` decorator
18
+
19
+ Apply `@role` at the class level to control which roles can perform which actions on an entity.
20
+
21
+ ```typescript
22
+ @role(roleName, actions, options?)
23
+ ```
24
+
25
+ | Parameter | Description |
26
+ | --- | --- |
27
+ | `roleName` | The role name (`'anonymous'` or `'authenticated'`). |
28
+ | `actions` | A single action or array of actions: `'create'`, `'read'`, `'update'`, `'delete'`, or `'*'` for all. |
29
+ | `options` | Optional object with `check`, `include`, and `exclude` properties. |
30
+
31
+ ## Basic example
32
+
33
+ Grant anonymous users read access and restrict authenticated users to their own data:
34
+
35
+ ```typescript
36
+ import { entity, role, uuid, text } from '@microsoft/rayfin-core';
37
+
38
+ @entity()
39
+ @role('anonymous', 'read')
40
+ @role('authenticated', ['create', 'read', 'update', 'delete'], {
41
+ check: (claims, item) => claims.sub.eq(item.user_id),
42
+ })
43
+ export class Todo {
44
+ @uuid() id!: string;
45
+ @text() title!: string;
46
+ @text() description?: string;
47
+ @text() user_id!: string;
48
+ }
49
+ ```
50
+
51
+ In this example, authenticated users can only access Todo items where `user_id` matches their JWT `sub` claim.
52
+
53
+ ## Type-safe policy expressions
54
+
55
+ The `check` callback provides typed access to both claims and entity fields.
56
+ TypeScript infers the entity type from the decorated class, so you get autocompletion and refactor safety with no extra configuration.
57
+
58
+ ```typescript
59
+ check: (claims, item) => claims.sub.eq(item.user_id)
60
+ ```
61
+
62
+ ### Supported claims
63
+
64
+ | Claim | Description |
65
+ | --- | --- |
66
+ | `claims.sub` | Subject identifier (user ID). |
67
+ | `claims.email` | User email address. |
68
+ | `claims.role` | User role. |
69
+
70
+ ### Expression operators
71
+
72
+ | Operator | Example | DAB output |
73
+ | --- | --- | --- |
74
+ | `.eq()` | `claims.sub.eq(item.user_id)` | `@claims.sub eq @item.user_id` |
75
+
76
+ ### Logical operators
77
+
78
+ Combine expressions with `.and()` and `.or()`:
79
+
80
+ ```typescript
81
+ check: (claims, item) =>
82
+ claims.sub.eq(item.user_id).and(item.isActive.eq(true))
83
+ ```
84
+
85
+ Both sides are parenthesized automatically, so grouping is always explicit:
86
+
87
+ ```typescript
88
+ // (claims.role eq 'admin') or (claims.sub eq @item.owner_id)
89
+ check: (claims, item) =>
90
+ claims.role.eq('admin').or(claims.sub.eq(item.owner_id))
91
+ ```
92
+
93
+ ## Field-level permissions
94
+
95
+ Control which fields a role can access using `include` or `exclude` in the role options.
96
+
97
+ ### Include specific fields
98
+
99
+ Only allow the `Title` field during create:
100
+
101
+ ```typescript
102
+ @role('authenticated', 'create', {
103
+ check: (claims, item) => claims.sub.eq(item.createdBy),
104
+ include: ['Title'],
105
+ })
106
+ ```
107
+
108
+ ### Exclude specific fields
109
+
110
+ Hide sensitive fields from read operations:
111
+
112
+ ```typescript
113
+ @role('authenticated', 'read', {
114
+ check: (_claims, item) => item.IsAdmin.eq(false),
115
+ exclude: ['last_login'],
116
+ })
117
+ ```
118
+
119
+ Field arrays are typed to the entity's actual property names.
120
+ Renaming a field produces a compile-time error in every `include` or `exclude` list that references it.
121
+
122
+ ## Action-specific permissions
123
+
124
+ Apply different rules per action by using multiple `@role` decorators with single actions:
125
+
126
+ ```typescript
127
+ @entity()
128
+ @role('anonymous', 'read')
129
+ @role('authenticated', 'create', {
130
+ check: (claims, item) => claims.sub.eq(item.createdBy),
131
+ include: ['Title'],
132
+ })
133
+ @role('authenticated', 'read', {
134
+ check: (claims, item) => claims.sub.eq(item.createdBy),
135
+ })
136
+ @role('authenticated', 'update', {
137
+ check: (claims, item) => claims.sub.eq(item.createdBy),
138
+ exclude: ['adminContent'],
139
+ })
140
+ export class SecureDocument {
141
+ @uuid() id!: string;
142
+ @text() Title!: string;
143
+ @text({ optional: true }) adminContent?: string;
144
+ @text() createdBy!: string;
145
+ }
146
+ ```
147
+
148
+ ## Storage permissions
149
+
150
+ The same `@role` decorator works with storage entities.
151
+ When applied to a `@blob()` class, Rayfin generates a storage policy instead of a database policy:
152
+
153
+ ```typescript
154
+ import { blob, role } from '@microsoft/rayfin-core';
155
+
156
+ @blob()
157
+ @role('authenticated', '*', {
158
+ check: (claims, item) => claims.sub.eq(item.owner_id),
159
+ })
160
+ export class ProfileImage {
161
+ owner_id!: string;
162
+ }
163
+ ```
164
+
165
+ ## How it works
166
+
167
+ - The `@role` decorator collects permission metadata at class definition time.
168
+ - When you run `npx rayfin up db apply`, the CLI reads that metadata and generates DAB-compliant permission entries in the configuration.
169
+ - Policy callbacks are compiled into DAB OData-style policy strings (for example `@claims.sub eq @item.user_id`).
170
+ - Field `include`/`exclude` arrays map directly to DAB field permission configuration.
171
+ - Multiple `@role` decorators on the same class are aggregated per role.
172
+ Conflicting declarations produce a warning at generation time.
@@ -0,0 +1,165 @@
1
+ ---
2
+ sidebar_position: 4
3
+ ---
4
+
5
+ # Form Validation
6
+
7
+ Rayfin can generate a [Standard Schema](https://standardschema.dev) validator directly from your decorated entity classes.
8
+ This lets you validate form input on the client without adding a separate validation library like Zod or Yup.
9
+
10
+ ## Build a validator from an entity
11
+
12
+ Use `toStandardSchema` to create a validator from any `@entity()` class.
13
+ The `id` field and relationship navigation properties (`@one`, `@many`) are automatically excluded — you only need to list additional fields your form does not collect.
14
+
15
+ ```typescript
16
+ import { toStandardSchema } from '@microsoft/rayfin-core';
17
+ import { Todo } from '../rayfin/data/Todo.js';
18
+
19
+ // id is auto-omitted. List fields you want to omit from validation.
20
+ const todoInputSchema = toStandardSchema(Todo, {
21
+ omit: ['createdAt', 'updatedAt'] as const,
22
+ });
23
+ ```
24
+
25
+ The returned object implements the Standard Schema v1 contract (`~standard`) so it works with any compatible library (TanStack Form, Conform, tRPC v11, and others).
26
+ It also exposes a convenience `.validate()` method for direct use.
27
+
28
+ ## Validate form input
29
+
30
+ Call `.validate()` with your form values.
31
+ The result is either `{ value }` on success or `{ issues }` on failure.
32
+
33
+ ```typescript
34
+ const result = todoInputSchema.validate({
35
+ title: title.trim(),
36
+ isCompleted: false,
37
+ priority: 'medium',
38
+ });
39
+
40
+ if (result.issues) {
41
+ // Map issues to per-field errors by path[0]
42
+ const errors: Record<string, string> = {};
43
+ for (const issue of result.issues) {
44
+ const key = String(issue.path?.[0] ?? '_');
45
+ if (!errors[key]) errors[key] = issue.message;
46
+ }
47
+ // Display errors in the UI
48
+ } else {
49
+ // result.value is typed as Omit<Todo, 'id' | 'createdAt' | 'updatedAt'>
50
+ await api.createTodo(result.value);
51
+ }
52
+ ```
53
+
54
+ Validation is synchronous.
55
+ All checks (type guards, string length, regex, enum membership) run in memory with no async overhead.
56
+
57
+ ## Read field constraints for UI hints
58
+
59
+ Use `getFieldConstraints` to read the decorator constraints for a single field.
60
+ This is useful for displaying character counters, limit labels, or other UI hints without building a full schema.
61
+
62
+ ```typescript
63
+ import { getFieldConstraints } from '@microsoft/rayfin-core';
64
+ import { Todo } from '../rayfin/data/Todo.js';
65
+
66
+ const titleConstraints = getFieldConstraints(Todo, 'title');
67
+ // { type: 'string', min: 1, max: 50, optional: false }
68
+
69
+ const maxLength =
70
+ titleConstraints?.type === 'string' ? titleConstraints.max : undefined;
71
+ ```
72
+
73
+ The field name is type-checked against the entity — typos are caught at compile time.
74
+
75
+ ## What gets validated
76
+
77
+ The validator checks each field based on its decorator:
78
+
79
+ | Decorator | Checks |
80
+ | --- | --- |
81
+ | `@text()` | Is a string. Enforces `min`, `max`, and `regex` when specified. |
82
+ | `@uuid()` | Is a string matching the UUID format. |
83
+ | `@email()` | Is a string matching a practical email pattern. |
84
+ | `@int()` | Is a finite integer. Enforces `min` and `max` bounds. |
85
+ | `@decimal()` | Is a finite number. Enforces `min` and `max` bounds. |
86
+ | `@boolean()` | Is a boolean. |
87
+ | `@date()` | Is a `Date` object, ISO string, or numeric timestamp. |
88
+ | `@set()` | Value is one of the declared enum literals. |
89
+
90
+ Required fields (the default) produce a "required" issue when missing or `null`.
91
+ Optional fields (`{ optional: true }`) are silently skipped when absent.
92
+ Unknown fields not declared on the entity are rejected.
93
+
94
+ ## Auto-omit behavior
95
+
96
+ `toStandardSchema` automatically excludes:
97
+
98
+ - The `id` primary key — typically server-generated, never part of a form.
99
+ - Relationship navigation properties (`@one`, `@many`) — not form inputs.
100
+
101
+ Pass additional field names in the `omit` option for other server-managed fields like timestamps or `user_id`.
102
+ The `omit` array is type-safe — misspelled field names are caught at compile time.
103
+
104
+ ```typescript
105
+ // Only need to list fields beyond id and relationships
106
+ const schema = toStandardSchema(Note, {
107
+ omit: ['createdAt', 'updatedAt', 'user_id'] as const,
108
+ });
109
+ ```
110
+
111
+ ## Complete React example
112
+
113
+ ```tsx
114
+ import { useMemo, useState } from 'react';
115
+ import { toStandardSchema, getFieldConstraints } from '@microsoft/rayfin-core';
116
+ import { Todo } from '../rayfin/data/Todo.js';
117
+
118
+ export function TodoForm({ onSubmit }) {
119
+ const [title, setTitle] = useState('');
120
+ const [error, setError] = useState('');
121
+
122
+ const todoInputSchema = useMemo(
123
+ () => toStandardSchema(Todo, { omit: ['createdAt', 'updatedAt'] as const }),
124
+ []
125
+ );
126
+
127
+ const titleConstraints = getFieldConstraints(Todo, 'title');
128
+ const maxLength =
129
+ titleConstraints?.type === 'string' ? titleConstraints.max : undefined;
130
+
131
+ const handleSubmit = async (e: React.FormEvent) => {
132
+ e.preventDefault();
133
+ const result = todoInputSchema.validate({ title: title.trim() });
134
+ if (result.issues) {
135
+ setError(result.issues[0].message);
136
+ return;
137
+ }
138
+ setError('');
139
+ await onSubmit(result.value);
140
+ setTitle('');
141
+ };
142
+
143
+ return (
144
+ <form onSubmit={handleSubmit}>
145
+ <input value={title} onChange={(e) => setTitle(e.target.value)} />
146
+ {maxLength && <span>{title.length}/{maxLength}</span>}
147
+ {error && <p style={{ color: 'red' }}>{error}</p>}
148
+ <button type="submit">Add</button>
149
+ </form>
150
+ );
151
+ }
152
+ ```
153
+
154
+ ## Standard Schema interop
155
+
156
+ The object returned by `toStandardSchema` implements `StandardSchemaV1` from `@standard-schema/spec`.
157
+ Any library that reads the `~standard` property can consume it directly.
158
+
159
+ ```typescript
160
+ // TanStack Form, Conform, tRPC v11, etc. read ~standard automatically.
161
+ // You can also access it explicitly if needed:
162
+ const result = todoInputSchema['~standard'].validate(formValues);
163
+ ```
164
+
165
+ The `RayfinStandardSchema` and `StandardSchemaV1` types are re-exported from `@microsoft/rayfin-core` so you do not need a direct dependency on `@standard-schema/spec`.
@@ -0,0 +1,118 @@
1
+ ---
2
+ sidebar_position: 5
3
+ title: Create app with CLI
4
+ ---
5
+
6
+
7
+
8
+ Project Rayfin is a modern **Backend-as-a-Service (BaaS)** platform that helps teams build and ship applications faster.
9
+ It provides ready-to-use backend infrastructure so you can focus on the product experience.
10
+
11
+ ## Create an app from template
12
+
13
+ Run `npm create @microsoft/rayfin@latest` in a terminal window and select welcome template for Typescript.
14
+
15
+ ## Run the app
16
+
17
+ 1. In a terminal window, run `npx rayfin up` to start the Rayfin backend.
18
+ 2. In a second terminal window run `npm run dev` to start the frontend.
19
+ 3. When the frontend starts, it will output the page to visit.
20
+ Visit and ensure you can view the Timestamp Tracker.
21
+ 4. Click **Send Timestamp** to POST the current time to `/api/graphql/Timestamp`, then use **Refresh list** to pull back the newest 100 entries.
22
+ 5. All UI plus data-fetching logic lives in a single file: `src/main.ts`.
23
+ 6. To point at a different backend, set `RAYFIN_PUBLIC_API_URL` in `rayfin/.env` and re-run `npm run dev` (defaults to `http://localhost:5168`).
24
+
25
+ ## Update the data model
26
+
27
+ Add a `message` field to the **Timestamp** entity in `rayfin/data/Timestamp.ts`.
28
+
29
+ ```typescript
30
+ import { entity, authenticated, uuid, text, date } from '@microsoft/rayfin-core';
31
+
32
+ @entity()
33
+ @authenticated('*')
34
+ export class Timestamp {
35
+ @uuid() id!: string;
36
+ @date() timestamp!: Date;
37
+ @text() message!: string;
38
+ }
39
+ ```
40
+
41
+ ## Update the frontend to display the message
42
+
43
+ In `src/main.ts`, update the creation and query to include the `message` field, add a table header, and display the message in the table rows.
44
+
45
+ ```typescript
46
+ // 1. Update sendTimestamp to include message
47
+ await this.rayfinClient.data.gql.Timestamp.create({
48
+ timestamp: now,
49
+ message: 'Hello from Rayfin!', // Add this
50
+ });
51
+
52
+ // 2. Update the query to include 'message'
53
+ const items = await this.rayfinClient.data.gql.Timestamp.select([
54
+ 'id',
55
+ 'timestamp',
56
+ 'message', // Add this
57
+ ]);
58
+
59
+ // 3. Update the row template in the updateTable function
60
+ const rows = this.timestamps
61
+ .map(
62
+ (entry) => `
63
+ <tr>
64
+ <td class="timestamp-mono">${entry.id}</td>
65
+ <td>${formatDate(entry.timestamp)}</td>
66
+ <td>${entry.message}</td>
67
+ </tr>
68
+ `
69
+ );
70
+
71
+ // 4. Update the table headers in the updateTable function
72
+ <thead>
73
+ <tr>
74
+ <th>ID</th>
75
+ <th>Timestamp</th>
76
+ <th>Message</th>
77
+ </tr>
78
+ </thead>
79
+ ```
80
+
81
+ ## Apply database changes
82
+
83
+ After updating your data models, apply the changes to your database.
84
+
85
+ ```bash
86
+ npx rayfin up db apply
87
+ ```
88
+
89
+ ## Test your changes
90
+
91
+ After updating your data models, test your app.
92
+
93
+ ```bash
94
+ npm run dev
95
+ ```
96
+
97
+ > NOTE: Any changes to `rayfin.yml` require you to run `npx rayfin up` again.
98
+
99
+ ## View your local database
100
+
101
+ 1. Identify your container name or ID for the database service.
102
+ Use this command to list all running containers and note the name or ID of your database container.
103
+
104
+ ```bash
105
+ docker ps
106
+ ```
107
+
108
+ 1. Access the container's shell and use the container name or ID for the database.
109
+
110
+ ```bash
111
+ docker exec -it <container_name_or_id> bash
112
+ ```
113
+
114
+ 1. Once inside the container's shell, use the appropriate command for your database system.
115
+
116
+ ```bash
117
+ psql -U RayfinDB
118
+ ```
@@ -0,0 +1,73 @@
1
+ ---
2
+ sidebar_position: 4
3
+ title: Create an App Backend
4
+ ---
5
+
6
+ This guide walks through creating a new Fabric data app directly in the Microsoft Fabric portal.
7
+
8
+ ## Prerequisites
9
+
10
+ - A Microsoft account with access to Microsoft Fabric.
11
+ - A Fabric workspace where you have contributor or admin permissions.
12
+ - Fabric data app enabled in your tenant admin settings (see below).
13
+
14
+ ## Enable Fabric data app in tenant admin settings
15
+
16
+ A Fabric tenant administrator must enable the Fabric data app workload before users can create Rayfin items.
17
+ If you are not a tenant admin, contact your organization's Fabric administrator to complete this step.
18
+
19
+ 1. Sign in to the [Fabric admin portal](https://app.fabric.microsoft.com/admin-portal).
20
+ 1. Navigate to **Tenant settings**.
21
+ 1. Under **Fabric Apps (preview)**, toggle the setting to **Enabled**.
22
+ 1. Choose whether to enable it for the entire organization or specific security groups.
23
+ 1. Click **Apply**.
24
+
25
+ Changes may take a few minutes to propagate.
26
+ Once enabled, users in the allowed scope can create Fabric data apps in their workspaces.
27
+
28
+ ## Step 1: Sign in to the Fabric portal
29
+
30
+ Open [Microsoft Fabric](https://app.fabric.microsoft.com) in your browser and sign in with your Microsoft account.
31
+
32
+ ## Step 2: Select a workspace
33
+
34
+ After signing in, select a workspace from the left navigation panel.
35
+ If you do not have an existing workspace, create one:
36
+
37
+ 1. Click **Workspaces** in the left navigation.
38
+ 1. Click **New workspace**.
39
+ 1. Enter a name for the workspace and select Fabric capacity.
40
+
41
+ ## Step 3: Create a new Fabric data app
42
+
43
+ 1. In the workspace view, click **New item**.
44
+ 1. Search for **Fabric data app** in the item type list or scroll to find it.
45
+ 1. Select **Fabric data app** to open the creation dialog.
46
+ 1. Enter a name for your Fabric data app (for example, `my-rayfin-app`).
47
+ 1. Click **Create**.
48
+
49
+ ## Step 4: Open, edit, and deploy your app
50
+
51
+ After creating the Fabric data app, open the project in VS Code and use GitHub Copilot to build your app.
52
+
53
+ 1. In the Fabric portal, click **Open in VS Code** on your newly created Fabric data app.
54
+ VS Code opens with the project files loaded.
55
+ 1. Use **GitHub Copilot** to make changes to your app.
56
+ For example, ask Copilot to add a new data model, create a UI component, or update your API endpoints.
57
+ 1. When you are ready to deploy, run the following command in the VS Code terminal:
58
+
59
+ ```bash
60
+ npx rayfin up
61
+ ```
62
+
63
+ You can also use the Command Palette and select **Project Rayfin: Up: Deploy to Fabric**.
64
+ 1. Once the deployment completes, Fabric provisions your app and displays the **App URL** in the terminal output.
65
+ 1. Click the **App URL** to open your deployed application in the browser and verify your changes.
66
+
67
+ See [Deploy to Microsoft Fabric](../app-backend/deploy.md) to learn more about deployment options.
68
+
69
+ ## Next steps
70
+
71
+ - Explore [Data Models & Decorators](../data/overview.md) to define your backend schema.
72
+ - Learn how to connect frontends with the [GraphQL guide](../data/graphql.md).
73
+ - Configure authentication with [Rayfin Auth](../auth/overview.md).