@softr/sdk 0.0.84 → 0.0.85
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/package.js +1 -1
- package/package.json +1 -1
- package/skills/softr-sdk/SKILL.md +4 -1
- package/skills/softr-sdk/auth/SKILL.md +2 -2
- package/skills/softr-sdk/charts-and-metrics/SKILL.md +1 -1
- package/skills/softr-sdk/current-user/SKILL.md +1 -1
- package/skills/softr-sdk/file-uploads/SKILL.md +1 -1
- package/skills/softr-sdk/formatting/SKILL.md +2 -1
- package/skills/softr-sdk/integrations/SKILL.md +6 -15
- package/skills/softr-sdk/mcp/SKILL.md +2 -1
- package/skills/softr-sdk/pwa/SKILL.md +2 -1
- package/skills/softr-sdk/record-mutations/SKILL.md +1 -1
- package/skills/softr-sdk/record-queries/SKILL.md +1 -1
- package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +3 -5
- package/skills/softr-sdk/workflows/SKILL.md +2 -2
package/dist/package.js
CHANGED
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
metadata:
|
|
9
9
|
type: core
|
|
10
10
|
library: softr-sdk
|
|
11
|
-
library_version: 0.0.
|
|
11
|
+
library_version: 0.0.85
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# Softr SDK core
|
|
@@ -16,8 +16,11 @@ metadata:
|
|
|
16
16
|
`@softr/sdk` is how a Softr application talks to Softr. The visitor's identity, who may see a route, records in a datasource, signing in: all of it goes through this package. Everything else is an ordinary TanStack Start app. Routes, server functions, loaders and middleware are Start's, not Softr's.
|
|
17
17
|
|
|
18
18
|
> **CRITICAL**: The SDK is split across four entrypoints and the split is enforced. `ds` and `auth` exist only on `@softr/sdk/server` and only run inside a `createServerFn` handler. Importing them anywhere else fails the build.
|
|
19
|
+
>
|
|
19
20
|
> **CRITICAL**: Every route file must declare its audience with `defineVisibility` and guard itself with that rule in `beforeLoad`. There is no default and nothing is inherited from a sibling.
|
|
21
|
+
>
|
|
20
22
|
> **CRITICAL**: The app is already wired. `src/start.ts`, `src/router.tsx` and `src/routes/__root.tsx` register `softrAuthMiddleware` and resolve the session. Do not add a second registration of it. `softrMcpMiddleware` is the one thing an app adds to `src/start.ts` itself — load the `softr-sdk/mcp` sub-skill before doing so.
|
|
23
|
+
>
|
|
21
24
|
> **CRITICAL**: `workflow.trigger` lives on `@softr/sdk/server` and only runs inside a `createServerFn` handler. Use `defineWorkflow` from `@softr/sdk` to declare the action, then pass it to `workflow.trigger`. Load the `softr-sdk/workflows` sub-skill for the full pattern.
|
|
22
25
|
|
|
23
26
|
Each sub-skill stands alone for its task and repeats the boundary above, so most work needs **one** sub-skill and not this file. Load this one when you are unsure which sub-skill applies, or when the wiring below is what you are touching. Add a second sub-skill only when the work genuinely crosses that boundary: a page that lists records behind a group restriction needs `record-queries` plus `routes-and-visibility`.
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.85
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
**Boundary.** `auth` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build.
|
|
@@ -465,4 +465,4 @@ The page keeps showing the old name and photo until its route context loads agai
|
|
|
465
465
|
## References
|
|
466
466
|
|
|
467
467
|
- [current-user/SKILL.md](../current-user/SKILL.md): reading the visitor these calls establish
|
|
468
|
-
- [routes-and-visibility/SKILL.md](../routes-and-visibility/SKILL.md): `NON_LOGGED_IN_USERS` and the `alreadyAuthenticated` redirect that keeps a signed-in visitor off a login page
|
|
468
|
+
- [routes-and-visibility/SKILL.md](../routes-and-visibility/SKILL.md): `NON_LOGGED_IN_USERS` and the `alreadyAuthenticated` redirect that keeps a signed-in visitor off a login page
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.85
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
**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.
|
|
@@ -7,7 +7,7 @@ description: >-
|
|
|
7
7
|
metadata:
|
|
8
8
|
type: sub-skill
|
|
9
9
|
library: softr-sdk
|
|
10
|
-
library_version: 0.0.
|
|
10
|
+
library_version: 0.0.85
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
**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.
|
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
metadata:
|
|
9
9
|
type: sub-skill
|
|
10
10
|
library: softr-sdk
|
|
11
|
-
library_version: 0.0.
|
|
11
|
+
library_version: 0.0.85
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# Dates, times and numbers
|
|
@@ -16,6 +16,7 @@ metadata:
|
|
|
16
16
|
Softr stores every date and time as UTC. The builder chooses in Studio how the app shows dates, times and numbers: in each visitor's own format and zone (the default), or in one fixed format or zone for everyone. The SDK resolves that choice once per request. The server and the browser then format with the same values, so the page the server renders and the page the browser hydrates show the same text.
|
|
17
17
|
|
|
18
18
|
> **CRITICAL**: Never call `toLocaleDateString`, `toLocaleTimeString`, `toLocaleString` or `new Intl.DateTimeFormat` / `new Intl.NumberFormat` in code that renders. The server and the browser have different locales and time zones, so the text differs and React throws a hydration mismatch. A hard-coded `'en-US'` or `timeZone: 'UTC'` hides the error but ignores the builder's settings and shows the wrong format and day to most visitors.
|
|
19
|
+
>
|
|
19
20
|
> **CRITICAL**: Format with the helpers below and nothing else. They follow the app's settings, and they are the only formatters that give the same text on the server and in the browser.
|
|
20
21
|
|
|
21
22
|
## Setup
|
|
@@ -7,7 +7,7 @@ description: >-
|
|
|
7
7
|
metadata:
|
|
8
8
|
type: sub-skill
|
|
9
9
|
library: softr-sdk
|
|
10
|
-
library_version: 0.0.
|
|
10
|
+
library_version: 0.0.85
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
**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.
|
|
@@ -59,8 +59,7 @@ Declare the **full endpoint path**, not just the host — one action names one e
|
|
|
59
59
|
|
|
60
60
|
`integration.createFetcher(action)` binds the action and returns a fetcher function. The `method` and `url` come from the action — you do not repeat them on the call. Pass an optional init object with:
|
|
61
61
|
|
|
62
|
-
- `params` — fills `{placeholder}` tokens in the declared url, one key per token. Pass each value raw: the
|
|
63
|
-
SDK encodes it.
|
|
62
|
+
- `params` — fills `{placeholder}` tokens in the declared url, one key per token. Pass each value raw: the SDK encodes it.
|
|
64
63
|
- `query` — appended as a query string (`?key=value`).
|
|
65
64
|
- `headers` — extra request headers.
|
|
66
65
|
- `body` — request body string.
|
|
@@ -127,11 +126,7 @@ Do not add an `Authorization` key to `headers` — Softr injects the integration
|
|
|
127
126
|
|
|
128
127
|
## Calling an AI model
|
|
129
128
|
|
|
130
|
-
A button on one record that hands its fields to the model and shows the answer is the `ai-action` pattern:
|
|
131
|
-
it carries this call and the model id, so write the call by hand only for another shape — a batch, a chat,
|
|
132
|
-
a field the model fills on create. An AI integration is called like any other. For `ANTHROPIC_AI`, the action is `POST` on
|
|
133
|
-
`https://api.anthropic.com/v1/messages`, the request carries the `anthropic-version` header and a body of
|
|
134
|
-
`model`, `max_tokens`, `system` and `messages`, and the answer's text is in `content[].text`:
|
|
129
|
+
A button on one record that hands its fields to the model and shows the answer is the `ai-action` pattern: it carries this call and the model id, so write the call by hand only for another shape — a batch, a chat, a field the model fills on create. An AI integration is called like any other. For `ANTHROPIC_AI`, the action is `POST` on `https://api.anthropic.com/v1/messages`, the request carries the `anthropic-version` header and a body of `model`, `max_tokens`, `system` and `messages`, and the answer's text is in `content[].text`:
|
|
135
130
|
|
|
136
131
|
```ts
|
|
137
132
|
const askClaude = defineIntegration({
|
|
@@ -157,17 +152,14 @@ const body = (await res.json()) as { content: Array<{ type: string; text?: strin
|
|
|
157
152
|
const text = body.content.flatMap((block) => (block.type === 'text' && block.text ? [block.text] : [])).join('')
|
|
158
153
|
```
|
|
159
154
|
|
|
160
|
-
**Use the model id above as it is written here.** Model ids are not derivable: one spelled from memory
|
|
161
|
-
(`claude-3-5-…`, `claude-sonnet-4-…`) names a model that no longer answers, and the error only shows up when
|
|
162
|
-
the button is pressed. `claude-opus-5-5` is the current default; `claude-sonnet-5-5` is the cheaper one for a
|
|
163
|
-
short summary.
|
|
155
|
+
**Use the model id above as it is written here.** Model ids are not derivable: one spelled from memory (`claude-3-5-…`, `claude-sonnet-4-…`) names a model that no longer answers, and the error only shows up when the button is pressed. `claude-opus-5-5` is the current default; `claude-sonnet-5-5` is the cheaper one for a short summary.
|
|
164
156
|
|
|
165
157
|
## Base URLs by integration type
|
|
166
158
|
|
|
167
159
|
Match the `integrationType` from the attached integrations section to find the base URL:
|
|
168
160
|
|
|
169
161
|
| integrationType | Base URL |
|
|
170
|
-
|
|
162
|
+
| --- | --- |
|
|
171
163
|
| ANTHROPIC_AI | `https://api.anthropic.com` |
|
|
172
164
|
| APOLLO | `https://api.apollo.io` |
|
|
173
165
|
| APIFY | `https://api.apify.com` |
|
|
@@ -216,8 +208,7 @@ Match the `integrationType` from the attached integrations section to find the b
|
|
|
216
208
|
| XERO | `https://api.xero.com` |
|
|
217
209
|
| ZOOM | `https://api.zoom.us` |
|
|
218
210
|
|
|
219
|
-
`REST_API` is a custom API, so it has no row here. Each connection has its own base URL, and
|
|
220
|
-
`connect-a-service` says where to read it.
|
|
211
|
+
`REST_API` is a custom API, so it has no row here. Each connection has its own base URL, and `connect-a-service` says where to read it.
|
|
221
212
|
|
|
222
213
|
## When the type is in the table but nothing is attached
|
|
223
214
|
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.85
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
**Boundary.** `softrMcpMiddleware` lives on `@softr/sdk` and is registered once, in `src/start.ts`. The actions you hand it are the same `defineQuery` / `defineMutation` values the app's own pages use — declared wherever they already live, not duplicated for MCP.
|
|
@@ -64,6 +64,7 @@ Connecting starts with an approval page the app serves. Most people hit it signe
|
|
|
64
64
|
**The app's login has to send them back.** Every exit that ends a sign-in, the second-factor step included, goes to `toReturnPath(location)` instead of the home page. See [auth/SKILL.md](../auth/SKILL.md). Lose the search param anywhere along the way and the approval dies quietly: they land on the home page, the assistant never gets its answer, nothing reports an error.
|
|
65
65
|
|
|
66
66
|
`toReturnPath` only ever goes to a path on this app. Never follow the param without it. The whole flow starts with a link someone was sent, so this is the one redirect an attacker can reliably aim.
|
|
67
|
+
|
|
67
68
|
## Failure modes
|
|
68
69
|
|
|
69
70
|
**Registering with no actions.** `softrMcpMiddleware()` is valid and serves an endpoint with nothing on it. The assistant connects, lists tools, finds only `whoami`. Pass the actions.
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.85
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# Installing the app
|
|
@@ -17,6 +17,7 @@ metadata:
|
|
|
17
17
|
The builder turns installing on in Studio, under Settings → Mobile app, and sets the name, colour and icon there. The app already answers with its manifest and icon once they do. What the app adds is two things: the links that point the browser at them, and a button that tells visitors how to install.
|
|
18
18
|
|
|
19
19
|
> **CRITICAL**: Never write a web app manifest, icon files, a service worker, or `manifest` / `apple-touch-icon` links of your own, in `public/` or in `head()`. The app already serves them from Studio's settings, and a file of your own shadows those settings.
|
|
20
|
+
>
|
|
20
21
|
> **CRITICAL**: Render an install button only while `usePwaInstall()` returns `available: true`. Never show it unconditionally, never cache the answer, never replace the check with your own. Softr can switch installing off for an app, and this check is the only way that reaches the button.
|
|
21
22
|
|
|
22
23
|
## Setup
|
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
metadata:
|
|
9
9
|
type: sub-skill
|
|
10
10
|
library: softr-sdk
|
|
11
|
-
library_version: 0.0.
|
|
11
|
+
library_version: 0.0.85
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
**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.
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.85
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
**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. The same holds for any helper that calls `ds`: declare it inside the handler. A function at module level that calls `ds` keeps the server-only import in the browser bundle, and every page stops loading until it moves. `defineQuery` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
metadata:
|
|
9
9
|
type: sub-skill
|
|
10
10
|
library: softr-sdk
|
|
11
|
-
library_version: 0.0.
|
|
11
|
+
library_version: 0.0.85
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
**Boundary.** `defineVisibility`, the access functions and the error predicates come from `@softr/sdk` and work anywhere.
|
|
@@ -63,9 +63,7 @@ defineVisibility({
|
|
|
63
63
|
})
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
Everyone is both audiences, as in the first line above. A set names each audience at most once and
|
|
67
|
-
is never empty — the type and the build both reject one that does not. `customGroups` never stands alone: it narrows the `LOGGED_IN_USERS` half of the set, so the rule still names that half, and one without `userGroupTypes` is a type error. Absent or empty it
|
|
68
|
-
means **any** signed-in visitor, not "nobody".
|
|
66
|
+
Everyone is both audiences, as in the first line above. A set names each audience at most once and is never empty — the type and the build both reject one that does not. `customGroups` never stands alone: it narrows the `LOGGED_IN_USERS` half of the set, so the rule still names that half, and one without `userGroupTypes` is a type error. Absent or empty it means **any** signed-in visitor, not "nobody".
|
|
69
67
|
|
|
70
68
|
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.
|
|
71
69
|
|
|
@@ -88,7 +86,7 @@ export const Route = createFileRoute('/_app')({
|
|
|
88
86
|
|
|
89
87
|
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.
|
|
90
88
|
|
|
91
|
-
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
|
|
89
|
+
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 `customGroups` 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.
|
|
92
90
|
|
|
93
91
|
### Change what a denied visitor gets
|
|
94
92
|
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.85
|
|
13
13
|
---
|
|
14
14
|
|
|
15
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.
|
|
@@ -56,7 +56,7 @@ Write the object inline in `defineWorkflow`. Passing it through a variable widen
|
|
|
56
56
|
These placeholders are resolved server-side when the workflow runs:
|
|
57
57
|
|
|
58
58
|
| Value | Resolves to |
|
|
59
|
-
|
|
59
|
+
| --- | --- |
|
|
60
60
|
| `{LOGGED_IN_USER:EMAIL}` | Signed-in user's email |
|
|
61
61
|
| `{LOGGED_IN_USER:NAME}` | Signed-in user's name |
|
|
62
62
|
| `{LOGGED_IN_USER:RECORD_ID}` | Signed-in user's record id |
|