@microsoft/rayfin-guide 1.35.0-alpha.1374 → 1.35.0-alpha.1541

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,188 @@
1
+ ---
2
+ sidebar_position: 50
3
+ ---
4
+
5
+ # Deep linking and shareable URLs
6
+
7
+ Deep linking lets a user copy the browser URL and send someone the exact view they were looking at.
8
+
9
+ A Fabric data app runs inside a cross-origin iframe and has no addressable URL of its own, so it cannot write to the address bar directly.
10
+ The `@microsoft/rayfin-app-state-fabric` package bridges that gap: your app hands the portal a plain JSON object, and the portal encodes it into the URL it owns.
11
+
12
+ ## Availability
13
+
14
+ Deep linking is rolling out per tenant, so it is not available everywhere yet.
15
+
16
+ Always call `isSupported()` before showing a share button or any affordance that depends on the URL carrying state.
17
+ It resolves to `undefined` when the host does not support deep linking, and your app should continue to work normally in that case.
18
+
19
+ ## How it works
20
+
21
+ 1. A user opens a link whose portal URL carries encoded state.
22
+ 2. The portal seeds that state onto your app's iframe URL before your app loads.
23
+ 3. Your app reads it synchronously on startup, so the shared view renders first and no default view flashes.
24
+ 4. As the user navigates, your app writes state back through the SDK and the portal updates its address bar.
25
+ 5. When the user presses Back or Forward, the portal notifies your app so it can restore the matching view.
26
+
27
+ The portal owns encoding, versioning, size limits, and browser history.
28
+ Your app owns the shape of the state object.
29
+
30
+ ## Install the package
31
+
32
+ ```bash
33
+ npm install @microsoft/rayfin-app-state-fabric
34
+ ```
35
+
36
+ ## Create the client
37
+
38
+ Create one client for the lifetime of your app and share it, rather than constructing one per component.
39
+
40
+ ```typescript
41
+ import { createFabricAppStateClient } from '@microsoft/rayfin-app-state-fabric';
42
+
43
+ const appState = createFabricAppStateClient({
44
+ targetOrigin: 'https://app.fabric.microsoft.com',
45
+ });
46
+ ```
47
+
48
+ Pass `targetOrigin` whenever you know it.
49
+ Without it, messages are posted with `"*"` and inbound events are not origin-checked.
50
+
51
+ ## Read launch state before first render
52
+
53
+ Read the launch state before your first render so a default view never flashes before the shared one.
54
+
55
+ ```typescript
56
+ const launch = appState.getLaunchStateSync();
57
+ renderApp(launch ?? defaultView);
58
+ ```
59
+
60
+ `getLaunchStateSync()` returns the state without awaiting, which is what you want on the first render path.
61
+ It returns `undefined` when the URL carries no state, which is the normal case for a fresh navigation.
62
+
63
+ Use `await appState.getLaunchState()` when your startup code can be asynchronous.
64
+ It resolves from the seeded URL on hosts that support it, so awaiting it does not delay first paint.
65
+
66
+ Treat launch state as untrusted input.
67
+ Anyone can edit a link before sharing it, so validate it exactly as you would a query parameter before using it to drive queries.
68
+
69
+ ## Write state as the user navigates
70
+
71
+ Choose between the two writers by asking who caused the change.
72
+
73
+ Use `setState()` when the *user* caused it, such as a click, a filter change, or opening a record.
74
+ It creates a history entry, so Back returns the user to where they were.
75
+
76
+ ```typescript
77
+ await appState.setState({ view: 'sales', region: 'AT' });
78
+ ```
79
+
80
+ Use `replaceState()` when the *app* caused it, or for high-frequency updates such as a slider drag where one history entry per update would make Back unusable.
81
+
82
+ ```typescript
83
+ await appState.replaceState({ view: 'sales', threshold: value });
84
+ ```
85
+
86
+ The whole object is replaced on every write.
87
+ There is no partial or namespaced update, so merge your state before writing it.
88
+
89
+ ## React to Back and Forward
90
+
91
+ Subscribe to observe changes your app did not initiate, such as the browser Back and Forward buttons or a deep link opened in the current tab.
92
+
93
+ ```typescript
94
+ const unsubscribe = appState.onStateChange((state) => {
95
+ restore(state ?? defaultView);
96
+ });
97
+
98
+ // On teardown
99
+ unsubscribe();
100
+ appState.dispose();
101
+ ```
102
+
103
+ The listener receives `undefined` when navigation reaches a URL that carries no state, which means your app should restore its own defaults.
104
+
105
+ ## Check support before showing a share button
106
+
107
+ ```typescript
108
+ const capabilities = await appState.isSupported();
109
+
110
+ if (capabilities) {
111
+ showShareButton();
112
+
113
+ // Some hosts can update the URL but cannot add history entries.
114
+ if (!capabilities.canPush) {
115
+ hideBackForwardHints();
116
+ }
117
+ }
118
+ ```
119
+
120
+ The host is authoritative.
121
+ It reports its own `maxEncodedBytes`, `maxDepth`, and `canPush`, and those values are the real contract.
122
+
123
+ ## Running standalone
124
+
125
+ Many apps ship standalone as well as embedded.
126
+ Standalone, your app owns its own address bar and there is no host to talk to, so deep-link state is unavailable by design.
127
+
128
+ The client is safe to construct either way.
129
+ It never throws at construction and never rewrites a URL it does not own.
130
+
131
+ Use the same `isSupported()` check to select your standalone path:
132
+
133
+ ```typescript
134
+ const capabilities = await appState.isSupported();
135
+
136
+ if (capabilities) {
137
+ await appState.setState({ view: 'sales', region: 'AT' });
138
+ } else {
139
+ router.push({ path: '/sales', query: { region: 'AT' } });
140
+ }
141
+ ```
142
+
143
+ Standalone, reads return `undefined` and writes reject with `NO_HOST_WINDOW`.
144
+ Reads degrade quietly so startup needs no branching, while writes fail loudly so a missing branch shows up during development.
145
+
146
+ ## Limits and rules
147
+
148
+ - State must be a plain JSON object. `Date`, `Map`, `Set`, class instances, functions, and `undefined` are rejected rather than silently degraded.
149
+ - State is capped at 4 KiB encoded and 20 levels deep, so shared links survive corporate proxies, mail gateways, and chat clients.
150
+ - For anything larger, store the data yourself and put an identifier in the state.
151
+ - The encoding is versioned but your payload is not, so a link shared before a shape change still arrives in the old shape and your app must tolerate it.
152
+
153
+ ## Security
154
+
155
+ State travels in a URL, so treat it accordingly.
156
+
157
+ **It is visible to the user.**
158
+ It appears in the address bar, browser history, bookmarks, screenshots, copied links, and corporate proxy logs.
159
+ Never put secrets, access tokens, or personal data in it.
160
+ When the state is sensitive, use an opaque identifier that maps to server-side data.
161
+
162
+ **It is untrusted input.**
163
+ Validate launch state before using it, and tolerate state written by a different version of your own app.
164
+
165
+ ## Troubleshooting
166
+
167
+ **`isSupported()` resolves to `undefined`.**
168
+ Either the app is not embedded in the Fabric portal, or deep linking has not reached this tenant yet.
169
+ Both are expected, and your app should fall back to its own routing.
170
+
171
+ **Writes reject with `STATE_TOO_LARGE`.**
172
+ The state exceeds the encoded budget the host reported.
173
+ Move the bulk of the data server-side and keep an identifier in the state.
174
+
175
+ **Writes reject with `INVALID_STATE`.**
176
+ The state contains something JSON cannot round-trip, such as a `Date`, a class instance, `undefined`, or a circular reference.
177
+ The error message names the offending path.
178
+
179
+ **Writes reject with `NO_HOST_WINDOW`.**
180
+ The app is not running embedded, so there is no portal to write to.
181
+
182
+ Errors are `FabricAppStateError` values with a stable `code`.
183
+ Branch on the code, never on the message.
184
+
185
+ ## Next steps
186
+
187
+ - [Fabric Brokered Auth](../auth/fabric.md) — How Fabric SSO authentication works.
188
+ - [Deploy to Microsoft Fabric](./deploy.md) — Detailed deployment commands and troubleshooting.
@@ -266,7 +266,7 @@ services:
266
266
 
