@softr/sdk 0.0.45-alpha.0 → 0.0.45-alpha.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.
- package/dist/auth/context.d.ts +1 -1
- package/dist/auth/isomorphic.d.ts +2 -19
- package/dist/auth/isomorphic.js +2 -24
- package/dist/auth/permissions.d.ts +1 -1
- package/dist/auth/session.js +1 -15
- package/dist/index.d.ts +5 -4
- package/dist/index.js +5 -4
- package/dist/package.js +1 -1
- package/dist/server.js +1 -1
- package/dist/visibility/audience.d.ts +31 -0
- package/dist/visibility/audience.js +25 -0
- package/dist/visibility/route.d.ts +71 -0
- package/dist/visibility/route.js +83 -0
- package/dist/visibility/visibility.d.ts +76 -0
- package/dist/visibility/visibility.js +63 -0
- package/package.json +1 -1
- package/skills/softr-sdk/SKILL.md +11 -12
- package/skills/softr-sdk/auth/SKILL.md +2 -2
- package/skills/softr-sdk/charts-and-metrics/SKILL.md +2 -2
- package/skills/softr-sdk/current-user/SKILL.md +12 -31
- package/skills/softr-sdk/file-uploads/SKILL.md +1 -1
- package/skills/softr-sdk/integrations/SKILL.md +2 -2
- package/skills/softr-sdk/record-mutations/SKILL.md +2 -2
- package/skills/softr-sdk/record-queries/SKILL.md +2 -2
- package/skills/softr-sdk/region-visibility/SKILL.md +63 -0
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +33 -28
- package/skills/softr-sdk/workflows/SKILL.md +2 -2
- package/dist/mcp.d.ts +0 -36
- package/dist/mcp.js +0 -581
- package/dist/routes/visibility.d.ts +0 -142
- package/dist/routes/visibility.js +0 -115
- package/skills/softr-sdk/mcp/SKILL.md +0 -74
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: current-user
|
|
3
3
|
description: >-
|
|
4
|
-
Read the signed-in Softr visitor
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
permissions are a rendering hint rather than an authorization check.
|
|
4
|
+
Read the signed-in Softr visitor: useCurrentUser and the User type,
|
|
5
|
+
context.user in a route's beforeLoad, and context.user inside a server
|
|
6
|
+
function handler, where Start types it from the app's registered request
|
|
7
|
+
middleware. Covers the null guest and the SoftrUserGroups module augmentation
|
|
8
|
+
that narrows group ids to your own.
|
|
10
9
|
metadata:
|
|
11
10
|
type: sub-skill
|
|
12
11
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.45
|
|
14
13
|
---
|
|
15
14
|
|
|
16
|
-
**Boundary.** `useCurrentUser`, `usePermissions` and every type come from `@softr/sdk` and work anywhere; `ds` and `auth` live on `@softr/sdk/server` and run only inside a `createServerFn` handler. The app is already wired — `src/start.ts` registers `softrAuthMiddleware`, `src/routes/__root.tsx` publishes the visitor as route context — never add a second registration. Load the core `softr-sdk` skill only if you are unsure this is the right sub-skill.
|
|
17
|
-
|
|
18
15
|
# The current user
|
|
19
16
|
|
|
20
17
|
One visitor is resolved once per request and published three ways: as a hook in components, as route context in `beforeLoad`, and on the `context` argument inside a server function. All three read the same `SoftrContext`. Which one you reach for depends only on where the code runs.
|
|
@@ -24,7 +21,7 @@ One visitor is resolved once per request and published three ways: as a hook in
|
|
|
24
21
|
Nothing to install or register, since the app is already wired. Import from the root entrypoint:
|
|
25
22
|
|
|
26
23
|
```ts
|
|
27
|
-
import { useCurrentUser
|
|
24
|
+
import { useCurrentUser } from '@softr/sdk'
|
|
28
25
|
import type { User } from '@softr/sdk'
|
|
29
26
|
```
|
|
30
27
|
|
|
@@ -56,12 +53,12 @@ It returns `User | null` and never suspends or fetches. It selects off root rout
|
|
|
56
53
|
|
|
57
54
|
```tsx
|
|
58
55
|
export const Route = createFileRoute('/_app/invoices')({
|
|
59
|
-
beforeLoad: visibility
|
|
56
|
+
beforeLoad: guardRoute(visibility),
|
|
60
57
|
loader: ({ context }) => fetchInvoicesFor({ data: { userId: context.user!.id } }),
|
|
61
58
|
})
|
|
62
59
|
```
|
|
63
60
|
|
|
64
|
-
A `
|
|
61
|
+
A `guardRoute` in `beforeLoad` guarantees a visitor at runtime, but it does not change what the compiler sees. `context.user` is still `User | null` further down the route. `isAuthorized(visibility, context)` is the check that narrows it, so prefer that over `!`. See [routes-and-visibility](../routes-and-visibility/SKILL.md).
|
|
65
62
|
|
|
66
63
|
### In a server function: `context`
|
|
67
64
|
|
|
@@ -85,24 +82,7 @@ const getMyInvoices = createServerFn().handler(async ({ context }) => {
|
|
|
85
82
|
})
|
|
86
83
|
```
|
|
87
84
|
|
|
88
|
-
`context.user`
|
|
89
|
-
|
|
90
|
-
### Hiding UI the visitor cannot use
|
|
91
|
-
|
|
92
|
-
```tsx
|
|
93
|
-
import { usePermissions } from '@softr/sdk'
|
|
94
|
-
|
|
95
|
-
const INVOICES = '00000000-0000-0000-0000-000000000001' // the registered datasource's id
|
|
96
|
-
|
|
97
|
-
function NewInvoiceButton() {
|
|
98
|
-
const { create } = usePermissions(INVOICES)
|
|
99
|
-
return create ? <button>New invoice</button> : null
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
`usePermissions(datasourceId)` returns `{ read, create, update, delete }` for one datasource. The id is the opaque UUID from the table's registration on the application, the same one `ds.*` takes — not the table id, and never built from the table's name. It hides a control. It does not protect what the control does.
|
|
104
|
-
|
|
105
|
-
Pass an id the visitor's session has no entry for and it throws while rendering, so a typo takes the page down rather than returning four falses.
|
|
85
|
+
`context.user` is typed. Start derives a handler's context from the app's registered request middleware as well as the server function's own, so there is nothing to import and nothing to cast.
|
|
106
86
|
|
|
107
87
|
### Narrowing group ids to your own
|
|
108
88
|
|
|
@@ -122,6 +102,7 @@ With this declaration, `user.userGroupIds` and every `userGroupIds` the SDK acce
|
|
|
122
102
|
|
|
123
103
|
## References
|
|
124
104
|
|
|
125
|
-
- [routes-and-visibility/SKILL.md](../routes-and-visibility/SKILL.md): `isAuthorized
|
|
105
|
+
- [routes-and-visibility/SKILL.md](../routes-and-visibility/SKILL.md): `isAuthorized` narrows `context.user` while applying the route's rule
|
|
106
|
+
- [region-visibility/SKILL.md](../region-visibility/SKILL.md): rendering a region only to the visitors a rule admits
|
|
126
107
|
- [record-queries/SKILL.md](../record-queries/SKILL.md): filtering a query by the visitor, and who really authorizes the read
|
|
127
108
|
- [auth/SKILL.md](../auth/SKILL.md): what changes the value `useCurrentUser` returns
|
|
@@ -10,7 +10,7 @@ description: >-
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: sub-skill
|
|
12
12
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.
|
|
13
|
+
library_version: 0.0.45
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
**Boundary.** `upload` lives on `@softr/sdk/server` and runs inside server functions. `useFileUpload` and `FileItem` live on `@softr/sdk` and run in components. `FileAttachment` comes from `@softr/sdk` and can be imported anywhere.
|
|
@@ -9,10 +9,10 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.45
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
**Boundary.** `integration` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineIntegration` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
15
|
+
**Boundary.** `integration` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineIntegration` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
16
16
|
|
|
17
17
|
# Integrations
|
|
18
18
|
|
|
@@ -11,10 +11,10 @@ description: >-
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: sub-skill
|
|
13
13
|
library: softr-sdk
|
|
14
|
-
library_version: 0.0.
|
|
14
|
+
library_version: 0.0.45
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
**Boundary.** `ds` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineMutation` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
17
|
+
**Boundary.** `ds` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineMutation` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
18
18
|
|
|
19
19
|
# Writing records
|
|
20
20
|
|
|
@@ -13,10 +13,10 @@ description: >-
|
|
|
13
13
|
metadata:
|
|
14
14
|
type: sub-skill
|
|
15
15
|
library: softr-sdk
|
|
16
|
-
library_version: 0.0.
|
|
16
|
+
library_version: 0.0.45
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
**Boundary.** `ds` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler or a server-side route hook — importing it into a component fails the build. `defineQuery` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
19
|
+
**Boundary.** `ds` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler or a server-side route hook — importing it into a component fails the build. `defineQuery` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
20
20
|
|
|
21
21
|
# Reading records
|
|
22
22
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: region-visibility
|
|
3
|
+
description: >-
|
|
4
|
+
Show part of a page only to the visitors a rule admits: the Audience
|
|
5
|
+
component, its children and fallback, where a region's rule may be declared,
|
|
6
|
+
and why hiding a control is not authorization. Use when a button, a column, a
|
|
7
|
+
nav item or a panel should appear for some visitors and not others, on a page
|
|
8
|
+
everyone may open. Gating the whole route is routes-and-visibility.
|
|
9
|
+
metadata:
|
|
10
|
+
type: sub-skill
|
|
11
|
+
library: softr-sdk
|
|
12
|
+
library_version: 0.0.45
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
**Boundary.** `Audience` and `defineVisibility` come from `@softr/sdk` and work anywhere.
|
|
16
|
+
|
|
17
|
+
# Region visibility
|
|
18
|
+
|
|
19
|
+
A route's rule decides who may open the page. A region's rule decides what they see once they are on it. The two are independent: an admin panel on a page every signed-in visitor may open is a region, and so is a refund button only staff should reach for.
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
Wrap the region in `Audience` and give it a rule:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { Audience, defineVisibility } from '@softr/sdk'
|
|
27
|
+
|
|
28
|
+
const staffOnly = defineVisibility({
|
|
29
|
+
userGroup: 'LOGGED_IN_USERS',
|
|
30
|
+
userGroupIds: ['00000000-0000-0000-0000-00000000000a'],
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
function InvoiceActions({ invoice }: { invoice: Invoice }) {
|
|
34
|
+
return (
|
|
35
|
+
<Audience visibility={staffOnly} fallback={<p>Contact your account manager to refund.</p>}>
|
|
36
|
+
<RefundButton invoice={invoice} />
|
|
37
|
+
</Audience>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`Audience` renders its children for the visitors the rule admits and `fallback` for everyone else. Without a `fallback` it renders nothing.
|
|
43
|
+
|
|
44
|
+
The rules are the same four a route uses, and [routes-and-visibility](../routes-and-visibility/SKILL.md) covers them.
|
|
45
|
+
|
|
46
|
+
## Core Patterns
|
|
47
|
+
|
|
48
|
+
### Where the rule is declared
|
|
49
|
+
|
|
50
|
+
A region's rule is an ordinary value. It can sit beside the component, or come from a module the whole app shares so several regions read the same one. A route's rule cannot. Softr reads that one out of the route file, so it has to be declared there.
|
|
51
|
+
|
|
52
|
+
Two files cannot hold a region's rule. Any declaration in `__root.tsx` is a build error. In the lazy half of a split route, Softr takes the first declaration as the route's own. Put the rule in the component's file or a shared module and import it.
|
|
53
|
+
|
|
54
|
+
A route file may declare a region's rule alongside its own. The route's rule is the one `beforeLoad` names.
|
|
55
|
+
|
|
56
|
+
### Hiding a control is not authorization
|
|
57
|
+
|
|
58
|
+
`Audience` decides what to render and nothing else. The server function behind a hidden button is still reachable on its own, and Softr authorizes that request whatever the page rendered. If a region hides something sensitive, declare the action behind it with the permissions it should have.
|
|
59
|
+
|
|
60
|
+
## References
|
|
61
|
+
|
|
62
|
+
- [routes-and-visibility/SKILL.md](../routes-and-visibility/SKILL.md): the rule shapes, and gating a whole page
|
|
63
|
+
- [current-user/SKILL.md](../current-user/SKILL.md): reading the visitor a rule has admitted
|
|
@@ -3,19 +3,20 @@ name: routes-and-visibility
|
|
|
3
3
|
description: >-
|
|
4
4
|
Add a route file to a Softr app and declare who may see it: defineVisibility
|
|
5
5
|
with ALL_USERS, LOGGED_IN_USERS or NON_LOGGED_IN_USERS, userGroupIds, and the
|
|
6
|
-
|
|
7
|
-
per-reason overrides, the
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
ways to apply a rule: guardRoute and assertRouteAccess in a route,
|
|
7
|
+
isAuthorized and checkAccess anywhere. Covers per-reason overrides, the
|
|
8
|
+
redirect and throw strategies, rendering an access error with
|
|
9
|
+
isAccessError, isUnauthenticatedError, isForbiddenError and
|
|
10
|
+
isAlreadyAuthenticatedError, guarding a layout, and the lint rule that requires a
|
|
11
|
+
declaration alongside the rules that reject a duplicate, computed or
|
|
12
|
+
root-level one.
|
|
12
13
|
metadata:
|
|
13
14
|
type: sub-skill
|
|
14
15
|
library: softr-sdk
|
|
15
|
-
library_version: 0.0.
|
|
16
|
+
library_version: 0.0.45
|
|
16
17
|
---
|
|
17
18
|
|
|
18
|
-
**Boundary.** `defineVisibility`, the
|
|
19
|
+
**Boundary.** `defineVisibility`, the access functions and the error predicates come from `@softr/sdk` and work anywhere.
|
|
19
20
|
|
|
20
21
|
# Route visibility
|
|
21
22
|
|
|
@@ -27,12 +28,12 @@ Every route file, without exception, looks like this:
|
|
|
27
28
|
|
|
28
29
|
```tsx
|
|
29
30
|
import { createFileRoute } from '@tanstack/react-router'
|
|
30
|
-
import { defineVisibility } from '@softr/sdk'
|
|
31
|
+
import { defineVisibility, guardRoute } from '@softr/sdk'
|
|
31
32
|
|
|
32
33
|
const visibility = defineVisibility({ userGroup: 'LOGGED_IN_USERS' })
|
|
33
34
|
|
|
34
35
|
export const Route = createFileRoute('/_app/billing')({
|
|
35
|
-
beforeLoad: visibility
|
|
36
|
+
beforeLoad: guardRoute(visibility),
|
|
36
37
|
component: Billing,
|
|
37
38
|
})
|
|
38
39
|
|
|
@@ -41,12 +42,13 @@ function Billing() {
|
|
|
41
42
|
}
|
|
42
43
|
```
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
The route's rule is one `defineVisibility` call at module scope, with an inline object literal as its argument.
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
Four constraints on that shape, because Softr reads the file rather than running it:
|
|
47
48
|
|
|
48
49
|
- The call must initialise a top-level `const`.
|
|
49
50
|
- The import must be a direct named import from `@softr/sdk`.
|
|
51
|
+
- The route guards itself in `beforeLoad`, naming a rule declared in the same file. A rule imported from elsewhere is rejected.
|
|
50
52
|
- Never declare on `__root.tsx`. The root cannot carry an audience, so a declaration there would gate nothing while looking like it does. Softr rejects it. Put the rule on a layout below the root instead.
|
|
51
53
|
|
|
52
54
|
The four rules:
|
|
@@ -70,28 +72,28 @@ Absent or empty `userGroupIds` means **any** signed-in visitor, not "nobody".
|
|
|
70
72
|
```tsx
|
|
71
73
|
// src/routes/_app.tsx: guards every route under /_app
|
|
72
74
|
import { Outlet, createFileRoute } from '@tanstack/react-router'
|
|
73
|
-
import { defineVisibility } from '@softr/sdk'
|
|
75
|
+
import { defineVisibility, guardRoute } from '@softr/sdk'
|
|
74
76
|
|
|
75
77
|
const visibility = defineVisibility({ userGroup: 'LOGGED_IN_USERS' })
|
|
76
78
|
|
|
77
79
|
export const Route = createFileRoute('/_app')({
|
|
78
|
-
beforeLoad: visibility
|
|
80
|
+
beforeLoad: guardRoute(visibility),
|
|
79
81
|
component: () => <Outlet />,
|
|
80
82
|
})
|
|
81
83
|
```
|
|
82
84
|
|
|
83
|
-
The whole match chain runs, so a layout guards everything beneath it. Sibling routes do not guard each other. `/admin/index.tsx` does nothing for `/admin/users.tsx` unless an `admin.tsx` layout exists.
|
|
85
|
+
The whole match chain runs, so a layout guards everything beneath it. Sibling routes do not guard each other. `/admin/index.tsx` does nothing for `/admin/users.tsx` unless an `admin.tsx` layout exists. A route below a guarded layout declares and guards its own rule too.
|
|
84
86
|
|
|
85
87
|
A thrown error renders in place of the guarded route's component. A shell layout guarded with `LOGGED_IN_USERS`, where `unauthenticated` redirects, sends a signed-out visitor away before anything renders there. A rule that can *throw* — a `userGroupIds` rule with `forbidden` set to throw — should not sit on the shell itself, or the error replaces the nav and chrome. Put it on a layout inside the shell, or on the restricted routes, so the error renders inside the chrome.
|
|
86
88
|
|
|
87
89
|
### Change what a denied visitor gets
|
|
88
90
|
|
|
89
|
-
`
|
|
91
|
+
`guardRoute` takes per-reason overrides as a second argument. A bare string is shorthand for a redirect.
|
|
90
92
|
|
|
91
93
|
```ts
|
|
92
|
-
beforeLoad: visibility
|
|
93
|
-
beforeLoad: visibility
|
|
94
|
-
beforeLoad: visibility
|
|
94
|
+
beforeLoad: guardRoute(visibility, { unauthenticated: '/marketing/sign-up' }),
|
|
95
|
+
beforeLoad: guardRoute(visibility, { forbidden: { strategy: 'throw' } }),
|
|
96
|
+
beforeLoad: guardRoute(visibility, {
|
|
95
97
|
unauthenticated: { strategy: 'redirect', to: '/marketing/sign-up' },
|
|
96
98
|
forbidden: { strategy: 'throw' },
|
|
97
99
|
}),
|
|
@@ -122,13 +124,13 @@ All three reasons are required, because nothing fills in a reason the file leave
|
|
|
122
124
|
| `forbidden` | Signed in, in none of the named groups |
|
|
123
125
|
| `alreadyAuthenticated` | Signed-in visitor on a signed-out-only route |
|
|
124
126
|
|
|
125
|
-
That file covers every route at once; a route's own
|
|
127
|
+
That file covers every route at once; a route's own overrides win over it. `ALL_USERS` admits everyone, so it never redirects and never throws. Only `strategy: 'throw'` reaches an `errorComponent`. A redirect navigates away and the route never renders.
|
|
126
128
|
|
|
127
129
|
### Render the error in place
|
|
128
130
|
|
|
129
131
|
```tsx
|
|
130
132
|
import { createFileRoute } from '@tanstack/react-router'
|
|
131
|
-
import { defineVisibility, isAccessError, isForbiddenError } from '@softr/sdk'
|
|
133
|
+
import { defineVisibility, guardRoute, isAccessError, isForbiddenError } from '@softr/sdk'
|
|
132
134
|
|
|
133
135
|
const visibility = defineVisibility({
|
|
134
136
|
userGroup: 'LOGGED_IN_USERS',
|
|
@@ -136,7 +138,7 @@ const visibility = defineVisibility({
|
|
|
136
138
|
})
|
|
137
139
|
|
|
138
140
|
export const Route = createFileRoute('/_app/billing')({
|
|
139
|
-
beforeLoad: visibility
|
|
141
|
+
beforeLoad: guardRoute(visibility),
|
|
140
142
|
component: Billing,
|
|
141
143
|
errorComponent: ({ error }: { error: unknown }) => {
|
|
142
144
|
if (isForbiddenError(error)) return <UpgradePrompt />
|
|
@@ -152,24 +154,27 @@ Use the predicates: `isAccessError` for any of the three, and `isUnauthenticated
|
|
|
152
154
|
|
|
153
155
|
### Run the check and then keep going
|
|
154
156
|
|
|
155
|
-
`
|
|
157
|
+
`guardRoute` covers almost everything. When a route needs its own logic afterwards, `assertRouteAccess(visibility, context)` applies the same rule and throws the same access error, using the default behaviour for each reason. It takes no overrides.
|
|
156
158
|
|
|
157
|
-
|
|
159
|
+
It also narrows `context.user` for the rest of the handler: `User` under `LOGGED_IN_USERS`, `null` under `NON_LOGGED_IN_USERS`. `guardRoute` cannot, because it hands `beforeLoad` a function rather than running inside one.
|
|
158
160
|
|
|
159
161
|
```ts
|
|
160
162
|
import { redirect } from '@tanstack/react-router'
|
|
163
|
+
import { assertRouteAccess } from '@softr/sdk'
|
|
161
164
|
|
|
162
165
|
beforeLoad: ({ context }) => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
+
assertRouteAccess(visibility, context)
|
|
167
|
+
if (context.user.email.endsWith('@example.com')) throw redirect({ to: '/internal' })
|
|
166
168
|
},
|
|
167
169
|
```
|
|
168
170
|
|
|
169
|
-
Reach for `
|
|
171
|
+
Reach for `assertRouteAccess` when the route wants the error thrown for it, `isAuthorized(visibility, context)` when it wants the same narrowing without a throw, and `checkAccess(visibility, context)` when it needs to branch on why. `checkAccess` returns `{ ok: true } | { ok: false, reason }` and throws nothing. All of them are synchronous and make no request.
|
|
172
|
+
|
|
173
|
+
`guardRoute` and `assertRouteAccess` throw router redirects, so they belong in a route and nowhere else. That is what `Route` in their names marks. `isAuthorized` and `checkAccess` throw nothing and answer anywhere, in a loader, a component or a server function.
|
|
170
174
|
|
|
171
175
|
## References
|
|
172
176
|
|
|
173
177
|
- [current-user/SKILL.md](../current-user/SKILL.md): reading the visitor a guard has just admitted
|
|
174
178
|
- [record-queries/SKILL.md](../record-queries/SKILL.md): a guard covers the page, not the server function behind it
|
|
175
179
|
- [auth/SKILL.md](../auth/SKILL.md): `NON_LOGGED_IN_USERS` and the sign-in flow it fronts
|
|
180
|
+
- [region-visibility/SKILL.md](../region-visibility/SKILL.md): showing part of a page to some of the visitors the route admits
|
|
@@ -9,10 +9,10 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.45
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
**Boundary.** `workflow` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineWorkflow` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
15
|
+
**Boundary.** `workflow` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineWorkflow` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
16
16
|
|
|
17
17
|
# Triggering workflows
|
|
18
18
|
|
package/dist/mcp.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { DatasourceAction } from "./action.js";
|
|
2
|
-
import * as _tanstack_react_start2 from "@tanstack/react-start";
|
|
3
|
-
|
|
4
|
-
//#region src/mcp.d.ts
|
|
5
|
-
|
|
6
|
-
type SoftrMcpOptions = {
|
|
7
|
-
/**
|
|
8
|
-
* The actions to expose as tools, from `defineQuery` and `defineMutation`.
|
|
9
|
-
*
|
|
10
|
-
* Listing them is deliberate: an action left out is invisible over MCP, even
|
|
11
|
-
* though the app's own pages may use it. The action's own `permissions` still
|
|
12
|
-
* decide who may run the ones that are listed.
|
|
13
|
-
*/
|
|
14
|
-
actions?: Array<DatasourceAction>;
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* Serves the MCP endpoint and its OAuth flow from the app's own origin.
|
|
18
|
-
*
|
|
19
|
-
* Register it in the app's `src/start.ts`, ahead of `softrAuthMiddleware`: it
|
|
20
|
-
* answers its own paths and resolves the caller from the bearer token, so it
|
|
21
|
-
* must not sit behind the session lookup that middleware does on every
|
|
22
|
-
* request. Everything else passes straight through.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* import { publishedPosts, addCafe } from './actions'
|
|
26
|
-
*
|
|
27
|
-
* export const startInstance = createStart(() => ({
|
|
28
|
-
* requestMiddleware: [
|
|
29
|
-
* softrMcpMiddleware({ actions: [publishedPosts, addCafe] }),
|
|
30
|
-
* softrAuthMiddleware(),
|
|
31
|
-
* ],
|
|
32
|
-
* }))
|
|
33
|
-
*/
|
|
34
|
-
declare function softrMcpMiddleware(options?: SoftrMcpOptions): _tanstack_react_start2.RequestMiddlewareAfterServer<{}, undefined, undefined>;
|
|
35
|
-
//#endregion
|
|
36
|
-
export { softrMcpMiddleware };
|