@microsoft/rayfin-guide 1.35.0-beta.0 → 1.36.0-alpha.1588

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.
@@ -91,12 +91,56 @@ With `--json`, the same information is available under `install`:
91
91
  }
92
92
  ```
93
93
 
94
+ ## Re-adding an existing connector
95
+
96
+ Running `connector add` against a name already in `rayfin.yml` refreshes the declaration.
97
+ It prompts before overwriting, or requires `--yes` when non-interactive.
98
+
99
+ Your generated entity files under `rayfin/connectors/<name>/` are **preserved**.
100
+ Only `metadata.json` is rewritten, by schema discovery.
101
+ The hand-authored aggregate `schema.ts` is preserved too, unless you pass `--yes` — which opts in to overwriting it with a fresh placeholder.
102
+
103
+ To start from a clean directory instead, use [`connector remove`](#related-commands) first.
104
+
105
+ ## When schema discovery fails
106
+
107
+ Schema discovery is best-effort.
108
+ If it fails, the connector is still written to `rayfin.yml` and the command still exits `0` — you can retry discovery later by re-running `connector add`.
109
+
110
+ The failure is reported as a warning with a recovery hint, and under `--json` as a `schemaDiscovery` object:
111
+
112
+ ```json
113
+ {
114
+ "status": "success",
115
+ "action": "connector.add",
116
+ "schemaDiscovery": {
117
+ "status": "failed",
118
+ "reason": "login",
119
+ "error": "The data source rejected the login for connector \"inventory\". ...",
120
+ "recovery": "Confirm your account has read access to item <id> in workspace <id> — that is the usual cause. If it does, run `rayfin login` to refresh your session and retry."
121
+ }
122
+ }
123
+ ```
124
+
125
+ `schemaDiscovery` is present only for Category A connectors, which are the only ones that run discovery.
126
+ Top-level `status` stays `success` because the connector was added; check `schemaDiscovery.status` to branch on discovery specifically.
127
+
128
+ | `reason` | Meaning |
129
+ | --- | --- |
130
+ | `permission` | The data source explicitly denied the read. Someone must grant your account access. |
131
+ | `login` | The login was rejected without saying why. Usually missing access to the item; occasionally a stale CLI session. |
132
+ | `auth` | The control plane rejected your token. Run `rayfin login`. |
133
+ | `unknown` | Anything else — the raw driver message is in `error`. |
134
+
94
135
  ## Related commands
95
136
 
96
137
  ```bash
97
- npx rayfin connector list [--verbose] [--json]
138
+ npx rayfin connector list --json
139
+ npx rayfin connector list --verbose
98
140
 
99
- # Removes the rayfin.yml entry AND the rayfin/connectors/<name>/ directory.
100
- # Re-add after remove to refresh metadata, then regenerate entity files yourself.
141
+ # Removes the rayfin.yml entry AND the rayfin/connectors/<name>/ directory,
142
+ # including any entity files you generated.
101
143
  npx rayfin connector remove <name> [--yes]