267
267
  ### Static deploy exceeds size limit
268
268
 
269
- The compressed archive must not exceed 100 MB. Optimize your build output by excluding source maps and large development assets, or move binary files to Rayfin storage.
269
+ The compressed archive must not exceed 100 MB. Optimize your build output by excluding source maps and large development assets.
270
270
 
271
271
  ### GraphQL returns "Internal server error" after deploy
272
272
 
@@ -0,0 +1,101 @@
1
+ ---
2
+ sidebar_position: 46
3
+ ---
4
+
5
+ # Fabric Deployment Pipelines
6
+
7
+ Microsoft Fabric's **Deployment Pipelines** feature promotes an AppBackend between dev/test/prod-style workspace stages without rebuilding it — the same pre-built artifact moves forward as-is.
8
+ This page covers what that means for a Rayfin app and how to check whether yours is ready for it.
9
+
10
+ ## What actually moves
11
+
12
+ An AppBackend's exported definition is a portable package of pre-built artifacts, not source code:
13
+
14
+ ```text
15
+ MyAppBackend.AppBackend/
16
+ ├── .platform Fabric-managed item metadata
17
+ ├── rayfin.yml Services config (auth, data, storage, staticHosting) — promotes as-is
18
+ ├── dab-config.json Pre-generated DAB entity schema — promotes as-is (dialect-locked)
19
+ └── static-app.zip Compiled SPA bundle — promotes as-is (byte-identical across stages)
20
+ ```
21
+
22
+ ### `rayfin.config.json` — generated per stage, not promoted
23
+
24
+ When you deploy with `rayfin up`, the CLI writes a `rayfin.config.json` file into `StaticAssets/` alongside the compiled SPA, containing that stage's API URL, publishable key, and Fabric item metadata.
25
+ `resolveRayfinConfig()` reads this file automatically at runtime when it's present, and falls back to whatever `apiUrl`/`publishableKey` defaults you pass in — typically your `VITE_RAYFIN_*` env vars — when it's not, which is the local-dev case before you've ever run `rayfin up`.
26
+
27
+ This is exactly why `rayfin.config.json` is **deliberately excluded** from the exported definition above: Fabric regenerates it fresh in the target workspace on every Import, using that stage's own API URL, publishable key, and item metadata, so a stale Dev config can never leak into Prod as your app moves through the pipeline.
28
+
29
+ ## Read your backend config with `resolveRayfinConfig()`
30
+
31
+ Because the compiled SPA bundle moves unchanged between stages, resolve your backend URL and key through `resolveRayfinConfig()` before constructing `RayfinClient` / `RayfinServerClient`, rather than constructing the client from your env vars directly.
32
+ Your `VITE_RAYFIN_API_URL` / `VITE_RAYFIN_PUBLISHABLE_KEY` env vars still belong in your app — `rayfin dev` depends on them — just pass them into `resolveRayfinConfig()` as defaults instead of using them to build the client yourself:
33
+
34
+ ```ts
35
+ import { RayfinClient, resolveRayfinConfig } from '@microsoft/rayfin-client';
36
+
37
+ const resolved = await resolveRayfinConfig({
38
+ apiUrl: import.meta.env.VITE_RAYFIN_API_URL,
39
+ publishableKey: import.meta.env.VITE_RAYFIN_PUBLISHABLE_KEY,
40
+ });
41
+ const client = new RayfinClient<Schema>({ ...resolved, authStorage: true });
42
+ ```
43
+
44
+ `resolveRayfinConfig()` prefers `rayfin.config.json` when it's present — every deployed stage — and falls back to the `apiUrl`/`publishableKey` defaults you pass in only when the file is absent, which is the local-dev case. Constructing the client directly from build-time values, skipping `resolveRayfinConfig()`, bakes in whatever you pass and never checks for `rayfin.config.json`, so a promoted bundle would keep pointing at the source environment's backend.
45
+
46
+ For your backend URL and key alone, call `resolveRayfinConfig()`, not `loadRayfinConfig()` directly — `loadRayfinConfig()` is the lower-level primitive `resolveRayfinConfig()` calls internally to fetch `rayfin.config.json`, and `resolveRayfinConfig()` is the one that overlays the result over your defaults and returns what you should construct the client with.
47
+ If your app also needs Fabric coordinates for its auth broker, pass them alongside `apiUrl`/`publishableKey` in `resolveRayfinConfig()`'s defaults — see [Fabric auth coordinates](#fabric-auth-coordinates-need-clientruntimeconfig) below.
48
+
49
+ If you're not sure whether your app already resolves config before constructing the client, check for direct construction:
50
+
51
+ ```bash
52
+ grep -rn "new RayfinClient(\|new RayfinServerClient(" src/
53
+ ```
54
+
55
+ Any match there should be preceded by a `resolveRayfinConfig()` call, with its result spread into the constructor as shown above — your existing `VITE_RAYFIN_*` reads can stay, just pass them in as `resolveRayfinConfig()`'s defaults instead of using them to construct the client directly.
56
+
57
+ ## Fabric auth coordinates need `client.runtimeConfig`
58
+
59
+ If your app embeds in Fabric and drives its own auth flow against the Fabric secure-embed broker, `resolveRayfinConfig()`'s `apiUrl`/`publishableKey` alone aren't enough — your auth wiring also needs the Fabric coordinates (`workspaceId`, `itemId`, `portalUrl`) from the same `rayfin.config.json`.
60
+
61
+ `client.runtimeConfig` is a single `RayfinRuntimeConfig` bag holding every value `rayfin.config.json` can supply (`apiUrl`, `publishableKey`, `workspaceId`, `itemId`, `portalUrl`, `tenantId`) — all fields optional, since neither the remote config nor your defaults are guaranteed to provide every one. Pass your build-time `VITE_FABRIC_*` values into `resolveRayfinConfig()`'s defaults alongside `apiUrl`/`publishableKey`, and read the resolved coordinates off `client.runtimeConfig` after construction:
62
+
63
+ ```ts
64
+ const resolved = await resolveRayfinConfig({
65
+ apiUrl: import.meta.env.VITE_RAYFIN_API_URL,
66
+ publishableKey: import.meta.env.VITE_RAYFIN_PUBLISHABLE_KEY,
67
+ workspaceId: import.meta.env.VITE_FABRIC_WORKSPACE_ID,
68
+ itemId: import.meta.env.VITE_FABRIC_ITEM_ID,
69
+ portalUrl: import.meta.env.VITE_FABRIC_PORTAL_URL,
70
+ });
71
+ const client = new RayfinClient<Schema>({ ...resolved, authStorage: true });
72
+
73
+ const { workspaceId, itemId, portalUrl } = client.runtimeConfig ?? {};
74
+ ```
75
+
76
+ `resolveRayfinConfig()` resolves `runtimeConfig` from the same `rayfin.config.json` fetch it already makes for `apiUrl`/`publishableKey`, overlaying remote values over your defaults per-field — no extra request. `resolveFabricConfig()`, which made its own independent fetch for just the Fabric fields, has been removed as redundant — always resolve through `client.runtimeConfig` instead.
77
+
78
+ Reading `workspaceId`/`itemId`/`portalUrl` directly from `VITE_FABRIC_*` env vars — without going through `client.runtimeConfig` — bakes in whatever stage the bundle was *built* in.
79
+ Since deployment pipelines promote the same compiled bundle across stages without rebuilding, that mismatch only shows up after a promotion: your API calls correctly hit the new stage's backend (via `resolveRayfinConfig()`'s `rayfin.config.json` read), but your auth broker still points at the *old* stage's workspace/item, because it never read from the runtime config at all.
80
+
81
+ If you're not sure whether your app resolves Fabric coordinates this way, check for direct reads of `VITE_FABRIC_*` that don't go through `runtimeConfig`/`client.runtimeConfig`:
82
+
83
+ ```bash
84
+ grep -rn "VITE_FABRIC_" src/
85
+ ```
86
+
87
+ Any match not passed through `runtimeConfig`/`client.runtimeConfig` should be updated as shown above.
88
+
89
+ ## Out of scope today
90
+
91
+ - **Functions** (`rayfin/functions/`) — not covered by this pattern yet.
92
+ - **Secrets** — a separate concern from config-file promotion.
93
+ - **Source-code / Git integration** — Deployment Pipelines move built artifacts, not source. [Git integration](https://learn.microsoft.com/en-us/fabric/cicd/git-integration/intro-to-git-integration) is a related but distinct Fabric feature for source-controlling item definitions.
94
+
95
+ ## Related Fabric platform concepts
96
+
97
+ These are Fabric platform features, not something the Rayfin CLI/SDK controls — see Fabric's own [CI/CD documentation](https://learn.microsoft.com/en-us/fabric/cicd/) for depth:
98
+
99
+ - **Git integration** — links a workspace to a Git branch for source control of Fabric item definitions.
100
+ - **Deployment pipelines** — promotes items between dev/test/prod-style workspace stages; the feature this page covers.
101
+ - **Variable libraries** — Fabric-managed, stage-scoped values usable across items; not currently wired to `rayfin.config.json` generation, but conceptually related.
@@ -67,7 +67,6 @@ The endpoint exposes paths for each service:
67
67
  | --- | --- |
68
68
  | `/api/graphql` | Data API (GraphQL) — used by `RayfinClient` for CRUD operations |
69
69
  | `/auth` | Authentication service |
70
- | `/storage` | File storage |
71
70
 
72
71
  Your frontend application uses this endpoint via the `VITE_RAYFIN_API_URL` environment variable, which is generated into `.env.local` from `rayfin/.env` after deployment.
73
72
 
@@ -123,4 +122,5 @@ Learn more about [Workspace roles](https://learn.microsoft.com/fabric/fundamenta
123
122
  - [Pricing and capacity usage](./pricing.md) — Understand what consumes Fabric capacity and how billing works.
124
123
  - [Create a Fabric data app](../getting-started/create-rayfin-item.md) — Step-by-step guide to creating your first item in the Fabric portal.
125
124
  - [Deploy to Microsoft Fabric](./deploy.md) — Detailed deployment commands and troubleshooting.
125
+ - [Deep linking and shareable URLs](./deep-linking.md) — Let users share a link that reopens the exact view they were looking at.
126
126
  - [Fabric Brokered Auth](../auth/fabric.md) — How Fabric SSO authentication works.
@@ -10,7 +10,7 @@ npx rayfin connector add --type <type> --workspace-id <ws-id> --item-id <item-id
10
10
 
11
11
  `connector add` declares a connector in `rayfin/rayfin.yml` and scaffolds its supporting files.
12
12
 
13
- The CLI verifies the Fabric item, derives a connector `name` from the item's display name (override with `--name`), writes the entry to `rayfin.yml`, scaffolds `rayfin/connectors/<name>/schema.ts`, and runs schema discovery. `rayfin/connectors/<name>/metadata.json` is written so a subset of entities can be generated later.
13
+ The CLI verifies the Fabric item, derives a connector `name` from the item's display name (override with `--name`), writes the entry to `rayfin.yml`, and scaffolds `rayfin/connectors/<name>/schema.ts`. For Category A connectors it also runs schema discovery and writes `rayfin/connectors/<name>/metadata.json` so a subset of entities can be generated later.
14
14
 
15
15
  If you do not already know the workspace and item IDs, run [`connector search`](./search.md) first — its `--json` output includes a ready-to-run `addCommand` for each result.
16
16
 
@@ -58,15 +58,39 @@ Rules:
58
58
 
59
59
  ## Category B connectors
60
60
 
61
- For `kusto` and `fabric-semanticmodel`, `connector add` writes the `rayfin.yml` entry but there are no GraphQL entities to discover, so no entity files are generated and no row-level security applies:
61
+ For `kusto` and `fabric-semanticmodel`, `connector add` writes the `rayfin.yml` entry and generates a complete typed `schema.ts`, but there are no GraphQL entities to discover, so no entity files are generated and no row-level security applies:
62
62
 
63
- - `executeQuery` is the only allowed operation.
63
+ - `fabric-semanticmodel` allows `executeQuery`; `kusto` allows `executeQuery` and `executeCommand`. Check `rayfin connector types --json` for the current set.
64
64
  - `auth.type` must be `delegated`.
65
65
  - The connector is pinned to an adapter version.
66
66
  - There is no `metadata.json` entity list to generate from.
67
67
 
68
68
  After adding, exercise the connector with [`connector invoke`](./invoke.md) rather than writing entity code.
69
69
 
70
+ ## Installing the packages
71
+
72
+ `connector add` scaffolds files but installs nothing, and the generated `schema.ts` imports packages a fresh app does not declare.
73
+ On success the command prints the exact, version-pinned install to run:
74
+
75
+ ```text
76
+ 📦 Install the packages this connector needs:
77
+ npm install @microsoft/rayfin-connector-kusto@1.35.0-alpha
78
+ ```
79
+
80
+ Run it verbatim.
81
+ **Do not drop the version.** Connector packages ship in lockstep with the CLI, but their npm `latest` and `preview` tags lag the published release, so an unversioned install resolves to an older connector that hard-pins its own `@microsoft/rayfin-data` — leaving two Rayfin version lines in one app.
82
+
83
+ With `--json`, the same information is available under `install`:
84
+
85
+ ```json
86
+ {
87
+ "install": {
88
+ "packages": ["@microsoft/rayfin-connector-kusto@1.35.0-alpha"],
89
+ "command": "npm install @microsoft/rayfin-connector-kusto@1.35.0-alpha"
90
+ }
91
+ }
92
+ ```
93
+
70
94
  ## Related commands
71
95
 
72
96
  ```bash