@softr/sdk 0.0.17 → 0.0.19

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.
@@ -68,7 +68,7 @@ const getSoftrContext = createServerFn({ method: "GET" }).handler(async () => {
68
68
  function initialSoftrContext() {
69
69
  return {
70
70
  user: null,
71
- permissions: { datasourcePermissions: {} }
71
+ permissions: { datasources: {} }
72
72
  };
73
73
  }
74
74
  /**
@@ -105,7 +105,7 @@ function useCurrentUser() {
105
105
  function usePermissions(datasourceId) {
106
106
  return useRouteContext({
107
107
  from: rootRouteId,
108
- select: (context) => requireDefined(context.permissions.datasourcePermissions[datasourceId], `the application has no datasource "${datasourceId}"`)
108
+ select: (context) => requireDefined(context.permissions.datasources[datasourceId], `the application has no datasource "${datasourceId}"`)
109
109
  });
110
110
  }
111
111
 
@@ -11,7 +11,7 @@ import { z } from "zod";
11
11
  */
12
12
  type Permissions = {
13
13
  /** Keyed by datasource id. */
14
- datasourcePermissions: Record<string, DatasourcePermissions>;
14
+ datasources: Record<string, DatasourcePermissions>;
15
15
  };
16
16
  //#endregion
17
17
  export { Permissions };
@@ -7,7 +7,7 @@ const datasourcePermissionsSchema = z.object({
7
7
  update: z.boolean(),
8
8
  delete: z.boolean()
9
9
  });
10
- const permissionsSchema = z.object({ datasourcePermissions: z.record(z.string(), datasourcePermissionsSchema) });
10
+ const permissionsSchema = z.object({ datasources: z.record(z.string(), datasourcePermissionsSchema) });
11
11
 
12
12
  //#endregion
13
13
  export { permissionsSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softr/sdk",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
@@ -5,12 +5,12 @@ description: >-
5
5
  four entrypoints (@softr/sdk, /server, /client, /vite) and the client-server
6
6
  boundary between them, softrAuthMiddleware, getSoftrContext,
7
7
  initialSoftrContext, SoftrContext, and which sub-skill to load for route
8
- visibility, the current user, reading records, or auth. Load this before any
9
- other Softr SDK skill.
8
+ visibility, the current user, reading records, writing records, or auth. Load
9
+ this before any other Softr SDK skill.
10
10
  metadata:
11
11
  type: core
12
12
  library: softr-sdk
13
- library_version: 0.0.17
13
+ library_version: 0.0.19
14
14
  ---
15
15
 
16
16
  # Softr SDK core
@@ -20,6 +20,7 @@ metadata:
20
20
  > **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.
21
21
  > **CRITICAL**: Every route file must declare its audience with `defineVisibility`. There is no default and nothing is inherited from a sibling. A route file without a declaration is a lint error.
22
22
  > **CRITICAL**: The app is already wired. `src/start.ts`, `src/router.tsx` and `src/routes/__root.tsx` register the middleware and resolve the session. Do not add a second registration.
23
+ > **CRITICAL**: `ds.metric`, `ds.chartData`, `workflow.trigger` and `integration.fetch` are exploratory. They exist on the entrypoints below and they typecheck, but their endpoints are not final. Do not build a feature on any of them. Tell the user the capability is not ready and ask how they want to proceed.
23
24
 
24
25
  Load this skill, then load **one** sub-skill for the task at hand. Add a second only when the work genuinely crosses that boundary. A page that lists records behind a group restriction needs `reading-records` plus `routes-and-visibility`.
25
26
 
@@ -39,6 +40,7 @@ Load this skill, then load **one** sub-skill for the task at hand. Add a second
39
40
  | Add a route, or change who can see one | [routes-and-visibility/SKILL.md](./routes-and-visibility/SKILL.md) |
40
41
  | Read the signed-in visitor or what they may do | [current-user/SKILL.md](./current-user/SKILL.md) |
41
42
  | Fetch records from a datasource | [reading-records/SKILL.md](./reading-records/SKILL.md) |
43
+ | Create, update or delete a record | [data-mutations/SKILL.md](./data-mutations/SKILL.md) |
42
44
  | Sign a visitor in, out, or up | [auth/SKILL.md](./auth/SKILL.md) |
43
45
 
44
46
  ## Quick Decision Tree
@@ -53,6 +55,9 @@ Showing the visitor's name, or hiding UI they cannot use?
53
55
  Putting records on a page, in a table, a list or a detail view?
54
56
  load softr-sdk/reading-records
55
57
 
58
+ Submitting a form, editing a record, or deleting one?
59
+ load softr-sdk/data-mutations
60
+
56
61
  Wiring a login, signup, or sign-out?
57
62
  load softr-sdk/auth
58
63
 
@@ -105,7 +110,7 @@ export function getRouter() {
105
110
  ```ts
106
111
  type SoftrContext = {
107
112
  user: User | null // null means signed out
108
- permissions: { datasourcePermissions: Record<string, DatasourcePermissions> }
113
+ permissions: { datasources: Record<string, DatasourcePermissions> }
109
114
  }
110
115
 
111
116
  type User = {
@@ -118,4 +123,4 @@ type User = {
118
123
 
119
124
  ## Version
120
125
 
121
- Targets `@softr/sdk` v0.0.17.
126
+ Targets `@softr/sdk` v0.0.19.
@@ -10,7 +10,7 @@ description: >-
10
10
  metadata:
11
11
  type: sub-skill
12
12
  library: softr-sdk
13
- library_version: 0.0.17
13
+ library_version: 0.0.19
14
14
  ---
15
15
 
16
16
  This skill builds on softr-sdk. Read it first for the entrypoints and the client/server boundary.
@@ -10,7 +10,7 @@ description: >-
10
10
  metadata:
11
11
  type: sub-skill
12
12
  library: softr-sdk
13
- library_version: 0.0.17
13
+ library_version: 0.0.19
14
14
  ---
15
15
 
16
16
  This skill builds on softr-sdk. Read it first for the entrypoints and the wiring that puts a visitor on route context.
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: data-mutations
3
+ description: >-
4
+ Create, update and delete records in a Softr datasource from inside a server
5
+ function: ds.create, ds.update and ds.delete from @softr/sdk/server, the data
6
+ payload key, select field aliasing on the way in and out, and what each call
7
+ returns or throws. Writes only. Reads, metrics and chart data are not covered.
8
+ metadata:
9
+ type: sub-skill
10
+ library: softr-sdk
11
+ library_version: 0.0.19
12
+ ---
13
+
14
+ This skill builds on softr-sdk. Read it first for the entrypoints and the client/server boundary.
15
+
16
+ # Writing records
17
+
18
+ `ds` writes records to a Softr datasource. Like every read, a write lives on `@softr/sdk/server` and runs on the server only, so it happens inside a `createServerFn` handler and never in a component.
19
+
20
+ ## Setup
21
+
22
+ ```ts
23
+ import { createServerFn } from '@tanstack/react-start'
24
+ import { z } from 'zod'
25
+ import { ds } from '@softr/sdk/server'
26
+ import { q } from '@softr/sdk'
27
+ ```
28
+
29
+ Pass a zod schema straight to `.validator`. Start accepts it and types `data` from it.
30
+
31
+ ## Core Patterns
32
+
33
+ ### Create a record
34
+
35
+ ```ts
36
+ const NewProject = z.object({ name: z.string(), status: z.string() })
37
+
38
+ const createProject = createServerFn({ method: 'POST' })
39
+ .validator(NewProject)
40
+ .handler(async ({ data }) =>
41
+ ds.create('ds_projects', {
42
+ data,
43
+ select: q.select({ name: 'fld_name', status: 'fld_status' }),
44
+ }),
45
+ )
46
+ ```
47
+
48
+ Returns the created record as `{ id, fields }`.
49
+
50
+ **The payload key is `data`, not `fields`.** A record answers with `fields`, which is where the confusion comes from, but the option you pass is `data`. `CreateOptions` requires it, so a call keyed by `fields` does not type-check.
51
+
52
+ **`data` speaks whatever `select` maps.** The keys above are the schema's own names because `select` maps them to field ids, which is why the validated input can go straight through. With no `select`, `data` has to be keyed by raw field ids instead.
53
+
54
+ ### Update a record
55
+
56
+ ```ts
57
+ const Rename = z.object({ id: z.string(), name: z.string() })
58
+
59
+ const renameProject = createServerFn({ method: 'POST' })
60
+ .validator(Rename)
61
+ .handler(async ({ data }) =>
62
+ ds.update('ds_projects', {
63
+ id: data.id,
64
+ data: { name: data.name },
65
+ select: q.select({ name: 'fld_name' }),
66
+ }),
67
+ )
68
+ ```
69
+
70
+ The record id is its own option, so keep it out of `data`.
71
+
72
+ Returns the updated record. `update` writes the keys in `data` and leaves every other field alone, so a field you leave out is a field you are not writing. An empty `id` throws.
73
+
74
+ ### Delete a record
75
+
76
+ ```ts
77
+ const RecordId = z.object({ id: z.string() })
78
+
79
+ const deleteProject = createServerFn({ method: 'POST' })
80
+ .validator(RecordId)
81
+ .handler(async ({ data }) => ds.delete('ds_projects', data.id))
82
+ ```
83
+
84
+ **`delete` takes the record id as a positional argument**, unlike `create` and `update`, which take an options object. It resolves to nothing.
85
+
86
+ ### Naming fields with `select`
87
+
88
+ `select` works in both directions on a write. Going in, it turns your names into Softr field ids. Coming back, it turns the returned field ids into your names. Without it, `data` has to be keyed by raw field ids and the returned record answers with them too.
89
+
90
+ The map has to cover every key in `data`. A key it does not map is not translated, so the write does not land on the field you meant.
91
+
92
+ ### Writing on behalf of the visitor
93
+
94
+ Stamping a record with who created it, or scoping an update to the person making it, reads the visitor off the handler's `context`. That is [current-user](../current-user/SKILL.md)'s subject, including the null guest and how Start types it. Load that skill rather than looking for it here.
95
+
96
+ ## Failure Modes
97
+
98
+ **A write that sends `fields`.** The option is `data`. Only typecheck catches this, so it survives any run that does not typecheck.
99
+
100
+ **A key missing from `select`.** The key is not translated, so the field it was meant for is not the field written. Check the map covers every key rather than trusting the call to complain.
101
+
102
+ **A write with no `select` and your own key names.** Without a map, `data` must be keyed by raw field ids.
103
+
104
+ **Passing an options object to `delete`.** The second argument is the id itself. `delete` throws when the record cannot be deleted.
105
+
106
+ **Expecting `select` on a read-then-write round trip.** `ds.get` takes no `select`, so a record fetched by id answers with raw field ids. Load `reading-records` before pairing a read with a write.
107
+
108
+ **A write outside a server function.** `ds` is server-only. Importing it into a component fails the build.
109
+
110
+ **Reading the visitor from somewhere other than `context`.** There is nothing to import. See [current-user](../current-user/SKILL.md).
111
+
112
+ ## References
113
+
114
+ - [reading-records/SKILL.md](../reading-records/SKILL.md): fetching the record a write is about to change
115
+ - [current-user/SKILL.md](../current-user/SKILL.md): reading the visitor off `context` to stamp or scope a write
@@ -10,7 +10,7 @@ description: >-
10
10
  metadata:
11
11
  type: sub-skill
12
12
  library: softr-sdk
13
- library_version: 0.0.17
13
+ library_version: 0.0.19
14
14
  ---
15
15
 
16
16
  This skill builds on softr-sdk. Read it first for the entrypoints and the client/server boundary.
@@ -12,7 +12,7 @@ description: >-
12
12
  metadata:
13
13
  type: sub-skill
14
14
  library: softr-sdk
15
- library_version: 0.0.17
15
+ library_version: 0.0.19
16
16
  ---
17
17
 
18
18
  This skill builds on softr-sdk. Read it first for the entrypoints and the wiring that puts a visitor on route context.