@softr/sdk 0.0.68-props.0 → 0.0.68
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/current-user-row.d.ts +20 -0
- package/dist/auth/current-user-row.js +22 -0
- package/dist/auth/platform.d.ts +1 -0
- package/dist/auth/platform.js +13 -33
- package/dist/auth/server.d.ts +2 -0
- package/dist/auth/server.js +3 -1
- package/dist/auth/session.js +1 -1
- package/dist/auth/user.d.ts +1 -8
- package/dist/auth/user.js +1 -2
- package/dist/cli.js +18 -26
- package/dist/codegen/datasource-docs.js +1 -93
- package/dist/codegen/datasource-types.js +1 -16
- package/dist/codegen/fetch-metadata.js +17 -44
- package/dist/codegen/softr-env.js +1 -2
- package/dist/codegen/softr-fields.js +41 -0
- package/dist/cookies.js +2 -10
- package/dist/core/options.d.ts +36 -0
- package/dist/core/options.js +39 -0
- package/dist/core/use-overrides.d.ts +17 -0
- package/dist/core/use-overrides.js +30 -0
- package/dist/datasource/datasource.d.ts +2 -0
- package/dist/datasource/datasource.js +3 -1
- package/dist/datasource/fields.js +3 -2
- package/dist/datasource/record.d.ts +2 -1
- package/dist/fields/format.d.ts +45 -0
- package/dist/fields/format.js +140 -0
- package/dist/formatting.d.ts +4 -4
- package/dist/formatting.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/mcp.js +2 -2
- package/dist/package.js +1 -1
- package/dist/pwa.d.ts +2 -2
- package/dist/pwa.js +1 -1
- package/dist/server.d.ts +2 -1
- package/dist/softr-config.schema.js +24 -45
- package/dist/visibility/route.js +1 -1
- package/dist/vite.js +19 -1
- package/package.json +10 -33
- package/schema/softr.schema.json +0 -13
- package/skills/softr-sdk/SKILL.md +2 -2
- package/skills/softr-sdk/auth/SKILL.md +1 -1
- package/skills/softr-sdk/charts-and-metrics/SKILL.md +1 -1
- package/skills/softr-sdk/current-user/SKILL.md +3 -24
- package/skills/softr-sdk/file-uploads/SKILL.md +1 -1
- package/skills/softr-sdk/formatting/SKILL.md +19 -1
- package/skills/softr-sdk/integrations/SKILL.md +1 -1
- package/skills/softr-sdk/mcp/SKILL.md +1 -1
- package/skills/softr-sdk/pwa/SKILL.md +1 -1
- package/skills/softr-sdk/record-mutations/SKILL.md +13 -1
- package/skills/softr-sdk/record-queries/SKILL.md +21 -1
- package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +1 -1
- package/skills/softr-sdk/workflows/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -1,37 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softr/sdk",
|
|
3
|
-
"version": "0.0.68
|
|
3
|
+
"version": "0.0.68",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
|
-
".": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"./server": {
|
|
15
|
-
"types": "./dist/server.d.ts",
|
|
16
|
-
"import": "./dist/server.js"
|
|
17
|
-
},
|
|
18
|
-
"./vite": {
|
|
19
|
-
"types": "./dist/vite.d.ts",
|
|
20
|
-
"import": "./dist/vite.js"
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
"bin": {
|
|
24
|
-
"softr-sdk": "./dist/cli.js"
|
|
25
|
-
},
|
|
26
|
-
"files": [
|
|
27
|
-
"dist",
|
|
28
|
-
"schema",
|
|
29
|
-
"skills",
|
|
30
|
-
"!skills/_artifacts"
|
|
31
|
-
],
|
|
32
|
-
"publishConfig": {
|
|
33
|
-
"access": "public"
|
|
34
|
-
},
|
|
6
|
+
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
|
|
7
|
+
"./client": { "types": "./dist/client.d.ts", "import": "./dist/client.js" },
|
|
8
|
+
"./server": { "types": "./dist/server.d.ts", "import": "./dist/server.js" },
|
|
9
|
+
"./vite": { "types": "./dist/vite.d.ts", "import": "./dist/vite.js" }
|
|
10
|
+
},
|
|
11
|
+
"bin": { "softr-sdk": "./dist/cli.js" },
|
|
12
|
+
"files": ["dist", "schema", "skills", "!skills/_artifacts"],
|
|
13
|
+
"publishConfig": { "access": "public" },
|
|
35
14
|
"scripts": {
|
|
36
15
|
"build": "tsdown",
|
|
37
16
|
"test": "node --import ./src/test-utils/setup.ts --test",
|
|
@@ -63,9 +42,7 @@
|
|
|
63
42
|
"optional": true
|
|
64
43
|
}
|
|
65
44
|
},
|
|
66
|
-
"keywords": [
|
|
67
|
-
"tanstack-intent"
|
|
68
|
-
],
|
|
45
|
+
"keywords": ["tanstack-intent"],
|
|
69
46
|
"intent": {
|
|
70
47
|
"version": 1,
|
|
71
48
|
"repo": "@softr/sdk",
|
package/schema/softr.schema.json
CHANGED
|
@@ -54,19 +54,6 @@
|
|
|
54
54
|
},
|
|
55
55
|
"required": ["routes", "guard"],
|
|
56
56
|
"additionalProperties": false
|
|
57
|
-
},
|
|
58
|
-
"user": {
|
|
59
|
-
"type": "object",
|
|
60
|
-
"description": "The signed-in user.",
|
|
61
|
-
"properties": {
|
|
62
|
-
"properties": {
|
|
63
|
-
"type": "object",
|
|
64
|
-
"description": "Fields of the users table the app reads as user.properties, keyed by the name the app uses. Each value is a field id. No other field leaves the server.",
|
|
65
|
-
"additionalProperties": { "type": "string" }
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"required": ["properties"],
|
|
69
|
-
"additionalProperties": false
|
|
70
57
|
}
|
|
71
58
|
},
|
|
72
59
|
"required": ["auth"],
|
|
@@ -12,7 +12,7 @@ description: >-
|
|
|
12
12
|
metadata:
|
|
13
13
|
type: core
|
|
14
14
|
library: softr-sdk
|
|
15
|
-
library_version: 0.0.68
|
|
15
|
+
library_version: 0.0.68
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
# Softr SDK core
|
|
@@ -30,7 +30,7 @@ Each sub-skill stands alone for its task and repeats the boundary above, so most
|
|
|
30
30
|
|
|
31
31
|
| Import | Runs | Holds |
|
|
32
32
|
| --- | --- | --- |
|
|
33
|
-
| `@softr/sdk` | Both | `defineQuery`, `defineMutation`, `defineVisibility`, `guardRoute`, `assertRouteAccess`, `isAuthorized`, `checkAccess`, `Audience`, `useCurrentUser`, `useUserTimezone`, `FormattedDate`, `FormattingScope`, `useFormatting`, `formatDate`, `formatDateTime`, `formatNumber`, `SoftrPwaHead`, `usePwaInstall`, `getSoftrContext`, `initialSoftrContext`, `softrAuthMiddleware`, the error predicates, and every type |
|
|
33
|
+
| `@softr/sdk` | Both | `defineQuery`, `defineMutation`, `defineVisibility`, `guardRoute`, `assertRouteAccess`, `isAuthorized`, `checkAccess`, `Audience`, `useCurrentUser`, `useUserTimezone`, `FormattedDate`, `FormattingScope`, `useFormatting`, `formatDate`, `formatDateTime`, `formatNumber`, `formatValue`, `formatField`, `fieldShape`, `registerSoftrFields`, `useOverrides`, `SoftrPwaHead`, `usePwaInstall`, `getSoftrContext`, `initialSoftrContext`, `softrAuthMiddleware`, the error predicates, and every type |
|
|
34
34
|
| `@softr/sdk/server` | Server only | `ds`, `auth`, `workflow`, `integration` |
|
|
35
35
|
| `@softr/sdk/client` | Browser only | Nothing yet, reserved for browser-only utilities |
|
|
36
36
|
| `@softr/sdk/vite` | Build | `softr()`, already in `vite.config.ts` |
|
|
@@ -19,7 +19,7 @@ description: >-
|
|
|
19
19
|
metadata:
|
|
20
20
|
type: sub-skill
|
|
21
21
|
library: softr-sdk
|
|
22
|
-
library_version: 0.0.68
|
|
22
|
+
library_version: 0.0.68
|
|
23
23
|
---
|
|
24
24
|
|
|
25
25
|
**Boundary.** `auth` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build.
|
|
@@ -12,7 +12,7 @@ description: >-
|
|
|
12
12
|
metadata:
|
|
13
13
|
type: sub-skill
|
|
14
14
|
library: softr-sdk
|
|
15
|
-
library_version: 0.0.68
|
|
15
|
+
library_version: 0.0.68
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
**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.
|
|
@@ -4,13 +4,12 @@ description: >-
|
|
|
4
4
|
Read the signed-in Softr visitor: useCurrentUser and the User type,
|
|
5
5
|
context.user in a route's beforeLoad, and context.user inside a server
|
|
6
6
|
function handler, where Start types it from the app's registered request
|
|
7
|
-
middleware. Covers the null guest
|
|
8
|
-
group ids to the app's own
|
|
9
|
-
row that softr.json declares.
|
|
7
|
+
middleware. Covers the null guest and the generated SoftrUserGroups that
|
|
8
|
+
narrows group ids to the app's own.
|
|
10
9
|
metadata:
|
|
11
10
|
type: sub-skill
|
|
12
11
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.68
|
|
12
|
+
library_version: 0.0.68
|
|
14
13
|
---
|
|
15
14
|
|
|
16
15
|
# The current user
|
|
@@ -32,7 +31,6 @@ type User = {
|
|
|
32
31
|
email: string
|
|
33
32
|
fullName: string
|
|
34
33
|
userGroupIds: Array<SoftrUserGroupId> // the app's own group ids
|
|
35
|
-
properties: SoftrUserProperties // the aliases softr.json declares
|
|
36
34
|
}
|
|
37
35
|
```
|
|
38
36
|
|
|
@@ -92,25 +90,6 @@ For every group's name and id, read `docs/user-groups.md`. It lists every group
|
|
|
92
90
|
|
|
93
91
|
`user.userGroupIds` and every `customGroups` the SDK accepts take only those ids, so an id the app does not have stops compiling. A new group appears in the list once it exists on the app.
|
|
94
92
|
|
|
95
|
-
### Fields of the visitor's row: `user.properties`
|
|
96
|
-
|
|
97
|
-
To read more of the visitor's row in the users table, give each field an alias in `softr.json`, under `user.properties`. The value is the field's id:
|
|
98
|
-
|
|
99
|
-
```json
|
|
100
|
-
{
|
|
101
|
-
"user": {
|
|
102
|
-
"properties": {
|
|
103
|
-
"plan": "OeIz1",
|
|
104
|
-
"seats": "1iHKE"
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
`user.properties` then holds exactly those aliases. To read them, see `docs/user-properties.md`.
|
|
111
|
-
|
|
112
|
-
Attachment and long text fields, and linked record and lookup fields with multiple entries, cannot be properties. Read those with `defineQuery` on the users table. See [record-queries](../record-queries/SKILL.md).
|
|
113
|
-
|
|
114
93
|
## References
|
|
115
94
|
|
|
116
95
|
- [routes-and-visibility/SKILL.md](../routes-and-visibility/SKILL.md): `isAuthorized` narrows `context.user` while applying the route's rule
|
|
@@ -10,7 +10,7 @@ description: >-
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: sub-skill
|
|
12
12
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.68
|
|
13
|
+
library_version: 0.0.68
|
|
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.
|
|
@@ -10,7 +10,7 @@ description: >-
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: sub-skill
|
|
12
12
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.68
|
|
13
|
+
library_version: 0.0.68
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
# Dates, times and numbers
|
|
@@ -116,6 +116,24 @@ The currency comes from the field or from the user, never from the locale. A Ger
|
|
|
116
116
|
|
|
117
117
|
`formatDate` and `formatDateTime` show a timestamp in UTC while `formatting.timeZone` is `null`. In JSX, prefer `<FormattedDate />`, which waits instead.
|
|
118
118
|
|
|
119
|
+
### A field's own text: `formatValue`
|
|
120
|
+
|
|
121
|
+
A value a query read already knows how it should look: money with its symbol and cents, a percent, a rating out of its scale, a duration in hours and minutes, a date in the app's format, a choice or a linked record by its label, a checkbox as Yes or No, several values joined by a comma, `''` for nothing. `formatValue` reads the field's type and options from the generated `src/softr-fields.ts` and does all of it, so a page never branches on a type itself.
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { formatValue, useFormatting } from '@softr/sdk'
|
|
125
|
+
import { dealsQuery } from '#/server/deals.ts'
|
|
126
|
+
|
|
127
|
+
function Amount({ deal }: { deal: Deal }) {
|
|
128
|
+
const formatting = useFormatting()
|
|
129
|
+
return <span>{formatValue(dealsQuery, 'amount', deal.fields.amount, formatting)}</span>
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The second argument is the alias the query holds the field under. `formatField(shape, value, formatting)` does the same for a shape in hand, and `fieldShape(query, alias)` reads one.
|
|
134
|
+
|
|
135
|
+
The SDK's codegen writes `src/softr-fields.ts` beside `softr-env.d.ts` after every schema change; it registers the shapes as a side effect of being imported, and the root route imports it once. `formatValue` throws when that import is missing.
|
|
136
|
+
|
|
119
137
|
### Outside render: a server function or `beforeLoad`
|
|
120
138
|
|
|
121
139
|
Not every date ends up in a component. A CSV or spreadsheet export, a text the server sends, or a value a loader prepares are formatted before React runs. There, `context.formatting` holds the same `Formatting` that `useFormatting()` returns. See [current-user](../current-user/SKILL.md) for how `context` reaches a handler.
|
|
@@ -10,7 +10,7 @@ description: >-
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: sub-skill
|
|
12
12
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.68
|
|
13
|
+
library_version: 0.0.68
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
**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.
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.68
|
|
12
|
+
library_version: 0.0.68
|
|
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.
|
|
@@ -11,7 +11,7 @@ description: >-
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: sub-skill
|
|
13
13
|
library: softr-sdk
|
|
14
|
-
library_version: 0.0.68
|
|
14
|
+
library_version: 0.0.68
|
|
15
15
|
---
|
|
16
16
|
|
|
17
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.
|
|
@@ -135,6 +135,18 @@ await ds.update(updateProject, {
|
|
|
135
135
|
|
|
136
136
|
A read answers a SELECT as `{ id, label }` and a write takes `{ id }`, so pass the id through rather than the whole value.
|
|
137
137
|
|
|
138
|
+
### Show the edit before the loader re-runs
|
|
139
|
+
|
|
140
|
+
A list whose rows a drawer or an inline control edits keeps the saved values over the loader's rows until a fresh read replaces them. `useOverrides` holds them by record id:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
const overrides = useOverrides<Partial<Deal['fields']>>()
|
|
144
|
+
const rows = deals.map((deal) => ({ ...deal, fields: { ...deal.fields, ...overrides.of(deal.id) } }))
|
|
145
|
+
<DealsDrawer onSaved={overrides.onSaved} />
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`onSaved(recordId, patch)` is what a drawer calls before the write and again with the old value if the write fails; `clear(recordId)` drops a record's patch once the page has reloaded it.
|
|
149
|
+
|
|
138
150
|
### Form dropdowns for linked record fields
|
|
139
151
|
|
|
140
152
|
A form select for a `LINKED_RECORD` field needs its options from the linked table, not from the record being edited. Query the linked table with its own `defineQuery` and `ds.list`; each result's `id` is what you pass to the write payload. See [record-queries](../record-queries/SKILL.md).
|
|
@@ -13,7 +13,7 @@ description: >-
|
|
|
13
13
|
metadata:
|
|
14
14
|
type: sub-skill
|
|
15
15
|
library: softr-sdk
|
|
16
|
-
library_version: 0.0.68
|
|
16
|
+
library_version: 0.0.68
|
|
17
17
|
---
|
|
18
18
|
|
|
19
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. 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.
|
|
@@ -146,6 +146,18 @@ const listMyInvoices = createServerFn().handler(async ({ context }) => {
|
|
|
146
146
|
})
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
+
When the owner is a link to the Users table, the row to match is the visitor's own row there, not `context.user.id`. `auth.currentUserRow(context.user, usersQuery, 'email')` finds it by email, or answers `null` when they are signed out or have no row:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { auth, ds } from '@softr/sdk/server'
|
|
153
|
+
|
|
154
|
+
const listMyDeals = createServerFn().handler(async ({ context }) => {
|
|
155
|
+
const me = await auth.currentUserRow(context.user, usersQuery, 'email')
|
|
156
|
+
if (me === null) return { records: [], nextOffset: null }
|
|
157
|
+
return await ds.list(deals).where((q) => q.owner.isOneOf([me.id]))
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
149
161
|
The SDK enforces `permissions`, so a visitor outside the declared audience never reaches the records. Which of those records belong to one visitor is a separate question, and the `where` above answers it: a group says nothing about who owns a row, so a per-visitor read filters on `context.user`. See [current-user](../current-user/SKILL.md) for how the visitor gets onto `context`.
|
|
150
162
|
|
|
151
163
|
### Populate a control from a linked table
|
|
@@ -178,6 +190,14 @@ Each returned record is one option: `record.id` is the value a `where` filter an
|
|
|
178
190
|
ds.list(products).where((q) => q.status.is(selectedStatusId))
|
|
179
191
|
```
|
|
180
192
|
|
|
193
|
+
For a picker that searches and pages, `ds.options` answers the shape a linked-record picker's `load` takes, `{ options: [{ id, label }], nextOffset }`, searched on the label field when a term is given:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const loadProductStatuses = createServerFn()
|
|
197
|
+
.validator(z.object({ term: z.string(), offset: z.number().nullable() }))
|
|
198
|
+
.handler(({ data }) => ds.options(productStatuses, { label: 'name', ...data }))
|
|
199
|
+
```
|
|
200
|
+
|
|
181
201
|
Skip this request when the options are predefined and fixed — hardcode them instead.
|
|
182
202
|
|
|
183
203
|
A read the datasource turns down throws rather than coming back empty, and it throws a plain `Error` carrying the status, followed by the reason the datasource gives when it sends one. There is no typed predicate to narrow it with. Handle it where you would handle any other failed fetch: an `errorComponent` on the route, or a check in the handler.
|
|
@@ -13,7 +13,7 @@ description: >-
|
|
|
13
13
|
metadata:
|
|
14
14
|
type: sub-skill
|
|
15
15
|
library: softr-sdk
|
|
16
|
-
library_version: 0.0.68
|
|
16
|
+
library_version: 0.0.68
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
**Boundary.** `defineVisibility`, the access functions and the error predicates come from `@softr/sdk` and work anywhere.
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.68
|
|
12
|
+
library_version: 0.0.68
|
|
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.
|