@softr/sdk 0.0.97-alpha.2 → 0.0.98-anonsession.0

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.
Files changed (42) hide show
  1. package/dist/action.d.ts +1 -45
  2. package/dist/action.js +1 -37
  3. package/dist/auth/context.d.ts +4 -1
  4. package/dist/auth/isomorphic.d.ts +1 -1
  5. package/dist/auth/isomorphic.js +2 -1
  6. package/dist/auth/methods/authenticator.js +3 -12
  7. package/dist/auth/methods/code.js +1 -4
  8. package/dist/auth/methods/emailCode2fa.js +1 -4
  9. package/dist/auth/methods/magicLink.js +1 -4
  10. package/dist/auth/methods/password.js +1 -4
  11. package/dist/auth/methods/passwordLink.js +1 -4
  12. package/dist/auth/methods/signup.js +1 -4
  13. package/dist/auth/methods/social.js +1 -4
  14. package/dist/auth/permissions.d.ts +0 -9
  15. package/dist/auth/platform.js +12 -27
  16. package/dist/default-error-overlay.d.ts +2 -2
  17. package/dist/index.d.ts +2 -3
  18. package/dist/index.js +2 -2
  19. package/dist/package.js +1 -1
  20. package/dist/pwa.d.ts +2 -2
  21. package/dist/server.d.ts +1 -2
  22. package/dist/server.js +1 -2
  23. package/package.json +1 -1
  24. package/skills/softr-sdk/SKILL.md +4 -10
  25. package/skills/softr-sdk/ai/SKILL.md +3 -1
  26. package/skills/softr-sdk/auth/SKILL.md +1 -1
  27. package/skills/softr-sdk/charts-and-metrics/SKILL.md +21 -4
  28. package/skills/softr-sdk/current-user/SKILL.md +16 -3
  29. package/skills/softr-sdk/file-uploads/SKILL.md +1 -1
  30. package/skills/softr-sdk/formatting/SKILL.md +1 -1
  31. package/skills/softr-sdk/integrations/SKILL.md +1 -3
  32. package/skills/softr-sdk/mcp/SKILL.md +1 -1
  33. package/skills/softr-sdk/pwa/SKILL.md +1 -1
  34. package/skills/softr-sdk/record-mutations/SKILL.md +41 -4
  35. package/skills/softr-sdk/record-queries/SKILL.md +132 -36
  36. package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
  37. package/skills/softr-sdk/routes-and-visibility/SKILL.md +2 -2
  38. package/skills/softr-sdk/workflows/SKILL.md +8 -10
  39. package/dist/auth/permissions.js +0 -13
  40. package/dist/core/email.d.ts +0 -48
  41. package/dist/core/email.js +0 -100
  42. package/skills/softr-sdk/email/SKILL.md +0 -136