102
144
  ```
145
+
146
+ `--json` and `--verbose` are mutually exclusive and cannot be combined.
@@ -4,672 +4,18 @@ sidebar_position: 8
4
4
 
5
5
  # Category A — GraphQL entity connectors
6
6
 
7
- Reference for `fabric-sqlanalytics` (Lakehouse SQL endpoint), `fabric-warehouse`, and `fabric-sqldatabase`.
8
- These types surface Fabric SQL as typed entities with read/create/update/delete operations and `@role` row-level security.
9
-
10
- For `kusto` and `fabric-semanticmodel`, read [Category B](./category-b-function-bridge.md) instead none of this page applies to them.
11
-
12
- This page picks up after the connector has been added and its `operations:` narrowed.
13
- Add and scope the connector first: `rayfin connector add --type <type> --workspace-id <ws> --item-id <item> [--operations read,update]`.
14
-
15
- ## Generate entity files you, not the CLI
16
-
17
- `rayfin connector add` writes `rayfin/connectors/<name>/metadata.json` and a placeholder `schema.ts`, then stops.
18
- The per-table entity `.ts` files are produced by **you** by reading `metadata.json` and following the [Entity generation contract](#entity-generation-contract).
19
- The CLI does not emit them.
20
-
21
- What to do, in order:
22
-
23
- 1. Read `rayfin/connectors/<name>/metadata.json` (shape: `SchemaMetadata` — see the [metadata.json reference](#metadatajson-reference)).
24
- 2. Pick the tables in scope.
25
- - **Full set** — every table under `schemas[].tables[]`.
26
- - **Subset** — only the tables the user named. Filter `schemas[].tables[]` by `tableName` before generating.
27
- 3. For each surviving table, write a file at `rayfin/connectors/<name>/<EntityName>.ts` following the [Entity generation contract](#entity-generation-contract) exactly.
28
- 4. Overwrite the placeholder `rayfin/connectors/<name>/schema.ts` with the [aggregate schema](#aggregate-connector-schema).
29
- 5. Surface every warning the contract emits (no-FK-metadata notes, unknown SQL types). Do not drop them silently.
30
-
31
- When the user asks for a refresh ("regenerate everything"), re-do the same flow against the existing `metadata.json`.
32
- Only re-run `rayfin connector remove` and `rayfin connector add` when the *source schema* changed and you need fresh metadata.
33
-
34
- ## Scope `@role(...)` on entities
35
-
36
- The YAML `operations:` controls which actions reach the source.
37
- The entity-level `@role(...)` decorator controls which roles can perform which actions on that entity.
38
-
39
- The entity's `actions` list **must be a subset** of the YAML `operations:` for that connector.
40
- The host's settings validator does not catch the mismatch today — DAB will fail at `rayfin up connector apply` time.
41
- Always narrow YAML first, then add `@role(...)` to entities.
42
-
43
- Example: a `fabric-warehouse` connector with `operations: [read, update]` and an `Order` entity locked to read-only:
44
-
45
- ```ts
46
- import { entity, int, text, decimal, Source } from '@microsoft/rayfin-core/experimental';
47
- import { role } from '@microsoft/rayfin-core';
48
-
49
- @role('authenticated', ['read'])
50
- @entity()
51
- export class Order extends Source({ schema: 'dbo', table: 'Order', primaryKey: ['orderId'] }) {
52
- @int({ column: 'OrderID' }) orderId!: number;
53
- @text() customerEmail!: string;
54
- @decimal({ precision: 18, scale: 2 }) total!: number;
55
- }
56
- ```
57
-
58
- Legal `actions`: `'read' | 'create' | 'update' | 'delete' | '*'`.
59
- Stack multiple `@role(...)` decorators to give different roles different actions on the same entity.
60
-
61
- ## Row-level policies
62
-
63
- Use a `policy` callback on `@role(...)` for row-level security.
64
- Policies use the typed `claims` / `item` DSL — never raw SQL strings.
65
-
66
- ```ts
67
- @role('authenticated', ['read', 'update'], {
68
- policy: (claims, item) => claims.sub.eq(item.owner_id),
69
- })
70
- @entity()
71
- export class Todo extends Source({ schema: 'dbo', table: 'Todo', primaryKey: ['id'] }) {
72
- @uuid() id!: string;
73
- @uuid() owner_id!: string;
74
- @text() body?: string;
75
- }
76
- ```
77
-
78
- DSL surface: `claims.sub | email | role`, `item.<columnName>`, `.eq(...)`, `.and(...)`, `.or(...)`.
79
- Use any claim the same way — for example `claims.email.eq(item.user_email)`.
80
- `RoleDeclarationOptions` also accepts `include` and `exclude` arrays for field-level allow/block lists.
81
-
82
- When to prompt the user: inspect `metadata.json` for columns named `owner_id`, `user_id`, `tenant_id`, `created_by`.
83
- If you see one, ask whether that column should restrict rows so each authenticated user only sees their own.
84
-
85
- ## Aggregate connector schema
86
-
87
- Install the connector packages first.
88
- `rayfin connector add` scaffolds files but installs nothing, and the aggregate `schema.ts` imports two packages a fresh app does not yet declare.
89
-
90
- `connector add` prints the exact command to run — copy it from that output, or rebuild it from the `packages` array in `rayfin connector types --json`, which carries both the package names and the version:
91
-
92
- ```bash
93
- # Shape only. Use the version connector add printed, not this one.
94
- npm install @microsoft/rayfin-connector-fabric-graphql@1.35.0-alpha @microsoft/rayfin-connectors@1.35.0-alpha
95
- ```
96
-
97
- **Always pin the version.** Connector packages ship in lockstep with the CLI, but their npm `latest` and `preview` tags lag behind.
98
- An unversioned `npm install` resolves to an older release that hard-pins its own `@microsoft/rayfin-data`, leaving two Rayfin version lines in one app.
99
-
100
- - `@microsoft/rayfin-connector-fabric-graphql` — provides `GraphQLBackedConnector`. Not a dependency of `@microsoft/rayfin-client`, so it is always missing on a fresh app.
101
- - `@microsoft/rayfin-connectors` — provides `ConnectorConfig`. It ships transitively under `@microsoft/rayfin-client`, but declare it directly so strict resolvers (pnpm) do not treat it as a phantom dependency.
102
-
103
- Even though both imports are `import type`, TypeScript still needs the packages present at compile time.
104
-
105
- The aggregate `rayfin/connectors/<name>/schema.ts` is what the app imports from.
106
- The CLI leaves a placeholder there; overwrite it so it exports **three** things:
107
-
108
- 1. **Entity re-exports** — re-export every entity class you generated for this connector, **as types** (`export type { ... }`).
109
- 2. **`<Name>Schema` (type)** — a `GraphQLBackedConnector<TSchema, typeof connectorConfig>` marker.
110
- `TSchema` maps each in-scope entity name to its class via `typeof`; the second argument is `typeof connectorConfig`, from which the marker derives both the CRUD operation union and the dialect.
111
- This is what makes `client.connectors.<name>.<Entity>.select(...)` strongly typed and gates CRUD methods to the declared operations at compile time.
112
- 3. **`connectorConfig` (value)** — a `ConnectorConfig` carrying the runtime `connector` type, the same `operations` allow-list, and an **`entities` map** keyed exactly as in `TSchema` and covering every entity there.
113
- The runtime routes on `connector`, gates CRUD verbs by `operations`, and uses `entities` as the default selection so a read or write without an explicit `select` returns the full row.
114
- Give the entries as **string arrays** of entity property names, not entity classes — see below.
115
- Only the class form carries relationship cardinality, so a relationship `select` against the array form throws `ENTITIES_REQUIRED_FOR_RELATIONSHIP_SELECT`.
116
-
117
- `GraphQLBackedConnector<TSchema, typeof connectorConfig>` **is** the published typed marker for Category A connectors.
118
- Never invent per-type names like `FabricWarehouse` or `FabricSqlAnalytics` — they do not exist.
119
-
120
- ### Import entities as types, never as values
121
-
122
- This file is imported by browser code, because the app client reads `connectorConfig` from it.
123
- A value import pulls the decorated entity classes into the browser bundle with it.
124
-
125
- That breaks the app. `vite build` still exits 0, type-check passes, lint passes, and deploy succeeds, but the bundler lowers the decorators into an invalid class expression (`var Order = @Source({...})`), the emitted bundle fails to parse, and the page renders blank with `Uncaught SyntaxError: Invalid or unexpected token`.
126
- Entity classes are build-time declarations consumed by DAB config generation. They have no business in a browser bundle.
127
-
128
- Every entity reference in this file is a `typeof` lookup, which is pure type information, so `import type` and `export type` are sufficient:
129
-
130
- ```ts
131
- import type { Order } from './Order.js'; // correct
132
- import { Order } from './Order.js'; // wrong — ships the decorated class
133
- ```
134
-
135
- The same applies to the re-exports. Use `export type { Order }`, not `export { Order }`.
136
-
137
- This is also why `entities` takes column names rather than classes.
138
-
139
- ### Single source of truth for operations
140
-
141
- For one connector, the `connectorConfig.operations` array and the YAML `operations:` must describe the same verb set — that pair is the connector-wide ceiling.
142
- Each entity's `@role(...)` actions stay a **subset** of it, so a read-only entity keeps `['read']` on a read/update connector.
143
- Narrow YAML first, mirror it into `connectorConfig.operations`, then grant each entity only the verbs it actually needs.
144
- Never widen a decorator to match the connector's full verb set.
145
-
146
- ### Subset rule
147
-
148
- In subset mode, include in `TSchema` only the entities you actually generated.
149
- Never reference an entity class you did not generate — the `typeof` lookup and its import would dangle.
150
-
151
- ```ts
152
- // rayfin/connectors/inventory/schema.ts
153
- import type { GraphQLBackedConnector } from '@microsoft/rayfin-connector-fabric-graphql';
154
- import type { ConnectorConfig } from '@microsoft/rayfin-connectors';
155
-
156
- import type { Order } from './Order.js';
157
- import type { Customer } from './Customer.js';
158
-
159
- export type { Order } from './Order.js';
160
- export type { Customer } from './Customer.js';
161
-
162
- // Use `as const satisfies` (not a `: ConnectorConfig` annotation) so the
163
- // `connector` and `operations` literals survive — the marker reads them to
164
- // derive the permitted operations and the dialect.
165
- export const connectorConfig = {
166
- connector: 'fabric-warehouse',
167
- operations: ['read', 'update'],
168
- entities: {
169
- Order: ['orderId', 'customerId', 'total', 'placedUtc'],
170
- Customer: ['customerId', 'email'],
171
- },
172
- } as const satisfies ConnectorConfig;
173
-
174
- export type InventorySchema = GraphQLBackedConnector<
175
- { Order: typeof Order; Customer: typeof Customer },
176
- typeof connectorConfig
177
- >;
178
- ```
179
-
180
- The pattern scales to any number of entities under any names — add one `import type` line, one `export type` line, and one `TSchema` key per entity.
181
-
182
- The `<Name>Schema` name is the PascalCase connector name plus `Schema` (connector `inventory` → `InventorySchema`).
183
- For a read-only Lakehouse (`fabric-sqlanalytics`), `operations` is `['read']`.
184
-
185
- ### The `entities` map
186
-
187
- `connectorConfig.entities` gives the runtime each entity's field names, and it is what makes a call without an explicit `select` work:
188
-
189
- - Reads — `findMany(filter?)`, `findFirst(filter?)` and `findByKey` use it as the default selection to return the full row.
190
- - Writes on `fabric-sqldatabase` — `create` / `update` / `delete` use it to read the row back, including server-generated columns the caller never sent.
191
-
192
- **Omitting it is not optional.** Without it, every no-selection read throws `SELECTION_REQUIRED`, on every dialect.
193
-
194
- Give the names as **string arrays**, and list every entity in `TSchema`.
195
- The map also accepts entity classes, but do not use that form here: it puts the decorated classes back in the browser bundle and reintroduces the blank page above.
196
-
197
- **Use the entity's property names, not the database column names.**
198
- These are the names on the generated class — the same keys that appear in the row type — not the `columnName` values in `metadata.json`.
199
- For `@uuid({ column: 'ProductID' }) productId!: string`, the entry is `productId`.
200
- The one exception is a field that declares `graphqlName`, in which case use that name.
201
- Include every scalar field and leave relationship fields out, since those need nested sub-selections.
202
-
203
- **Relationship selects need the class form.**
204
- Cardinality (`@one` versus `@many`) lives in the decorator metadata, which a name array cannot carry, so selecting a relationship path against the array form throws `ENTITIES_REQUIRED_FOR_RELATIONSHIP_SELECT`.
205
- Scalar reads and writes are unaffected.
206
- If an app needs relationship selects in browser code, select the foreign key scalar and fetch the related entity separately, or move that query server-side where the class can be imported.
207
-
208
- ## Wire connectors into the app client
209
-
210
- Declaring and deploying a connector does not make it callable from app code.
211
- Expose it through a `ConnectorsRayfinClient` so it shows up as `client.connectors.<name>`.
212
-
213
- `ConnectorsRayfinClient` is experimental — import it only from the `@microsoft/rayfin-client/experimental` subpath, never the stable `@microsoft/rayfin-client` entry.
214
-
215
- The connector key must be identical in three places: the `name` in `rayfin.yml`, the property in `AppConnectorsSchema`, and the property in the `connectors` option.
216
-
217
- ```ts
218
- import { ConnectorsRayfinClient } from '@microsoft/rayfin-client/experimental';
219
- import { ProductDbSchema, connectorConfig as productDbConfig } from '../../rayfin/connectors/productDb/schema';
220
- import { InventorySchema, connectorConfig as inventoryConfig } from '../../rayfin/connectors/inventory/schema';
221
-
222
- type AppConnectorsSchema = {
223
- productDb: ProductDbSchema;
224
- inventory: InventorySchema;
225
- };
226
-
227
- export function getRayfinClient() {
228
- // Type params are <DataSchema, FunctionsSchema, ConnectorsSchema> — set the
229
- // unused slots to Record<string, never>, or pass real schema types if the app
230
- // also has a data model / functions.
231
- return new ConnectorsRayfinClient<Record<string, never>, Record<string, never>, AppConnectorsSchema>({
232
- baseUrl: import.meta.env.VITE_RAYFIN_API_URL,
233
- publishableKey: import.meta.env.VITE_RAYFIN_PUBLISHABLE_KEY,
234
- authStorage: true,
235
- connectors: {
236
- productDb: productDbConfig,
237
- inventory: inventoryConfig,
238
- },
239
- });
240
- }
241
- ```
242
-
243
- Verify by type-checking the project (`tsc --noEmit` or the app build).
244
- A mismatch between a connector key in `AppConnectorsSchema` and the `connectors` option surfaces there.
245
-
246
- ## Reading and writing connector entities
247
-
248
- Each entity is reached through `client.connectors.<name>.<Entity>`.
249
- What the surface exposes depends on the connector's `operations:` (which gate the verbs) and the connector type/dialect (which shapes what writes return).
250
-
251
- ### Reads
252
-
253
- Reads work identically on Lakehouse, Warehouse, and SQL Database — only writes differ.
254
-
255
- ```ts
256
- // Query chain: select -> where -> orderBy -> execute
257
- const orders = await client.connectors.inventory.Order
258
- .select(['orderId', 'customerEmail', 'total'])
259
- .where({ total: { gt: 100 } })
260
- .orderBy({ total: 'desc' })
261
- .execute();
262
-
263
- // By-key read. Pass a key OBJECT (all parts of the composite PK) AND a
264
- // required scalar-only `select`. The result is Pick<Row, selected> | null.
265
- const order = await client.connectors.inventory.Order.findByKey(
266
- { orderId: 'o-1' },
267
- ['orderId', 'customerEmail', 'total'],
268
- );
269
-
270
- const lineItem = await client.connectors.sales.OrderItem.findByKey(
271
- { orderId: 'o-1', productId: 'p-9' }, // both parts of the composite PK required
272
- ['orderId', 'productId', 'quantity'],
273
- );
274
- ```
275
-
276
- `findByKey` takes the key object and a **required** `select` of scalar columns, returning `Pick<Row, selected> | null`.
277
- Relationships cannot be selected via `findByKey` — read them through the query chain.
278
- A composite key requires all its parts in the key object; omitting one is a compile error.
279
-
280
- ### Reading related columns
281
-
282
- `select` can pull columns from a related entity — a `@one` (forward FK) or `@many` (reverse FK) navigation field — by naming them as a **dotted path**.
283
- The builder expands each path into the nested selection the server expects and unwraps the response so the related rows sit inline on the result.
284
-
285
- ```ts
286
- const products = await client.connectors.inventory.Product
287
- .select(['name', 'category.name', 'orderItems.quantity'])
288
- .where({ stock: { gt: 0 } })
289
- .execute();
290
-
291
- products[0].category.name; // @one — related row inline
292
- products[0].orderItems[0].quantity; // @many — related rows inline (unwrapped)
293
-
294
- // Paths nest to arbitrary depth, hopping across entities.
295
- await client.connectors.inventory.Product
296
- .select(['name', 'orderItems.order.customerEmail'])
297
- .execute();
298
- ```
299
-
300
- - Name related columns as a dotted path (`category.name`), never the bare relationship (`category`) — a navigation field is not a selectable leaf and is a compile error.
301
- - Each path is validated segment-by-segment against the schema, so a wrong hop (`category.nope`) fails to compile; depth is unbounded.
302
- - `findByKey` cannot select relationships — it is scalar-only.
303
-
304
- Self-referencing foreign keys generate an entity and a DAB relationship, but the client cannot query across one.
305
- Do not select a dotted path over a self-relationship.
306
-
307
- ### Writes
308
-
309
- `create` / `update` / `delete` exist only when the connector's `operations:` and the entity `@role(...)` allow them, and what they return is fixed by the connector's dialect:
310
-
311
- | Connector type | Dialect | `create`/`update`/`delete` return | Notes |
312
- | --- | --- | --- | --- |
313
- | `fabric-sqlanalytics` (Lakehouse) | — | **not available** | Read-only at the host; only `select`/`findByKey`/query chain exist. Writes are a compile error. |
314
- | `fabric-sqldatabase` (SQL Database) | row-returning | the **full entity row** | Reads the whole row back after the write — every column, including server-generated ones the caller did not send. Needs the [`entities` map](#the-entities-map). |
315
- | `fabric-warehouse` (Warehouse) | no `OUTPUT` clause | `DbOperationResult { result: string }` | DWSQL cannot read the row back, so mutations resolve to a status object. On success `result` is `"success"`; a failed write throws a GraphQL error. |
316
-
317
- ```ts
318
- // SQL Database — mutation returns the full row, with every server-generated
319
- // column filled in by the database.
320
- const created = await client.connectors.orders.Order.create({
321
- quantity: 3, // supply only the columns you own…
322
- unitPrice: 19.99,
323
- });
324
- created.id; // server-assigned identity — returned, though never sent
325
- created.createdUtc; // server default (SYSUTCDATETIME()) — returned
326
- created.lineTotal; // computed (quantity * unitPrice) — returned
327
-
328
- // Warehouse — mutation returns DbOperationResult, NOT the row
329
- const result = await client.connectors.inventory.Order.update(
330
- { orderId: 'o-1' }, // key object (all composite parts)
331
- { total: 250 }, // partial update
332
- );
333
- result.result; // "success" — status string, not the row
334
-
335
- // Delete by key (both dialects) — full composite key required
336
- await client.connectors.sales.OrderItem.delete({ orderId: 'o-1', productId: 'p-9' });
337
-
338
- // Lakehouse — writes do not exist
339
- client.connectors.analytics.Sales.create({ /* ... */ }); // compile error: read-only
340
- ```
341
-
342
- **Server-generated columns.**
343
- These are the columns marked `AutoGenerated<T>` on the entity: `IDENTITY`, any `DEFAULT`, and computed (`AS (...)`) columns.
344
- They are optional on `create` / `update` and come back populated in the returned row.
345
- Most cannot be written: passing an `IDENTITY` or computed value is rejected by the database, and rowversion / temporal columns are server-maintained.
346
- The one exception is a plain `DEFAULT` column — omit it to get the default, or pass a value to override it.
347
-
348
- By-key `update`/`delete` take the same full key object as `findByKey`.
349
- A keyless entity (`primaryKey` omitted or `[]`) exposes no `findByKey` / `update` / `delete` at all — it is read-only regardless of the connector type.
350
-
351
- ### Quick decision guide
352
-
353
- - **Lakehouse (`fabric-sqlanalytics`)** — reads only. Model entities, `select`, `findByKey`, query chain. No `@role` write actions.
354
- - **SQL Database (`fabric-sqldatabase`)** — full CRUD; mutations hand back the full row, so you can render the created/updated record directly.
355
- - **Warehouse (`fabric-warehouse`)** — full CRUD; mutations hand back a `DbOperationResult`, so re-query with `findByKey`/`select` if you need the persisted values.
356
-
357
- For aggregation, `groupBy`, or year/quarter/month time-bucketing, search the docs (`rayfin docs search`).
358
-
359
- ## Worked example — a use case on specific entities
360
-
361
- The user already ran `rayfin connector add` for a `fabric-warehouse` connector named `sales` and says *"I just need users to read and update orders and their line items."*
362
-
363
- - **Scope and narrow.** Operations are `read`, `update`; set `operations:` on the `sales` entry to just those.
364
- - **Generate the subset.** Filter `metadata.json` to `Order` and `OrderItem`, write both per the contract. Drop any `@one(() => Customer, ...)` to an out-of-scope table (no dangling import).
365
- - **Roles and aggregate.** `@role('authenticated', ['read', 'update'])` on both; aggregate `SalesSchema = GraphQLBackedConnector<{ Order: typeof Order; OrderItem: typeof OrderItem }, typeof connectorConfig>`, with both entities imported and re-exported as **types**, and `connectorConfig` declared `as const satisfies ConnectorConfig` with `operations: ['read', 'update']`.
366
- - **Wire and verify.** Add `sales` to `AppConnectorsSchema` and the `connectors` option. Type-check: `client.connectors.sales.Order.create(...)` is now a compile error — the proof the scope took effect. Deploy with `rayfin up`.
367
-
368
- ## Entity generation contract
369
-
370
- When you generate `rayfin/connectors/<name>/<EntityName>.ts` from `metadata.json`, follow these rules end-to-end.
371
- `metadata.json` is the only source of truth for physical keys and relationships — never infer either from column names, values, naming conventions, or table shape.
372
- The CLI itself does not emit entity files.
373
-
374
- ### 1. File name and class name
375
-
376
- - `className = pascalCase(table.tableName)` (for example `product_category` → `ProductCategory`).
377
- - **Pluralization is allowed, but must be idempotent — never double-pluralize.**
378
- Pluralizing a singular table name is fine (`Order` → `Orders`, `Category` → `Categories`).
379
- But first check whether the name is already plural: if the source table is already plural (`Orders`, `Categories`, `sales_line_items`), keep it exactly as-is — do not add another plural suffix.
380
- Whatever name you settle on, the `@entity` name and the `client.connectors.<name>.<Entity>` access path must stay consistent with it.
381
- - File name is `<className>.ts`. One file per table; no nesting.
382
- - **Cross-connector uniqueness — disambiguate only on collision.**
383
- GraphQL type names are global across every connector in the app: two connectors that each produce an entity with the same name collide at `rayfin up connector apply` time even when they point at different physical tables.
384
- Before finalizing a name, scan every other `rayfin/connectors/*/` directory and the entities you have already generated for the current connector for a matching class / `@entity` name.
385
- If — and only if — the base name is already taken, prefix it with the connector's source database name (PascalCased, from `metadata.json` `source`): `Product` → `SalesDbProduct`.
386
- Disambiguation is required even when the colliding entities refer to the same physical source table.
387
- If that database name is itself shared across the colliding connectors, fall back to the PascalCased connector name (the `rayfin.yml` `name`, which the host guarantees unique).
388
- Never rename a name that does not collide, and never blanket-prefix every entity.
389
- When you rename an entity, use the disambiguated name everywhere: the class name, the file name, the `@entity` name, the `TSchema` key and re-export, and the `client.connectors.<name>.<Entity>` access path.
390
- Surface a one-line note to the user for each rename.
391
-
392
- ### 2. Primary keys from metadata
393
-
394
- When `table.primaryKeyColumns` is present and non-empty, declare exactly those columns on `Source(...)`, using each column's TypeScript property name and preserving `table.primaryKeyColumns` (`ORDINAL_POSITION`) order.
395
- This rule applies to single-column and composite keys.
396
- Keep one property per key column and preserve each SQL column name with `column:` when it differs.
397
-
398
- ```ts
399
- export class OrderItem extends Source({
400
- schema: 'dbo',
401
- table: 'OrderItem',
402
- primaryKey: ['orderId', 'productId'],
403
- }) {
404
- @uuid({ column: 'OrderID' }) orderId!: string;
405
- @uuid({ column: 'ProductID' }) productId!: string;
406
- @int() quantity!: number;
407
- }
408
- ```
409
-
410
- Every key part is then required in the by-key methods (`findByKey`/`update`/`delete`).
411
-
412
- When `primaryKeyColumns` is absent or empty, emit `primaryKey: []`.
413
- The entity is keyless and exposes no `findByKey`/`update`/`delete` methods.
414
- Do not choose a column as a synthetic key, including columns named `id`, columns that appear unique in sampled data, or the first non-nullable column.
415
-
416
- Lakehouse SQL endpoints commonly omit PK metadata.
417
- For `fabric-sqlanalytics`, this means generating a read-only, keyless entity unless `primaryKeyColumns` is actually present in `metadata.json`.
418
- Never add a logical PK on the agent's own initiative; a Builder may deliberately declare one later when they know the source's logical uniqueness contract.
419
-
420
- ### 3. Key-field validation
421
-
422
- - Resolve every `primaryKeyColumns` entry against `table.columns` by exact SQL column name before converting it to a TypeScript property name.
423
- - Preserve the metadata order for composite keys.
424
- - A key column must be non-nullable. If metadata names a nullable or missing column, do not guess a replacement; stop generation for that table and report the inconsistency.
425
- - An empty table or a table without PK metadata is keyless.
426
-
427
- The key's property name and datatype come from the matching column metadata; the PK is not renamed to `id`.
428
- Preserve the on-disk SQL column name via the `column:` option.
429
-
430
- ### 4. SQL type to decorator mapping
431
-
432
- Look up `column.dataType` (case-insensitive) in this table:
433
-
434
- | SQL type family | Decorator | TS type |
435
- | --- | --- | --- |
436
- | `int`, `bigint`, `smallint`, `tinyint` | `@int()` | `number` |
437
- | `decimal`, `numeric`, `money`, `smallmoney`, `float`, `real` | `@decimal({ precision, scale })` | `number` |
438
- | `bit` | `@boolean()` | `boolean` |
439
- | `date`, `datetime`, `datetime2`, `smalldatetime`, `datetimeoffset`, `time` | `@date()` | `Date` |
440
- | `uniqueidentifier` | `@uuid()` | `string` |
441
- | `varchar`, `nvarchar`, `char`, `nchar`, `text`, `ntext` | `@text()` | `string` |
442
- | Anything else (`geography`, `hierarchyid`, `xml`, vector) | `@text()` + warning | `string` |
443
-
444
- For the fallback case, emit a warning: `Unknown SQL type <dataType> for <table>.<column>; falling back to @text().`
445
-
446
- ### 4a. Server-generated columns
447
-
448
- `metadata.json` flags the columns the database fills in.
449
- On each column, check for these server-generation markers:
450
-
451
- - `identity` — an `IDENTITY(seed, increment)` key.
452
- - `default` — a column `DEFAULT` (for example `newid()`, `sysutcdatetime()`, `NEXT VALUE FOR <seq>`).
453
- - `computed` — a computed column (`AS (<expr>)`).
454
- - `serverManaged` — a column the server maintains with no user-facing expression: `'rowversion'`, `'temporalRowStart'`, or `'temporalRowEnd'`.
455
-
456
- If any of these is present, the column is server-generated: wrap its TS type from section 4 in `AutoGenerated<…>`.
457
- The decorator and its `column:` option are unchanged; do not emit a `default:` option — the marker is type-only and the connector never writes these columns.
458
-
459
- `AutoGenerated<T>` makes the column optional on create/update input and read back as plain `T` in the returned row.
460
- Nullability is unchanged: a non-null server-generated column still uses `!:`; a nullable one uses `?:`.
461
-
462
- Optional on input does not mean "accepts a value".
463
- For most server-generated columns, passing a value is rejected by the database:
464
-
465
- - `identity` — inserting an explicit value fails. Never send it.
466
- - `computed` — a computed column cannot be written; setting it is a server error.
467
- - `serverManaged` — server-maintained; writing is rejected.
468
- - plain `default` — the one exception: omit it to get the default, or pass a value to override it.
469
-
470
- ```ts
471
- @int({ column: 'Id' })
472
- id!: AutoGenerated<number>; // IDENTITY — omit on write, server assigns
473
-
474
- @uuid({ column: 'PublicId' })
475
- publicId!: AutoGenerated<string>; // DEFAULT newid()
476
-
477
- @decimal({ optional: true, column: 'LineTotal', precision: 28, scale: 2 })
478
- lineTotal?: AutoGenerated<number>; // computed: AS ([Quantity] * [UnitPrice])
479
-
480
- @text({ column: 'RowVer' })
481
- rowVer!: AutoGenerated<string>; // rowversion (serverManaged) — read-back-only
482
- ```
483
-
484
- ### 5. Field-level decorator options
485
-
486
- For every column, the decorator option object is built in this order (omit keys you do not need):
487
-
488
- 1. `optional: true` — if `column.isNullable` is true.
489
- 2. `column: '<columnName>'` — when the SQL column name differs from the TS property name. Single-quote the value; escape embedded `'`.
490
- 3. **Text only:** `max: <maxLength>` — when the decorator is `text` and `column.maxLength > 0`.
491
- 4. **Decimal only:** `precision: <p>, scale: <s>` — when the decorator is `decimal` and both are present in metadata.
492
- 5. **Integer:** do not emit `min`/`max` based on SQL precision; those are value bounds, not storage capacity. Plain `@int()` is correct.
493
-
494
- If the option object is empty, emit `@text()` rather than `@text({})`.
495
-
496
- Property name is always `camelCase(column.columnName)` — the PK is not special-cased or renamed to `id`.
497
- Nullable columns use `?:`; non-nullable use `!:`.
498
-
499
- ### 5a. Server-generated columns have no decorator option
500
-
501
- `metadata.json` carries `column.identity`, `column.default`, and `column.computed`, but no field decorator has an option to mark a column as server-generated.
502
- Do not invent one.
503
-
504
- Declare the column normally — those markers change nothing about the emitted decorator, beyond the `AutoGenerated<T>` type wrapper in section 4a.
505
- What they do change is what you tell the Builder: surface a one-line note for each such column (for example, `Column '<table>.<column>' is server-generated (identity); the source will populate it, so omit it on create()`).
506
-
507
- An identity or computed column that is also a declared key follows the normal [primary key rules](#2-primary-keys-from-metadata) unchanged — being server-generated never exempts it from key-field validation.
508
-
509
- ### 6. Global-type shadow rule
510
-
511
- If `pascalCase(table.tableName)` equals a column's TS type (for example a table named `Date` with a `datetime2` column), the unqualified `Date` in the annotation resolves to the entity class, not the global.
512
- Render the type as `globalThis.Date` in that one field annotation only.
513
-
514
- ### 7. Relationships
515
-
516
- Forward (`@one`) and reverse (`@many`) relationships are emitted from foreign-key metadata **only**.
517
- Do not infer Lakehouse relationships from matching column names, star-schema naming, or sampled values.
518
-
519
- Schema discovery records an FK on the referencing table only, so `table.foreignKeys` governs **forward** relationships alone.
520
- When it is absent or empty, emit no `@one` — but still emit every `@many` the reverse-FK index below produces, because a parent table legitimately has no FKs of its own while being the target of another table's.
521
- A table with neither its own FKs nor any incoming constraint gets no relationship decorators at all.
522
-
523
- Before generating relationships, group `table.foreignKeys` entries by `constraintName`.
524
- One group is one FK relationship; rows in the same group are the ordered column pairs of a composite FK.
525
- Preserve their metadata order.
526
-
527
- **`@one` — one per FK constraint on this table.** For each FK constraint group:
528
-
529
- - All rows must reference the same schema and table. If they do not, report inconsistent metadata and skip the constraint.
530
- - `fieldName = camelCase(singularize(referencedTableName))`. If more than one constraint on the table would produce the same field name, derive a stable disambiguated name from `constraintName` rather than overwriting one.
531
- - `sourceFields = group.map(fk => camelCase(fk.columnName))`.
532
- - `targetFields = group.map(fk => camelCase(fk.referencedColumnName))`.
533
- - The relationship is optional when any source column in the group is nullable. Emit `{ optional: true }` and `?:` in that case.
534
-
535
- ```ts
536
- @one(() => <ReferencedClass>, { sourceFields: ['<src1>', '<src2>'], targetFields: ['<tgt1>', '<tgt2>'] })
537
- <fieldName>!: <ReferencedClass>;
538
-
539
- // Optional relationship
540
- @one(() => <ReferencedClass>, { optional: true, sourceFields: ['<src>'], targetFields: ['<tgt>'] })
541
- <fieldName>?: <ReferencedClass>;
542
- ```
543
-
544
- Self-referencing FK constraints follow the same rules and must be optional.
545
- Use the current class directly in the resolver and add no sibling import.
546
- The entity and DAB relationship generate fine, but the client does not currently support selecting a dotted path across a self-relationship.
547
-
548
- If the referenced table is not in the table index, skip the relationship and warn: `Foreign key <table>.<column> references unknown table <refSchema>.<refTable>; relationship skipped.`
549
-
550
- In subset mode, apply the same skip when the referenced table exists in `metadata.json` but is not in the set you are generating: omit the `@one` and its sibling import, and warn that the relationship was dropped because the target is out of scope.
551
- Never import a `./<Class>.js` file you did not write.
552
-
553
- **`@many` — reverse-direction relationships pointing at this table.**
554
- Build a reverse-FK index across all tables: every grouped FK constraint from another table to this table becomes one `@many` on this table.
555
-
556
- - `fieldName = camelCase(pluralize(otherTable.tableName))` — `pluralize` is idempotent, so an already-plural table (`Orders`) stays `orders`, never `orderses`.
557
- - `sourceFields = group.map(fk => camelCase(fk.referencedColumnName))` — this table's referenced properties.
558
- - `targetFields = group.map(fk => camelCase(fk.columnName))` — the other table's FK properties.
559
-
560
- ```ts
561
- @many(() => <OtherClass>, { sourceFields: ['<src>'], targetFields: ['<tgt>'] })
562
- <fieldName>!: <OtherClass>[];
563
- ```
564
-
565
- In subset mode, only emit a `@many` when the other table is also being generated.
566
-
567
- `singularize` / `pluralize` are the same simplified rules the generator uses:
568
-
569
- - `singularize`: `ies → y` (length > 3); `xes|ses|ches|shes → drop -es`; `<non-s>s → drop trailing -s`; else unchanged.
570
- - `pluralize` (idempotent): if the name is already plural (ends in `s`, `es`, or `ies`), return it unchanged; otherwise `<non-vowel>y → -ies`; `x|z|ch|sh → +es`; else `+s`.
571
-
572
- ### 8. Missing key and relationship metadata
573
-
574
- When `primaryKeyColumns` is absent or empty, emit one warning to the user: `No PK metadata available for <tableName>; generated as a keyless entity.`
575
-
576
- When `foreignKeys` is absent or empty and no reverse FK constraint points at the table, emit one warning: `No FK metadata available for <tableName>; relationships omitted.`
577
-
578
- For Lakehouse, these warnings describe a known metadata limitation, not a request to infer schema.
579
- The entity file still generates with `primaryKey: []` and without `@one`/`@many`.
580
- If the Builder knows logical keys or relationships the endpoint does not expose, explain that they may add those declarations manually; never add them without that explicit input.
581
-
582
- ### 9. Imports
583
-
584
- Build the import line deterministically:
585
-
586
- - Always include `entity` and `Source`.
587
- - Then append, in this exact order, any decorator names actually used in the file: `boolean`, `date`, `decimal`, `int`, `text`, `uuid`, `one`, `many`. Skip any not used.
588
- - All from `@microsoft/rayfin-core/experimental`.
589
- - If any column is server-generated, also add `import type { AutoGenerated } from '@microsoft/rayfin-core/experimental';`.
590
- - For every relationship target that is a different class, add a sibling import: `import { <SiblingClass> } from './<SiblingClass>.js';`. Sibling imports are alphabetised; self-references get no sibling import.
591
- - In subset mode, only relationships that survived the subset skip contribute sibling imports.
592
-
593
- ### 10. Canonical example
594
-
595
- ```ts
596
- // @generated — do not edit.
597
-
598
- import { entity, uuid, text, int, date, one, many, Source } from '@microsoft/rayfin-core/experimental';
599
- import type { AutoGenerated } from '@microsoft/rayfin-core/experimental';
600
- import { Category } from './Category.js';
601
- import { OrderItem } from './OrderItem.js';
602
-
603
- @entity()
604
- export class Product extends Source({ schema: 'dbo', table: 'Product', primaryKey: ['productId'] }) {
605
- @uuid({ column: 'ProductID' })
606
- productId!: string;
607
-
608
- @text()
609
- name!: string;
610
-
611
- @int()
612
- stock!: number;
613
-
614
- // DEFAULT sysutcdatetime() — server-generated, so AutoGenerated: optional on
615
- // write, read back in the returned row.
616
- @date({ column: 'CreatedUtc' })
617
- createdUtc!: AutoGenerated<Date>;
618
-
619
- @one(() => Category, { sourceFields: ['categoryId'], targetFields: ['categoryId'] })
620
- category!: Category;
621
-
622
- @many(() => OrderItem, { sourceFields: ['productId'], targetFields: ['productId'] })
623
- orderItems!: OrderItem[];
624
- }
625
- ```
626
-
627
- ## metadata.json reference
628
-
629
- Path: `rayfin/connectors/<name>/metadata.json`.
630
- Written by `connector add`; never edit by hand.
631
- It is regenerated only by `rayfin connector remove <name>` followed by `rayfin connector add ...`.
632
-
633
- Top-level: `SchemaMetadata { source, connector, connectionString, discoveredAt, schemas[] }`.
634
- Each schema entry: `{ schemaName, tables[] }`.
635
- Each table: `{ tableName, columns[], foreignKeys?, primaryKeyColumns? }`.
636
-
637
- Columns carry `columnName`, `dataType`, `isNullable`, and optional `maxLength` / `precision` / `scale`.
638
- Server-generated columns additionally carry `identity` (`{ seed, increment }`), `default` (the SQL default expression), `computed` (the `AS (...)` expression), `serverManaged`, and `datePrecision`.
639
- Foreign keys carry `constraintName`, `columnName`, and the `referencedTableSchema` / `referencedTableName` / `referencedColumnName` triple.
640
-
641
- `primaryKeyColumns`, when available, contains single-column or composite PKs in `ORDINAL_POSITION` order.
642
- Multiple `foreignKeys` entries with the same `constraintName` form one composite FK and must be generated as one relationship.
643
-
644
- Fabric SQL Database generally exposes PK/FK and server-generation metadata.
645
- Warehouse and Lakehouse may omit some catalog metadata; Lakehouse commonly omits PK/FK constraints entirely.
646
- Absence means "unknown / not exposed", not permission to synthesize keys or relationships.
647
-
648
- ## Category A anti-patterns
649
-
650
- - Never leave `schema.ts` as bare re-exports — the client import of `<Name>Schema` and `connectorConfig` would fail.
651
- - Never import or re-export entity classes as **values** in `schema.ts`. Use `import type` / `export type`; a value import ships decorated classes to the browser and the page renders blank.
652
- - Populate `connectorConfig.entities` with **column-name arrays**, never entity classes. Omitting it makes every no-selection read throw `SELECTION_REQUIRED`; using classes puts them back in the bundle.
653
- - In subset mode, list in `TSchema` only entities you actually generated.
654
- - Keep `connectorConfig.operations` identical to the YAML `operations:`, and keep each entity `@role(...)` action a subset of that pair. Narrow YAML first; never widen above the type's catalog allowlist, and never widen a decorator just to match the connector.
655
- - Policies use the typed `claims` / `item` DSL — never raw SQL or DAB-policy strings like `"@claims.sub eq @item.owner_id"`.
656
- - Never double-pluralize an entity/class name.
657
- - GraphQL type names are global across connectors — disambiguate duplicates, never blanket-prefix.
658
- - `metadata.json` is the only source of truth for keys and relationships. Never synthesize a primary key or infer a relationship from column names, sampled values, or naming conventions — absent metadata means keyless and relationship-free.
659
- - When the user asks for one entity, read `metadata.json` and filter — do not regenerate every table.
660
- - Never edit `metadata.json` or `dab-config.json` by hand — both are regenerated.
661
-
662
- ## Troubleshooting
663
-
664
- | Symptom | Likely cause | Fix |
665
- | --- | --- | --- |
666
- | `rayfin up connector apply` fails on a role action | Entity `@role(...)` includes an action not in the YAML `operations:` | Narrow the decorator to match YAML. |
667
- | `rayfin up connector apply` fails with a duplicate / redefined GraphQL type | Two connectors generated an entity with the same name — type names are global | Disambiguate the colliding entity by prefixing its source database name; update the class, file name, `@entity` name, `TSchema` key, and access path together, then re-apply. |
668
- | `rayfin connector add` writes the YAML entry but no entity files | Expected — the CLI never emits entity `.ts` files | Generate them from `metadata.json` per the contract. If `metadata.json` is also missing, schema discovery failed. |
669
- | `Property '<name>' does not exist on connectors` | Connector key in `AppConnectorsSchema` does not match the key in the `connectors` option | Use the `rayfin.yml` `name` in all three places. |
670
- | A CRUD method is missing from autocomplete | Expected: `<Name>Schema` narrows methods to the connector's `operations` | Widen the allow-list via `rayfin connector add --operations`, then re-import. |
671
- | Import of `ConnectorsRayfinClient` fails to resolve | Imported from the stable `@microsoft/rayfin-client` entry | Import from `@microsoft/rayfin-client/experimental`. |
672
- | `Cannot find module '@microsoft/rayfin-connector-fabric-graphql'` | The connector packages were never installed — `connector add` does not add them | Run the pinned `npm install` command `connector add` printed, or rebuild it from `rayfin connector types --json`. |
673
- | Deployed page is blank with `Uncaught SyntaxError: Invalid or unexpected token`, though build, tests and deploy all passed | `schema.ts` imports or re-exports entity classes as values, so the decorated classes were bundled into the browser build | Switch every entity import and re-export in `schema.ts` to `import type` / `export type`, and give `connectorConfig.entities` column-name arrays instead of classes. |
674
- | A read throws `SELECTION_REQUIRED` | `connectorConfig.entities` is missing, so there is no default column list | Add the entity's property names, or pass an explicit `select([...])`. |
675
- | A read returns null or errors on a field that exists in the database | `entities` lists database column names instead of the entity's property names | Use the names declared on the generated class (or `graphqlName` where set), for example `productId`, not `ProductID`. |
7
+ > **Moved.** This reference now ships **inside the connector package**,
8
+ > version-locked to the connector the Builder actually installed so per-dialect
9
+ > read/write behavior, SQL-type mappings, and composite-PK semantics can never
10
+ > drift from the CLI. It is fetched on demand with `packageVersion` provenance.
11
+ >
12
+ > Read it from `@microsoft/rayfin-connector-fabric-graphql`:
13
+ >
14
+ > - MCP: `search_docs` / `discover_packages`
15
+ > - CLI: `rayfin docs search "<term>"` (module `rayfin-connector-fabric-graphql`)
16
+ > - Source: `@microsoft/rayfin-connector-fabric-graphql/assets/docs/`
17
+ >
18
+ > Pages: `index.md` (generation workflow, the aggregate `schema.ts`, client
19
+ > wiring, worked example), `entities.md` (`@role` row-level policies, the entity
20
+ > generation contract, the `metadata.json` reference), `querying.md`,
21
+ > `mutations.md`, and `troubleshooting.md`.
@@ -11,7 +11,7 @@ The Builder never writes or sees the function code.
11
11
  The Builder declares the connector and calls a typed method; the Fabric app backend injects connector configuration and delegated authentication before forwarding to the function.
12
12
  There are no GraphQL entities, so do **not** generate entity files, `@role` policies, or `metadata.json` entities for these connector types.
13
13
 
14
- For the entity-generating types (`fabric-sqlanalytics`, `fabric-warehouse`, `fabric-sqldatabase`), read [Category A](./category-a-entities.md) instead.
14
+ For the entity-generating types (`fabric-sqlanalytics`, `fabric-warehouse`, `fabric-sqldatabase`), read the package-owned Category A docs in `@microsoft/rayfin-connector-fabric-graphql` (`rayfin docs search` / `search_docs`) instead.
15
15
 
16
16
  ## What each type exposes
17
17
 
@@ -30,12 +30,12 @@ Each command has its own reference page:
30
30
 
31
31
  - [`connector search`](./search.md) — discover Fabric sources the signed-in identity can add.
32
32
  - [`connector add`](./add.md) — declare a connector in `rayfin.yml` and scaffold its files.
33
- - [`connector inspect`](./inspect.md) — run a single read-only sample query against a source.
33
+ - [`connector inspect`](./inspect.md) — list a source's entity names, or run a single read-only sample query against one.
34
34
  - [`connector invoke`](./invoke.md) — run one named operation against a configured connector.
35
35
 
36
36
  Each category has its own contract page — read the one that matches your connector type, not both:
37
37
 
38
- - [Category A — GraphQL entity connectors](./category-a-entities.md) — entity generation, `@role` policies, the aggregate schema, and the per-dialect read/write matrix.
38
+ - [Category A — GraphQL entity connectors](./category-a-entities.md) — now **package-owned**; the full reference (entity generation, `@role` policies, the aggregate schema, and the per-dialect read/write matrix) ships in `@microsoft/rayfin-connector-fabric-graphql` (`rayfin docs search` / `search_docs`).
39
39
  - [Category B — function-bridge connectors](./category-b-function-bridge.md) — the `kusto` and `fabric-semanticmodel` contract, including the Kusto cluster routing baked into the generated `schema.ts`.
40
40
 
41
41
  A typical loop is search → add → inspect (Category A) or search → add → invoke (Category B).
@@ -44,16 +44,16 @@ A typical loop is search → add → inspect (Category A) or search → add →
44
44
 
45
45
  The commands available to a connector, and the code you write against it, depend on its category.
46
46
 
47
- | | Category A — GraphQL entity connectors | Category B — function-bridge connectors |
48
- | ------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------ |
49
- | **Types** | `fabric-sqlanalytics`, `fabric-warehouse`, `fabric-sqldatabase` | `kusto`, `fabric-semanticmodel` |
50
- | **App surface** | Generated entity files with typed CRUD through the data client | A single `executeQuery` operation carrying a raw query |
47
+ | | Category A — GraphQL entity connectors | Category B — function-bridge connectors |
48
+ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
49
+ | **Types** | `fabric-sqlanalytics`, `fabric-warehouse`, `fabric-sqldatabase` | `kusto`, `fabric-semanticmodel` |
50
+ | **App surface** | Generated entity files with typed CRUD through the data client | A single `executeQuery` operation carrying a raw query |
51
51
  | **Operations** | `read` only for `fabric-sqlanalytics` (Lakehouse SQL endpoints are read-only); `read`, `create`, `update`, `delete` for `fabric-warehouse` and `fabric-sqldatabase` (narrowable) | `executeQuery` only |
52
- | **Auth** | `delegated` or configured per project | Must be `delegated` |
53
- | **Entity files and `@role` policies** | Yes | No |
54
- | **`metadata.json` entities** | Yes | No |
55
- | **`connector inspect`** | Supported | `fabric-semanticmodel` only — `kusto` is not supported |
56
- | **`connector invoke`** | Rarely needed | The main way to exercise the connector |
52
+ | **Auth** | `delegated` or configured per project | Must be `delegated` |
53
+ | **Entity files and `@role` policies** | Yes | No |
54
+ | **`metadata.json` entities** | Yes | No |
55
+ | **`connector inspect`** | Supported | `fabric-semanticmodel` only — `kusto` is not supported |
56
+ | **`connector invoke`** | Rarely needed | The main way to exercise the connector |
57
57
 
58
58
  Category B connectors are pinned to an adapter version and expose no GraphQL entities, so there is nothing to generate and no row-level security to author.
59
59
 
@@ -5,28 +5,43 @@ sidebar_position: 3
5
5
  # connector inspect
6
6
 
7
7
  ```bash
8
- npx rayfin connector inspect (--name <name> | <direct-selector-flags>) (--entity <name> | --query <path>) [--rows <n>] [--verbose] [--json]
8
+ npx rayfin connector inspect (--name <name> | <direct-selector-flags>) [--entity <name> | --query <path>] [--rows <n>] [--verbose] [--json]
9
9
  ```
10
10
 
11
11
  `connector inspect` takes no positional arguments — every selector and query mode is a flag.
12
12
 
13
- `connector inspect` runs a single read-only sample query against a connector's underlying source, before you have written any app code. Use it to check a table's real data and shape ahead of entity generation, or to debug a row-level-security or query issue on an already-wired connector. It is a development aid — never use it to power app functionality.
13
+ `connector inspect` explores a connector's underlying source in read-only mode, before you have written any app code. Run it with just a selector to **list the entity/table names** the source exposes; add `--entity` to sample one of them, or `--query` to run a `.sql`/`.dax` file of your own. Use it to check a table's real data and shape ahead of entity generation, or to debug a row-level-security or query issue on an already-wired connector. It is a development aid — never use it to power app functionality.
14
14
 
15
15
  Supported types: the three Category A SQL types (`fabric-sqlanalytics`, `fabric-warehouse`, `fabric-sqldatabase`) and `fabric-semanticmodel` (DAX). `kusto` is **not** supported today — the command errors with `Unsupported connector type: kusto`.
16
16
 
17
- ## Pick exactly one selector and one query mode
17
+ ## Pick exactly one selector, and at most one query mode
18
18
 
19
- Two independent choices, each a mutually exclusive pair. Passing zero or both options in a pair fails validation before any network call.
19
+ Two independent choices. The **selector** is a mutually exclusive pair passing zero or both fails validation before any network call. The **query mode** has three options, and omitting it is a mode of its own rather than an error; only passing `--entity` and `--query` together fails.
20
20
 
21
- | Choice | Option A | Option B |
22
- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23
- | **Selector** — which connector or item to query | `--name <name>` a connector already declared in `rayfin.yml` | Direct mode — `--workspace-id`/`--workspace <name>` plus `--item-id`/`--item <name>` plus `--type <type>`, or `--url <portal-url>` for a semantic model (auto-extracts workspace and item IDs) |
24
- | **Query mode** | `--entity <name>` structured: builds `SELECT TOP (n) * FROM <entity>` (SQL) or `EVALUATE TOPN(n, '<entity>')` (DAX) for you | `--query <path>` raw: runs the literal `.sql` or `.dax` file verbatim, still capped and validated |
21
+ | Selector which connector or item to query | Meaning |
22
+ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23
+ | `--name <name>` | A connector already declared in `rayfin.yml`. |
24
+ | Direct mode | `--workspace-id`/`--workspace <name>` plus `--item-id`/`--item <name>` plus `--type <type>`, or `--url <portal-url>` for a semantic model (auto-extracts workspace and item IDs). |
25
25
 
26
- All four combinations of `{--name, direct} × {--entity, --query}` are valid.
26
+ | Query mode | What it runs |
27
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
28
+ | _omit both_ — entity listing | Lists the entity/table names the source exposes, so you have something to pass to `--entity`. This is the default. |
29
+ | `--entity <name>` — structured | Builds `SELECT TOP (n) * FROM <entity>` (SQL) or `EVALUATE TOPN(n, '<entity>')` (DAX) for you. |
30
+ | `--query <path>` — raw | Runs the literal `.sql` or `.dax` file verbatim, still capped and validated. |
31
+
32
+ All six combinations of `{--name, direct} × {listing, --entity, --query}` are valid.
27
33
 
28
34
  `--workspace` and `--item` accept display names and are resolved to IDs the same way [`connector add`](./add.md) fuzzy matching works; `--workspace-id` and `--item-id` take literal IDs directly.
29
35
 
36
+ ## Entity listing
37
+
38
+ Omitting both `--entity` and `--query` lists the entity names available on the source. This is the starting point when you don't know the names yet — the output is what you feed back into `--entity`, and the command prints a ready-to-run `--entity` suggestion carrying the same selector flags you just used.
39
+
40
+ - **SQL types** — reads `INFORMATION_SCHEMA.TABLES`, returning `TABLE_SCHEMA` and `TABLE_NAME`.
41
+ - **`fabric-semanticmodel`** — reads `INFO.TABLES()`, returning table names.
42
+
43
+ Listing honours `--rows` and `--json` like any other mode, and the `--json` envelope reports `queryMode: "entities"`.
44
+
30
45
  ## Entity resolution
31
46
 
32
47
  Applies to `--entity` mode only.
@@ -37,22 +52,28 @@ Applies to `--entity` mode only.
37
52
 
38
53
  ## Validation
39
54
 
40
- Applies to both query modes.
55
+ Applies to `--entity` and `--query` modes.
41
56
 
42
57
  - **SQL** — must start with `SELECT` or `WITH`; must be a single statement (a trailing `;` is fine, an embedded one is not); rejects `INSERT`, `UPDATE`, `DELETE`, `MERGE`, `CREATE`, `ALTER`, `DROP`, `TRUNCATE`, `EXEC`/`EXECUTE`, and `INTO` anywhere outside a string literal.
43
58
  - **DAX** — must start with `EVALUATE`.
44
59
  - `--query <path>` must resolve to a `.sql` or `.dax` file inside the project root; paths outside it are rejected. The project root is the directory containing `rayfin/rayfin.yml` when one is found. Direct mode (no `--name`) falls back to the current working directory if no `rayfin.yml` exists, so `--query` never requires a Rayfin project.
45
- - `--rows <n>` caps the sample size — default 10, maximum 100. The result reports `truncated: true` when the source had more rows than the cap.
60
+ - `--rows <n>` caps the sample size — maximum 100. The default is mode-specific: entity listing (neither `--entity` nor `--query`) defaults to **100**, while `--entity` and `--query` sampling default to **10**. The result reports `truncated: true` when the source had more rows than the cap; inspect fetches one row beyond the cap to detect that, then discards it.
46
61
 
47
62
  ## Examples
48
63
 
49
64
  ```bash
65
+ # --name selector, no query mode: list the entity names on the source
66
+ npx rayfin connector inspect --name inventory
67
+
50
68
  # --name selector + structured entity mode
51
69
  npx rayfin connector inspect --name inventory --entity Order
52
70
 
53
71
  # --name selector + raw query file
54
72
  npx rayfin connector inspect --name inventory --query rayfin/queries/order.sql
55
73
 
74
+ # Direct selector, no query mode: list the entity names on the source
75
+ npx rayfin connector inspect --workspace-id <ws-id> --item-id <item-id> --type fabric-warehouse
76
+
56
77
  # Direct selector (literal IDs) + structured entity mode
57
78
  npx rayfin connector inspect --workspace-id <ws-id> --item-id <item-id> --type fabric-warehouse --entity Order
58
79
 
@@ -63,7 +84,7 @@ npx rayfin connector inspect --workspace "Sales Analytics" --item "Inventory War
63
84
  npx rayfin connector inspect --url <fabric-portal-semantic-model-url> --query rayfin/queries/model.dax
64
85
  ```
65
86
 
66
- Combine `--workspace-id`, `--item-id`, and `--type` with either `--entity` or `--query` freely — none of the four combinations require `--name` or a `rayfin.yml` entry to exist.
87
+ Combine `--workspace-id`, `--item-id`, and `--type` with entity listing, `--entity`, or `--query` freely — none of the six combinations require `--name` or a `rayfin.yml` entry to exist.
67
88
 
68
89
  ## Errors
69
90
 
@@ -78,6 +78,8 @@ services:
78
78
  data:
79
79
  enabled: true
80
80
  dialect: mssql
81
+ storage:
82
+ enabled: false
81
83
  staticHosting:
82
84
  enabled: true
83
85
  root: .
@@ -158,6 +160,12 @@ Configure an email provider for magic links, password resets, and email verifica
158
160
  | `useStartTls` | `boolean` | `false` | Use STARTTLS for the SMTP connection. |
159
161
  | `webPort` | `number` | `1080` | MailDev web UI port (local development only). |
160
162
 
163
+ #### `services.storage`
164
+
165
+ | Field | Type | Default | Description |
166
+ | --- | --- | --- | --- |
167
+ | `enabled` | `boolean` | `false` | Enable the storage service. |
168
+
161
169
  #### `services.staticHosting`
162
170
 
163
171
  | Field | Type | Default | Description |
@@ -38,6 +38,24 @@ services:
38
38
  | `buildCommand` | No | — | Shell command to run before packaging (for example, `npm run build`). |
39
39
  | `indexDocument` | No | — | Default document to serve for directory requests (for example, `index.html`). |
40
40
 
41
+ ### Declared package versions
42
+
43
+ Every `rayfin up` records which packages produced the deployment, under `packageVersions` in the runtime settings it uploads:
44
+
45
+ | Key | When it is sent |
46
+ | --- | --- |
47
+ | `@microsoft/rayfin-cli` | Always, set to the running CLI's version. |
48
+ | `@microsoft/rayfin-auth` | When your frontend package has the auth SDK installed, set to the installed version. |
49
+
50
+ You do not author this.
51
+ Run `rayfin up --dry-run` to see exactly which versions a deploy would declare, without deploying.
52
+
53
+ Once your tenant enforces static-hosting access control, a CLI too old to record its version cannot deploy a static-hosted app, and the deployment is rejected with instructions to upgrade:
54
+
55
+ ```bash
56
+ npm install -g @microsoft/rayfin-cli@latest
57
+ ```
58
+
41
59
  ### Example with a separate frontend directory
42
60
 
43
61
  If your frontend lives in a subdirectory:
@@ -133,7 +151,7 @@ The deploy tool updates the configuration and pushes it to the backend during de
133
151
 
134
152
  - The compressed ZIP archive must not exceed **100 MB**.
135
153
  - The CLI uses maximum compression to minimize upload size.
136
- - If your build output exceeds the limit, consider excluding large assets from the deployed bundle.
154
+ - If your build output exceeds the limit, consider excluding large assets or using the storage service for binary files.
137
155
 
138
156
  ## Complete example
139
157
 
@@ -182,6 +200,7 @@ If the ZIP exceeds 100 MB:
182
200
 
183
201
  - Review your build output for unnecessary files (source maps, unoptimized images).
184
202
  - Configure your bundler to exclude development artifacts from the production build.
203
+ - Move large binary assets to Rayfin storage instead of bundling them as static content.
185
204
 
186
205
  ### No remote endpoint configured
187
206
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/rayfin-guide",
3
- "version": "1.35.0-beta.0",
3
+ "version": "1.36.0-alpha.1588",
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": [