@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.
@@ -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>, _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;
@@ -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 _tanstack_react_start0 from "@tanstack/react-start";
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): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>;
34
+ declare function softrMcpMiddleware(options?: SoftrMcpOptions): _tanstack_react_start6.RequestMiddlewareAfterServer<{}, undefined, undefined>;
35
35
  //#endregion
36
36
  export { softrMcpMiddleware };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.0.91-alpha.0";
2
+ var version = "0.0.91";
3
3
 
4
4
  //#endregion
5
5
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softr/sdk",
3
- "version": "0.0.91-alpha.0",
3
+ "version": "0.0.91",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
@@ -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 fetchInvoicesFor({ data: { userId: opts.context.user.id } })
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 handles authorization automatically — you never set API keys or auth headers yourself.
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
- .validator((data: { customerId: string }) => data)
93
- .handler(async ({ data }) => {
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: data.customerId } })
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: { channel: string; text: string }) => 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: data.channel, text: data.text }),
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
@@ -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 };