package/dist/action.d.ts CHANGED
@@ -234,50 +234,6 @@ type IntegrationDefinition<TId extends string> = {
234
234
  * const res = await listEvents({ query: { timeMin: now, singleEvents: "true" } })
235
235
  */
236
236
  declare function defineIntegration<const TId extends string>(definition: IntegrationDefinition<TId>): IntegrationAction<TId>;
237
- type EmailInputs = {
238
- senderId?: string;
239
- to?: ReadonlyArray<string>;
240
- cc?: ReadonlyArray<string>;
241
- bcc?: ReadonlyArray<string>;
242
- replyTo?: string;
243
- };
244
- type EmailResource = {
245
- type: "SOFTR_EMAIL";
246
- inputs: EmailInputs;
247
- };
248
- type EmailAction<TId extends string = string> = Action<EmailResource> & {
249
- id: TId;
250
- };
251
- type EmailDefinition<TId extends string> = EmailInputs & {
252
- id: TId;
253
- permissions: ActionPermissions;
254
- };
255
- /**
256
- * Declares an email action: who may send it, and the fields every email it sends keeps whatever the
257
- * call says. Softr sends it from the app's email sender and bills the workspace's workflow credits.
258
- *
259
- * - `to`, `cc`, `bcc`: addresses. An element may be a whole `{LOGGED_IN_USER:EMAIL}` or
260
- * `{LOGGED_IN_USER:<field>}` placeholder, filled in for the signed-in sender.
261
- * - `replyTo`: an address or one such placeholder.
262
- * - `senderId`: one of the workspace's email senders, for an email that must come from that address.
263
- * Left out, the app's sender is used. The call can never choose the sender.
264
- *
265
- * A field declared here wins over the call's, and declaring `to` also ignores the call's `cc` and
266
- * `bcc`. Fix the recipients whenever the use case allows,
267
- * because anyone in the audience can otherwise send any email to anyone.
268
- *
269
- * Pass the returned object to `email.send()` at runtime.
270
- *
271
- * @example
272
- * const notifyOwner = defineEmailAction({
273
- * id: "notifyOwner",
274
- * to: ["owner@acme.com"],
275
- * permissions: { userGroupTypes: ["NON_LOGGED_IN_USERS", "LOGGED_IN_USERS"] },
276
- * })
277
- *
278
- * await email.send(notifyOwner, { subject: "New submission", body: html, replyTo: data.email })
279
- */
280
- declare function defineEmailAction<const TId extends string>(definition: EmailDefinition<TId>): EmailAction<TId>;
281
237
  type AiResource = {
282
238
  type: "SOFTR_AI";
283
239
  model: string;
@@ -312,4 +268,4 @@ type AiDefinition<TId extends string> = {
312
268
  */
313
269
  declare function defineAiAction<const TId extends string>(definition: AiDefinition<TId>): AiAction<TId>;
314
270
  //#endregion
315
- export { ActionData, ActionPermissions, ActionRecord, ActionShape, AiAction, DatasourceAction, EmailAction, FieldTypeOf, IntegrationAction, ReadOf, ReadShape, SoftrDatasources, WorkflowAction, defineAiAction, defineEmailAction, defineIntegration, defineMutation, defineQuery, defineWorkflow };
271
+ export { ActionData, ActionPermissions, ActionRecord, ActionShape, AiAction, DatasourceAction, FieldTypeOf, IntegrationAction, ReadOf, ReadShape, SoftrDatasources, WorkflowAction, defineAiAction, defineIntegration, defineMutation, defineQuery, defineWorkflow };
package/dist/action.js CHANGED
@@ -50,42 +50,6 @@ function defineIntegration(definition) {
50
50
  permissions
51
51
  };
52
52
  }
53
- /**
54
- * Declares an email action: who may send it, and the fields every email it sends keeps whatever the
55
- * call says. Softr sends it from the app's email sender and bills the workspace's workflow credits.
56
- *
57
- * - `to`, `cc`, `bcc`: addresses. An element may be a whole `{LOGGED_IN_USER:EMAIL}` or
58
- * `{LOGGED_IN_USER:<field>}` placeholder, filled in for the signed-in sender.
59
- * - `replyTo`: an address or one such placeholder.
60
- * - `senderId`: one of the workspace's email senders, for an email that must come from that address.
61
- * Left out, the app's sender is used. The call can never choose the sender.
62
- *
63
- * A field declared here wins over the call's, and declaring `to` also ignores the call's `cc` and
64
- * `bcc`. Fix the recipients whenever the use case allows,
65
- * because anyone in the audience can otherwise send any email to anyone.
66
- *
67
- * Pass the returned object to `email.send()` at runtime.
68
- *
69
- * @example
70
- * const notifyOwner = defineEmailAction({
71
- * id: "notifyOwner",
72
- * to: ["owner@acme.com"],
73
- * permissions: { userGroupTypes: ["NON_LOGGED_IN_USERS", "LOGGED_IN_USERS"] },
74
- * })
75
- *
76
- * await email.send(notifyOwner, { subject: "New submission", body: html, replyTo: data.email })
77
- */
78
- function defineEmailAction(definition) {
79
- const { id, permissions,...inputs } = definition;
80
- return {
81
- id,
82
- resource: {
83
- type: "SOFTR_EMAIL",
84
- inputs
85
- },
86
- permissions
87
- };
88
- }
89
53
  function isDatasourceAction(value) {
90
54
  if (typeof value !== "object" || value === null) return false;
91
55
  const candidate = value;
@@ -105,4 +69,4 @@ function defineAiAction(definition) {
105
69
  }
106
70
 
107
71
  //#endregion
108
- export { defineAiAction, defineEmailAction, defineIntegration, defineMutation, defineQuery, defineWorkflow, isDatasourceAction };
72
+ export { defineAiAction, defineIntegration, defineMutation, defineQuery, defineWorkflow, isDatasourceAction };
@@ -7,7 +7,10 @@ import { Permissions } from "./permissions.js";
7
7
  type Session = {
8
8
  /** The visitor, or `null` when signed out. */
9
9
  user: User | null;
10
- /** The visitor's global data restrictions. An escape hatch; the SDK already checks them. */
10
+ /**
11
+ * @deprecated Always empty. Softr checks every data request against the
12
+ * action's own `permissions`.
13
+ */
11
14
  permissions: Permissions;
12
15
  };
13
16
  /**
@@ -8,7 +8,7 @@ import * as _tanstack_react_start0 from "@tanstack/react-start";
8
8
  //#region src/auth/isomorphic.d.ts
9
9
 
10
10
  /**
11
- * Resolves the Softr visitor, their permissions and the app's formatting for
11
+ * Resolves the Softr visitor and the app's formatting for
12
12
  * every request, so that `getSoftrContext` and the route guards have something
13
13
  * to read.
14
14
  *
@@ -19,7 +19,7 @@ import { jsx } from "react/jsx-runtime";
19
19
 
20
20
  //#region src/auth/isomorphic.tsx
21
21
  /**
22
- * Resolves the Softr visitor, their permissions and the app's formatting for
22
+ * Resolves the Softr visitor and the app's formatting for
23
23
  * every request, so that `getSoftrContext` and the route guards have something
24
24
  * to read.
25
25
  *
@@ -42,6 +42,7 @@ function softrAuthMiddleware() {
42
42
  ]).server(async ({ next }) => {
43
43
  async function getSession() {
44
44
  const token = readSessionToken();
45
+ if (token === null) return { user: null };
45
46
  if (process.env.NODE_ENV === "production") {
46
47
  const cached = readSessionData(token);
47
48
  if (cached !== null) return cached;
@@ -46,10 +46,7 @@ async function confirmAuthenticatorSetup(details) {
46
46
  throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows an authenticator app.`);
47
47
  }
48
48
  const { session, backupCodes } = result.data;
49
- writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
50
- user: session.user,
51
- permissions: session.permissions
52
- }, session.jwt)]);
49
+ writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({ user: session.user }, session.jwt)]);
53
50
  clearPendingAuth();
54
51
  return {
55
52
  success: true,
@@ -75,10 +72,7 @@ async function skipAuthenticatorSetup() {
75
72
  }
76
73
  const { session } = result.data;
77
74
  assert(session, "Expected a session from skipping setup, but got none");
78
- writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
79
- user: session.user,
80
- permissions: session.permissions
81
- }, session.jwt)]);
75
+ writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({ user: session.user }, session.jwt)]);
82
76
  clearPendingAuth();
83
77
  return {
84
78
  success: true,
@@ -101,10 +95,7 @@ async function verifyAuthenticator(credentials) {
101
95
  }
102
96
  const { session } = result.data;
103
97
  assert(session, "Expected a session from verification, but got none");
104
- writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
105
- user: session.user,
106
- permissions: session.permissions
107
- }, session.jwt)]);
98
+ writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({ user: session.user }, session.jwt)]);
108
99
  clearPendingAuth();
109
100
  return {
110
101
  success: true,
@@ -56,10 +56,7 @@ async function signInWithCode(credentials) {
56
56
  }
57
57
  const { session } = result.data;
58
58
  assert(session, "Expected a session from sign-in, but got none");
59
- writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
60
- user: session.user,
61
- permissions: session.permissions
62
- }, session.jwt)]);
59
+ writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({ user: session.user }, session.jwt)]);
63
60
  clearPendingAuth();
64
61
  return {
65
62
  success: true,
@@ -36,10 +36,7 @@ async function verifyEmailCode(details) {
36
36
  }
37
37
  const { session } = result.data;
38
38
  assert(session, "Expected a session from verification, but got none");
39
- writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
40
- user: session.user,
41
- permissions: session.permissions
42
- }, session.jwt)]);
39
+ writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({ user: session.user }, session.jwt)]);
43
40
  clearPendingAuth();
44
41
  return {
45
42
  success: true,
@@ -36,10 +36,7 @@ async function signInWithMagicLink(credentials) {
36
36
  }
37
37
  const { session } = result.data;
38
38
  assert(session, "Expected a session from magic-link sign-in, but got none");
39
- writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
40
- user: session.user,
41
- permissions: session.permissions
42
- }, session.jwt)]);
39
+ writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({ user: session.user }, session.jwt)]);
43
40
  return {
44
41
  success: true,
45
42
  data: {
@@ -40,10 +40,7 @@ async function signIn(credentials) {
40
40
  }
41
41
  const { session } = result.data;
42
42
  assert(session, "Expected a session from sign-in, but got none");
43
- writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
44
- user: session.user,
45
- permissions: session.permissions
46
- }, session.jwt)]);
43
+ writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({ user: session.user }, session.jwt)]);
47
44
  clearPendingAuth();
48
45
  return {
49
46
  success: true,
@@ -68,10 +68,7 @@ async function setPasswordWithLink(details) {
68
68
  }
69
69
  const { session } = result.data;
70
70
  assert(session, "Expected a session from setting a password, but got none");
71
- writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
72
- user: session.user,
73
- permissions: session.permissions
74
- }, session.jwt)]);
71
+ writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({ user: session.user }, session.jwt)]);
75
72
  clearPendingAuth();
76
73
  return {
77
74
  success: true,
@@ -66,10 +66,7 @@ async function confirmSignUp(details) {
66
66
  }
67
67
  const { session } = result.data;
68
68
  assert(session, "Expected a session from sign-up, but got none");
69
- writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
70
- user: session.user,
71
- permissions: session.permissions
72
- }, session.jwt)]);
69
+ writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({ user: session.user }, session.jwt)]);
73
70
  clearPendingAuth();
74
71
  return {
75
72
  success: true,
@@ -41,10 +41,7 @@ async function signInWithGoogle(credentials) {
41
41
  }
42
42
  const { session } = result.data;
43
43
  assert(session, "Expected a session from Google sign-in, but got none");
44
- writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
45
- user: session.user,
46
- permissions: session.permissions
47
- }, session.jwt)]);
44
+ writeCookies([...makeSessionTokenCookie(session.jwt), makeSessionDataCookie({ user: session.user }, session.jwt)]);
48
45
  clearPendingAuth();
49
46
  return {
50
47
  success: true,
@@ -1,13 +1,4 @@
1
- import { z } from "zod";
2
-
3
1
  //#region src/auth/permissions.d.ts
4
-
5
- /**
6
- * What the visitor may do in this app, as the platform sees it.
7
- *
8
- * Rendering hints, not enforcement: every data request is authorized again
9
- * server-side, and these may go stale between the two.
10
- */
11
2
  type DatasourcePermissions = {
12
3
  read: boolean;
13
4
  create: boolean;
@@ -2,7 +2,6 @@ import { assert, panic } from "../assertions.js";
2
2
  import { softrApiUrl } from "../softr-api.js";
3
3
  import { fetch } from "../fetch.js";
4
4
  import { softrConfig } from "../softr-config.js";
5
- import { permissionsSchema } from "./permissions.js";
6
5
  import { z } from "zod";
7
6
 
8
7
  //#region src/auth/platform.ts
@@ -61,10 +60,7 @@ function makePlatform(applicationId) {
61
60
  const result = await request(`/v1/vibe/applications/${applicationId}/users/session${withAttributes}`, {
62
61
  method: "GET",
63
62
  token
64
- }, z.object({
65
- user: userSchema.nullable(),
66
- permissions: permissionsSchema
67
- }));
63
+ }, z.object({ user: userSchema.nullable() }));
68
64
  if (result.status === 404) panic(`Application ${applicationId} not found. Check SOFTR_APPLICATION_ID.`);
69
65
  return result;
70
66
  },
@@ -75,8 +71,7 @@ function makePlatform(applicationId) {
75
71
  status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
76
72
  session: z.object({
77
73
  jwt: z.string(),
78
- user: userSchema,
79
- permissions: permissionsSchema
74
+ user: userSchema
80
75
  }).nullish(),
81
76
  mfa: z.object({
82
77
  action: z.enum([
@@ -96,8 +91,7 @@ function makePlatform(applicationId) {
96
91
  status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
97
92
  session: z.object({
98
93
  jwt: z.string(),
99
- user: userSchema,
100
- permissions: permissionsSchema
94
+ user: userSchema
101
95
  }).nullish(),
102
96
  mfa: z.object({
103
97
  action: z.enum([
@@ -123,8 +117,7 @@ function makePlatform(applicationId) {
123
117
  status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
124
118
  session: z.object({
125
119
  jwt: z.string(),
126
- user: userSchema,
127
- permissions: permissionsSchema
120
+ user: userSchema
128
121
  }).nullish(),
129
122
  mfa: z.object({
130
123
  action: z.enum([
@@ -142,8 +135,7 @@ function makePlatform(applicationId) {
142
135
  status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
143
136
  session: z.object({
144
137
  jwt: z.string(),
145
- user: userSchema,
146
- permissions: permissionsSchema
138
+ user: userSchema
147
139
  }).nullish(),
148
140
  mfa: z.object({
149
141
  action: z.enum([
@@ -161,8 +153,7 @@ function makePlatform(applicationId) {
161
153
  status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
162
154
  session: z.object({
163
155
  jwt: z.string(),
164
- user: userSchema,
165
- permissions: permissionsSchema
156
+ user: userSchema
166
157
  }).nullish(),
167
158
  mfa: z.object({
168
159
  action: z.enum([
@@ -188,8 +179,7 @@ function makePlatform(applicationId) {
188
179
  status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
189
180
  session: z.object({
190
181
  jwt: z.string(),
191
- user: userSchema,
192
- permissions: permissionsSchema
182
+ user: userSchema
193
183
  }).nullish(),
194
184
  mfa: z.object({
195
185
  action: z.enum([
@@ -211,8 +201,7 @@ function makePlatform(applicationId) {
211
201
  status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
212
202
  session: z.object({
213
203
  jwt: z.string(),
214
- user: userSchema,
215
- permissions: permissionsSchema
204
+ user: userSchema
216
205
  }).nullish(),
217
206
  mfa: z.object({
218
207
  action: z.enum([
@@ -230,8 +219,7 @@ function makePlatform(applicationId) {
230
219
  status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
231
220
  session: z.object({
232
221
  jwt: z.string(),
233
- user: userSchema,
234
- permissions: permissionsSchema
222
+ user: userSchema
235
223
  }).nullish(),
236
224
  mfa: z.object({
237
225
  action: z.enum([
@@ -256,8 +244,7 @@ function makePlatform(applicationId) {
256
244
  }, z.object({
257
245
  session: z.object({
258
246
  jwt: z.string(),
259
- user: userSchema,
260
- permissions: permissionsSchema
247
+ user: userSchema
261
248
  }),
262
249
  backupCodes: z.array(z.string())
263
250
  })),
@@ -268,8 +255,7 @@ function makePlatform(applicationId) {
268
255
  status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
269
256
  session: z.object({
270
257
  jwt: z.string(),
271
- user: userSchema,
272
- permissions: permissionsSchema
258
+ user: userSchema
273
259
  }).nullish(),
274
260
  mfa: z.object({
275
261
  action: z.enum([
@@ -288,8 +274,7 @@ function makePlatform(applicationId) {
288
274
  status: z.enum(["AUTHENTICATED", "MFA_REQUIRED"]),
289
275
  session: z.object({
290
276
  jwt: z.string(),
291
- user: userSchema,
292
- permissions: permissionsSchema
277
+ user: userSchema
293
278
  }).nullish(),
294
279
  mfa: z.object({
295
280
  action: z.enum([
@@ -1,4 +1,4 @@
1
- import * as react6 from "react";
1
+ import * as react4 from "react";
2
2
 
3
3
  //#region src/default-error-overlay.d.ts
4
4
  type Props = {
@@ -8,6 +8,6 @@ type Props = {
8
8
  declare function DefaultErrorOverlay({
9
9
  error,
10
10
  reset
11
- }: Props): react6.JSX.Element;
11
+ }: Props): react4.JSX.Element;
12
12
  //#endregion
13
13
  export { DefaultErrorOverlay };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { SoftrUserGroupId, SoftrUserGroups, SoftrUserProperties, User } from "./auth/user.js";
2
- import { ActionPermissions, SoftrDatasources, defineAiAction, defineEmailAction, defineIntegration, defineMutation, defineQuery, defineWorkflow } from "./action.js";
2
+ import { ActionPermissions, SoftrDatasources, defineAiAction, defineIntegration, defineMutation, defineQuery, defineWorkflow } from "./action.js";
3
3
  import { FileAttachment } from "./core/file-upload.js";
4
4
  import { FileItem, useFileUpload } from "./core/use-file-upload.js";
5
5
  import { AiAnswer, AiFile, AiGenerateInit, AiMessage, AiUsage } from "./core/ai.js";
6
- import { EmailSendInit, EmailSent } from "./core/email.js";
7
6
  import { DateOptions, DateParts, DateSeparator, DateValue, FixedDateFormat, FormattedDate, Formatting, FormattingScope, WeekDay, formatDate, formatDateTime, formatNumber, useFormatting } from "./formatting.js";
8
7
  import { SoftrContext } from "./auth/context.js";
9
8
  import { AuthSettings } from "./auth/platform.js";
@@ -19,4 +18,4 @@ import { useUserTimezone } from "./timezone.js";
19
18
  import { PwaInstall, PwaPlatform, SoftrPwaHead, usePwaInstall } from "./pwa.js";
20
19
  import { DefaultErrorOverlay } from "./default-error-overlay.js";
21
20
  import { Audience } from "./route/audience.js";
22
- export { type ActionPermissions, type AiAnswer, type AiFile, type AiGenerateInit, type AiMessage, type AiUsage, Audience, type AuthOptions, type AuthSettings, AuthSettingsProvider, type DateOptions, type DateParts, type DateSeparator, type DateValue, DefaultErrorOverlay, type EmailSendInit, type EmailSent, type FieldShape, type FileAttachment, type FileItem, type FixedDateFormat, FormattedDate, type Formatting, FormattingScope, type Page, type PageSettings, type PwaInstall, type PwaPlatform, type SoftrContext, type SoftrDatasources, type SoftrFieldRegistry, SoftrPwaHead, type SoftrUserGroupId, type SoftrUserGroups, type SoftrUserProperties, type User, type WeekDay, assertRouteAccess, checkAccess, defineAiAction, defineEmailAction, defineIntegration, defineMutation, definePage, defineQuery, defineVisibility, defineWorkflow, fieldShape, formatDate, formatDateTime, formatDuration, formatField, formatNumber, formatValue, getAuthSettings, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, pageHead, registerSoftrFields, softrAuthMiddleware, softrMcpMiddleware, toReturnPath, useAuthSettings, useCurrentUser, useFileUpload, useFormatting, useOverrides, usePwaInstall, useUserTimezone };
21
+ export { type ActionPermissions, type AiAnswer, type AiFile, type AiGenerateInit, type AiMessage, type AiUsage, Audience, type AuthOptions, type AuthSettings, AuthSettingsProvider, type DateOptions, type DateParts, type DateSeparator, type DateValue, DefaultErrorOverlay, type FieldShape, type FileAttachment, type FileItem, type FixedDateFormat, FormattedDate, type Formatting, FormattingScope, type Page, type PageSettings, type PwaInstall, type PwaPlatform, type SoftrContext, type SoftrDatasources, type SoftrFieldRegistry, SoftrPwaHead, type SoftrUserGroupId, type SoftrUserGroups, type SoftrUserProperties, type User, type WeekDay, assertRouteAccess, checkAccess, defineAiAction, defineIntegration, defineMutation, definePage, defineQuery, defineVisibility, defineWorkflow, fieldShape, formatDate, formatDateTime, formatDuration, formatField, formatNumber, formatValue, getAuthSettings, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, pageHead, registerSoftrFields, softrAuthMiddleware, softrMcpMiddleware, toReturnPath, useAuthSettings, useCurrentUser, useFileUpload, useFormatting, useOverrides, usePwaInstall, useUserTimezone };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { defineAiAction, defineEmailAction, defineIntegration, defineMutation, defineQuery, defineWorkflow } from "./action.js";
1
+ import { defineAiAction, defineIntegration, defineMutation, defineQuery, defineWorkflow } from "./action.js";
2
2
  import { useFileUpload } from "./core/use-file-upload.js";
3
3
  import { checkAccess, defineVisibility, isAuthorized } from "./route/visibility.js";
4
4
  import { assertRouteAccess, guardRoute, isAccessError, isAlreadyAuthenticatedError, isForbiddenError, isUnauthenticatedError } from "./route/route.js";
@@ -14,4 +14,4 @@ import { useUserTimezone } from "./timezone.js";
14
14
  import { DefaultErrorOverlay } from "./default-error-overlay.js";
15
15
  import { Audience } from "./route/audience.js";
16
16
 
17
- export { Audience, AuthSettingsProvider, DefaultErrorOverlay, FormattedDate, FormattingScope, SoftrPwaHead, assertRouteAccess, checkAccess, defineAiAction, defineEmailAction, defineIntegration, defineMutation, definePage, defineQuery, defineVisibility, defineWorkflow, fieldShape, formatDate, formatDateTime, formatDuration, formatField, formatNumber, formatValue, getAuthSettings, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, pageHead, registerSoftrFields, softrAuthMiddleware, softrMcpMiddleware, toReturnPath, useAuthSettings, useCurrentUser, useFileUpload, useFormatting, useOverrides, usePwaInstall, useUserTimezone };
17
+ export { Audience, AuthSettingsProvider, DefaultErrorOverlay, FormattedDate, FormattingScope, SoftrPwaHead, assertRouteAccess, checkAccess, defineAiAction, defineIntegration, defineMutation, definePage, defineQuery, defineVisibility, defineWorkflow, fieldShape, formatDate, formatDateTime, formatDuration, formatField, formatNumber, formatValue, getAuthSettings, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, pageHead, registerSoftrFields, softrAuthMiddleware, softrMcpMiddleware, toReturnPath, useAuthSettings, useCurrentUser, useFileUpload, useFormatting, useOverrides, usePwaInstall, useUserTimezone };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.0.97-alpha.2";
2
+ var version = "0.0.98-anonsession.0";
3
3
 
4
4
  //#endregion
5
5
  export { version };
package/dist/pwa.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as react4 from "react";
1
+ import * as react5 from "react";
2
2
  import "@tanstack/react-start";
3
3
 
4
4
  //#region src/pwa.d.ts
@@ -23,7 +23,7 @@ import "@tanstack/react-start";
23
23
  * )
24
24
  * }
25
25
  */
26
- declare function SoftrPwaHead(): react4.FunctionComponentElement<react4.FragmentProps>;
26
+ declare function SoftrPwaHead(): react5.FunctionComponentElement<react5.FragmentProps>;
27
27
  type PwaPlatform = "ios" | "android" | "desktop";
28
28
  type PwaInstall = {
29
29
  available: false;
package/dist/server.d.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  import { upload } from "./core/file-upload.js";
2
2
  import { AiError, ai } from "./core/ai.js";
3
- import { EmailError, EmailErrorCode, email } from "./core/email.js";
4
3
  import { auth } from "./auth/server.js";
5
4
  import { FieldChoice, RecordOption, RecordOptions } from "./core/options.js";
6
5
  import { ds } from "./datasource/datasource.js";
7
6
  import { workflow } from "./core/workflow.js";
8
7
  import { integration } from "./core/integration.js";
9
8
  import "@tanstack/react-start/server-only";
10
- export { AiError, EmailError, type EmailErrorCode, type FieldChoice, type RecordOption, type RecordOptions, ai, auth, ds, email, integration, upload, workflow };
9
+ export { AiError, type FieldChoice, type RecordOption, type RecordOptions, ai, auth, ds, integration, upload, workflow };
package/dist/server.js CHANGED
@@ -4,7 +4,6 @@ import { auth } from "./auth/server.js";
4
4
  import { workflow } from "./core/workflow.js";
5
5
  import { integration } from "./core/integration.js";
6
6
  import { AiError, ai } from "./core/ai.js";
7
- import { EmailError, email } from "./core/email.js";
8
7
  import "@tanstack/react-start/server-only";
9
8
 
10
- export { AiError, EmailError, ai, auth, ds, email, integration, upload, workflow };
9
+ export { AiError, ai, auth, ds, integration, upload, workflow };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softr/sdk",
3
- "version": "0.0.97-alpha.2",
3
+ "version": "0.0.98-anonsession.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
@@ -9,7 +9,7 @@ description: >-
9
9
  metadata:
10
10
  type: core
11
11
  library: softr-sdk
12
- library_version: 0.0.97-alpha.2
12
+ library_version: 0.0.98-anonsession.0
13
13
  ---
14
14
 
15
15
  # Softr SDK core
@@ -22,8 +22,6 @@ metadata:
22
22
  >
23
23
  > **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.
24
24
  >
25
- > **CRITICAL**: `email.send` lives on `@softr/sdk/server` and only runs inside a `createServerFn` handler. Use `defineEmailAction` from `@softr/sdk` to declare who may send it and the recipients it fixes. An email the app sends itself needs no workflow and no connected mail service. Load the `softr-sdk/email` sub-skill for the full pattern.
26
- >
27
25
  > **CRITICAL**: `ai.generate` and `ai.stream` live on `@softr/sdk/server` and only run inside a `createServerFn` handler. Use `defineAiAction` from `@softr/sdk` to declare the action with the model it runs on, an id copied from the `ai_list_models` tool. Softr runs the model on its own keys; never ask the user for an API key for the app's own AI feature. Load the `softr-sdk/ai` sub-skill for the full pattern.
28
26
  >
29
27
  > **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.
@@ -34,8 +32,8 @@ Each sub-skill stands alone for its task and repeats the boundary above, so most
34
32
 
35
33
  | Import | Runs | Holds |
36
34
  | --- | --- | --- |
37
- | `@softr/sdk` | Both | `defineQuery`, `defineMutation`, `defineEmailAction`, `defineAiAction`, `definePage`, `pageHead`, `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 |
38
- | `@softr/sdk/server` | Server only | `ds`, `auth`, `email`, `EmailError`, `workflow`, `integration`, `ai` |
35
+ | `@softr/sdk` | Both | `defineQuery`, `defineMutation`, `defineAiAction`, `definePage`, `pageHead`, `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 |
36
+ | `@softr/sdk/server` | Server only | `ds`, `auth`, `workflow`, `integration`, `ai` |
39
37
  | `@softr/sdk/client` | Browser only | `useFileUpload` |
40
38
  | `@softr/sdk/vite` | Build | `softr()`, already in `vite.config.ts` |
41
39
 
@@ -49,11 +47,10 @@ Each sub-skill stands alone for its task and repeats the boundary above, so most
49
47
  | Fetch records from a datasource | [record-queries/SKILL.md](./record-queries/SKILL.md) |
50
48
  | Chart, aggregate or count records | [charts-and-metrics/SKILL.md](./charts-and-metrics/SKILL.md) |
51
49
  | Create, update or delete a record | [record-mutations/SKILL.md](./record-mutations/SKILL.md) |
52
- | Send an email from the app: a form that emails the owner, a confirmation, staff emailing a client | [email/SKILL.md](./email/SKILL.md) |
53
50
  | Trigger a Softr workflow | [workflows/SKILL.md](./workflows/SKILL.md) |
54
51
  | Sign a visitor in, out, or up | [auth/SKILL.md](./auth/SKILL.md) |
55
52
  | Ask an AI model: a summary, an extraction from a document, a classification, a draft, a chat | [ai/SKILL.md](./ai/SKILL.md) |
56
- | Call a connected third-party API (Slack, Stripe, Gmail from the user's own mailbox, …), or an AI model on the user's own provider account | [integrations/SKILL.md](./integrations/SKILL.md) |
53
+ | Call a connected third-party API (Slack, Stripe, Gmail, …), or an AI model on the user's own provider account | [integrations/SKILL.md](./integrations/SKILL.md) |
57
54
  | Open the app to AI assistants over MCP | [mcp/SKILL.md](./mcp/SKILL.md) |
58
55
  | Let visitors install the app on their home screen | [pwa/SKILL.md](./pwa/SKILL.md) |
59
56
  | Show a date, a time or a number | [formatting/SKILL.md](./formatting/SKILL.md) |
@@ -79,9 +76,6 @@ Drawing a chart, a stat tile, or any aggregated number?
79
76
  Submitting a form, editing a record, or deleting one?
80
77
  load softr-sdk/record-mutations
81
78
 
82
- Sending an email when a form is submitted or a button is pressed?
83
- load softr-sdk/email — no workflow, no connected mail service
84
-
85
79
  Triggering a Softr workflow from a button or form?
86
80
  load softr-sdk/workflows
87
81
 
@@ -13,7 +13,7 @@ description: >-
13
13
  metadata:
14
14
  type: sub-skill
15
15
  library: softr-sdk
16
- library_version: 0.0.97-alpha.2
16
+ library_version: 0.0.98-anonsession.0
17
17
  ---
18
18
 
19
19
  **Boundary.** `ai` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineAiAction` and every type come from `@softr/sdk` and can be imported anywhere.
@@ -102,6 +102,8 @@ const summarize = createServerFn({ method: 'POST' })
102
102
  })
103
103
  ```
104
104
 
105
+ The record id comes from the browser, so it can name any deal. The read of `dealQuery` takes the table's scope, like every other read of the table. Where reps see only their own deals, a read without it briefs anyone on any deal: see [record-queries](../record-queries/SKILL.md#keep-each-visitor-to-their-own-records).
106
+
105
107
  ## Extraction: an answer that is data
106
108
 
107
109
  A document dropped on the app, fields pulled out of it, a record created — the model reads the file and answers in the shape the record needs. Give it the schema and the file; validate the parsed answer before writing it, since a model may stray from the shape.
@@ -9,7 +9,7 @@ description: >-
9
9
  metadata:
10
10
  type: sub-skill
11
11
  library: softr-sdk
12
- library_version: 0.0.97-alpha.2
12
+ library_version: 0.0.98-anonsession.0
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.
@@ -3,13 +3,13 @@ name: charts-and-metrics
3
3
  description: >-
4
4
  Aggregate records in a server function: ds.chart with groupBy, metrics, where,
5
5
  orderBy and date buckets for chart rows, ds.metric for one number, and
6
- distinct values for filter controls. Use for a chart, a stat tile or any
7
- counted, summed or averaged number, or when a chart comes back wrong. Single
8
- records are record-queries.
6
+ distinct values for filter controls, scoped to the records the visitor may
7
+ read. Use for a chart, a stat tile or any counted, summed or averaged number,
8
+ or when a chart comes back wrong. Single records are record-queries.
9
9
  metadata:
10
10
  type: sub-skill
11
11
  library: softr-sdk
12
- library_version: 0.0.97-alpha.2
12
+ library_version: 0.0.98-anonsession.0
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.
@@ -166,6 +166,23 @@ const loadDashboard = createServerFn().handler(async () => {
166
166
 
167
167
  Each tile and panel is its own request, fired in parallel from one handler.
168
168
 
169
+ ### Scope a figure like the records behind it
170
+
171
+ A chart or a metric reads the same records as a list, so it takes the same scope as every read of the table (see [record-queries](../record-queries/SKILL.md#keep-each-visitor-to-their-own-records)). Without it, a "my revenue" tile adds up everyone's sales, and a chart grouped by a person shows the names of people the visitor may not see. Here a rep sees only their own sales, and `usersQuery` is the app's query on the Users table:
172
+
173
+ ```ts
174
+ import { auth, ds } from '@softr/sdk/server'
175
+
176
+ const myRevenue = createServerFn().handler(async ({ context }) => {
177
+ const me = await auth.currentUserRow(context.user, usersQuery, 'email')
178
+ if (me === null) return null
179
+ const { value } = await ds
180
+ .metric(sales, (q) => q.amount.sum)
181
+ .where((q) => q.rep.isOneOf([me.id]))
182
+ return value
183
+ })
184
+ ```
185
+
169
186
  ## Order of calls
170
187
 
171
188
  `groupBy` comes first and exactly once; a chart cannot be awaited before it. `metrics` follows, at most once, or not at all for distinct values. `where` can be chained anywhere, repeatedly, and calling it more than once ands the filters together. `orderBy` sorts by the chart's own keys, group and metric alike, and only sees the keys declared so far, so call it last.