@softr/sdk 0.0.91-alpha.0 → 0.0.91
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/isomorphic.d.ts +1 -1
- package/dist/auth/isomorphic.js +0 -2
- package/dist/mcp.d.ts +2 -2
- package/dist/package.js +1 -1
- package/package.json +1 -1
- package/skills/softr-sdk/current-user/SKILL.md +4 -2
- package/skills/softr-sdk/integrations/SKILL.md +10 -8
- package/skills/softr-sdk/record-queries/references/filters-and-sorting.md +2 -0
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +2 -0
- package/dist/app-identity.js +0 -25
|
@@ -22,7 +22,7 @@ import * as _tanstack_react_start0 from "@tanstack/react-start";
|
|
|
22
22
|
* requestMiddleware: [softrAuthMiddleware()],
|
|
23
23
|
* }))
|
|
24
24
|
*/
|
|
25
|
-
declare function softrAuthMiddleware(): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, readonly [_tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>, _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>, _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>, _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined
|
|
25
|
+
declare function softrAuthMiddleware(): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, readonly [_tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>, _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>, _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>, _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>], {
|
|
26
26
|
user: User | null;
|
|
27
27
|
permissions: Permissions;
|
|
28
28
|
formatting: Formatting;
|
package/dist/auth/isomorphic.js
CHANGED
|
@@ -10,7 +10,6 @@ import { bootErrorMiddleware } from "../boot-error.js";
|
|
|
10
10
|
import { pwaMiddleware } from "../pwa.js";
|
|
11
11
|
import { faviconMiddleware } from "../favicon.js";
|
|
12
12
|
import { embeddingMiddleware } from "../embedding.js";
|
|
13
|
-
import { appIdentityMiddleware } from "../app-identity.js";
|
|
14
13
|
import { createContext, useContext } from "react";
|
|
15
14
|
import { createMiddleware, createServerFn, getGlobalStartContext } from "@tanstack/react-start";
|
|
16
15
|
import { getRequestHeader } from "@tanstack/react-start/server";
|
|
@@ -35,7 +34,6 @@ import { jsx } from "react/jsx-runtime";
|
|
|
35
34
|
function softrAuthMiddleware() {
|
|
36
35
|
return createMiddleware({ type: "request" }).middleware([
|
|
37
36
|
embeddingMiddleware(),
|
|
38
|
-
appIdentityMiddleware(),
|
|
39
37
|
bootErrorMiddleware(),
|
|
40
38
|
pwaMiddleware(),
|
|
41
39
|
faviconMiddleware()
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatasourceAction } from "./action.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as _tanstack_react_start6 from "@tanstack/react-start";
|
|
3
3
|
|
|
4
4
|
//#region src/mcp.d.ts
|
|
5
5
|
|
|
@@ -31,6 +31,6 @@ type SoftrMcpOptions = {
|
|
|
31
31
|
* ],
|
|
32
32
|
* }))
|
|
33
33
|
*/
|
|
34
|
-
declare function softrMcpMiddleware(options?: SoftrMcpOptions):
|
|
34
|
+
declare function softrMcpMiddleware(options?: SoftrMcpOptions): _tanstack_react_start6.RequestMiddlewareAfterServer<{}, undefined, undefined>;
|
|
35
35
|
//#endregion
|
|
36
36
|
export { softrMcpMiddleware };
|
package/dist/package.js
CHANGED
package/package.json
CHANGED
|
@@ -56,9 +56,9 @@ It returns `User | null` and never suspends or fetches. It selects off root rout
|
|
|
56
56
|
```tsx
|
|
57
57
|
export const Route = createFileRoute('/_app/invoices')({
|
|
58
58
|
beforeLoad: guardRoute(pageSettings),
|
|
59
|
-
loader: (opts) => {
|
|
59
|
+
loader: async (opts) => {
|
|
60
60
|
assertRouteAccess(pageSettings, opts)
|
|
61
|
-
return
|
|
61
|
+
return { name: opts.context.user.fullName, invoices: await getMyInvoices() }
|
|
62
62
|
},
|
|
63
63
|
head: pageHead(pageSettings),
|
|
64
64
|
})
|
|
@@ -66,6 +66,8 @@ export const Route = createFileRoute('/_app/invoices')({
|
|
|
66
66
|
|
|
67
67
|
A `guardRoute` in `beforeLoad` guarantees a visitor at runtime, but it does not change what the compiler sees. `context.user` is still `User | null` in the loader. `assertRouteAccess(pageSettings, opts)` at the top of the loader narrows it there, so prefer that over `!`. Take `opts` whole: a destructured `context` does not narrow. See [routes-and-visibility](../routes-and-visibility/SKILL.md).
|
|
68
68
|
|
|
69
|
+
Never pass the visitor's id from a loader into a server function. The loader also runs in the browser, so the id arrives as an argument anyone can set. `getMyInvoices` below reads the visitor from its own `context`.
|
|
70
|
+
|
|
69
71
|
### In a server function: `context`
|
|
70
72
|
|
|
71
73
|
```ts
|
|
@@ -14,14 +14,14 @@ metadata:
|
|
|
14
14
|
|
|
15
15
|
# Integrations
|
|
16
16
|
|
|
17
|
-
`integration.createFetcher` calls a third-party API through Softr's authenticated proxy. It
|
|
17
|
+
`integration.createFetcher` calls a third-party API through Softr's authenticated proxy. It adds the connected account's credentials automatically — you never set API keys or auth headers yourself. **Every call acts as that one account, whoever the visitor is**, so take what a call reaches, such as a customer or a channel, from the session or a record the server read, never from the arguments the browser sends.
|
|
18
18
|
|
|
19
19
|
## Setup
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
22
|
import { createServerFn } from '@tanstack/react-start'
|
|
23
23
|
import { defineIntegration } from '@softr/sdk'
|
|
24
|
-
import { integration } from '@softr/sdk/server'
|
|
24
|
+
import { auth, integration } from '@softr/sdk/server'
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Reading connected integrations from the prompt
|
|
@@ -77,7 +77,7 @@ const getCalendarEvents = createServerFn().handler(async () => {
|
|
|
77
77
|
})
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
For a dynamic path segment, pass `params` matching each `{placeholder}` token:
|
|
80
|
+
For a dynamic path segment, pass `params` matching each `{placeholder}` token. Here the customer comes from the visitor's own Users row, which `auth.currentUserRow` finds through `usersQuery`, the app's `defineQuery` on the Users table:
|
|
81
81
|
|
|
82
82
|
```ts
|
|
83
83
|
const getStripeCustomer = defineIntegration({
|
|
@@ -89,10 +89,12 @@ const getStripeCustomer = defineIntegration({
|
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
const fetchCustomer = createServerFn({ method: 'GET' })
|
|
92
|
-
.
|
|
93
|
-
|
|
92
|
+
.handler(async ({ context }) => {
|
|
93
|
+
const me = await auth.currentUserRow(context.user, usersQuery, 'email')
|
|
94
|
+
const customerId = me?.fields.stripeCustomerId
|
|
95
|
+
if (!customerId) throw new Error('No Stripe customer for this visitor')
|
|
94
96
|
const stripe = integration.createFetcher(getStripeCustomer)
|
|
95
|
-
const res = await stripe({ params: { id:
|
|
97
|
+
const res = await stripe({ params: { id: customerId } })
|
|
96
98
|
if (!res.ok) throw new Error(`Stripe error: ${res.status} ${await res.text()}`)
|
|
97
99
|
return res.json()
|
|
98
100
|
})
|
|
@@ -110,12 +112,12 @@ const postSlackMessage = defineIntegration({
|
|
|
110
112
|
})
|
|
111
113
|
|
|
112
114
|
const sendSlackMessage = createServerFn({ method: 'POST' })
|
|
113
|
-
.validator((data: {
|
|
115
|
+
.validator((data: { text: string }) => data)
|
|
114
116
|
.handler(async ({ data }) => {
|
|
115
117
|
const slack = integration.createFetcher(postSlackMessage)
|
|
116
118
|
const res = await slack({
|
|
117
119
|
headers: { 'Content-Type': 'application/json' },
|
|
118
|
-
body: JSON.stringify({ channel:
|
|
120
|
+
body: JSON.stringify({ channel: '#sales', text: data.text }),
|
|
119
121
|
})
|
|
120
122
|
if (!res.ok) throw new Error(`Slack error: ${res.status} ${await res.text()}`)
|
|
121
123
|
return res.json()
|
|
@@ -127,6 +127,8 @@ Both take filters or nested groups:
|
|
|
127
127
|
|
|
128
128
|
Nesting goes two levels, a group inside a group. A third level throws `Cannot nest filter groups more than three levels deep.` Flatten by distributing the outer condition rather than nesting further.
|
|
129
129
|
|
|
130
|
+
**An `$or` with no members matches every row.** When its members come from a list that can be empty, return no rows for an empty list instead of reading the table.
|
|
131
|
+
|
|
130
132
|
## Sorting
|
|
131
133
|
|
|
132
134
|
```ts
|
|
@@ -73,6 +73,8 @@ Everyone is both audiences, as in the first line above. A set names each audienc
|
|
|
73
73
|
|
|
74
74
|
Take every id in `customGroups` from `docs/user-groups.md`, which lists each group the app has with its name and id. An id the app does not have is a type error.
|
|
75
75
|
|
|
76
|
+
**`visibility` guards the page, not the server functions it calls.** A call can reach them without the page and with any arguments, so the queries and mutations behind a page carry its groups in their own `permissions` and reach only the rows the visitor's role allows.
|
|
77
|
+
|
|
76
78
|
## Core Patterns
|
|
77
79
|
|
|
78
80
|
### Guard a whole section with one layout route
|
package/dist/app-identity.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { createMiddleware } from "@tanstack/react-start";
|
|
2
|
-
|
|
3
|
-
//#region src/app-identity.ts
|
|
4
|
-
/**
|
|
5
|
-
* Tells support which Softr app answered, in an `x-softr-app-id` response
|
|
6
|
-
* header.
|
|
7
|
-
*/
|
|
8
|
-
function appIdentityMiddleware() {
|
|
9
|
-
return createMiddleware({ type: "request" }).server(async ({ next }) => {
|
|
10
|
-
const result = await next();
|
|
11
|
-
const applicationId = process.env.SOFTR_APPLICATION_ID;
|
|
12
|
-
if (!applicationId) return result;
|
|
13
|
-
try {
|
|
14
|
-
result.response.headers.set("x-softr-app-id", applicationId);
|
|
15
|
-
return result;
|
|
16
|
-
} catch {
|
|
17
|
-
const response = new Response(result.response.body, result.response);
|
|
18
|
-
response.headers.set("x-softr-app-id", applicationId);
|
|
19
|
-
return response;
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
//#endregion
|
|
25
|
-
export { appIdentityMiddleware };
|