@softr/sdk 0.0.64-alpha.0 → 0.0.64

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 (40) hide show
  1. package/dist/auth/isomorphic.d.ts +1 -60
  2. package/dist/auth/isomorphic.js +9 -65
  3. package/dist/auth/methods/signup.js +0 -1
  4. package/dist/auth/methods/social.js +0 -1
  5. package/dist/auth/methods/utils.d.ts +1 -1
  6. package/dist/auth/platform.js +0 -54
  7. package/dist/auth/server.d.ts +0 -6
  8. package/dist/auth/server.js +1 -7
  9. package/dist/default-error-overlay.d.ts +2 -2
  10. package/dist/index.d.ts +3 -4
  11. package/dist/index.js +3 -3
  12. package/dist/mcp.d.ts +8 -2
  13. package/dist/mcp.js +8 -8
  14. package/dist/package.js +1 -1
  15. package/dist/pwa.d.ts +2 -2
  16. package/dist/softr-config.js +5 -26
  17. package/dist/visibility/route.d.ts +6 -26
  18. package/dist/visibility/route.js +13 -36
  19. package/package.json +10 -33
  20. package/schema/softr.schema.json +1 -42
  21. package/skills/softr-sdk/SKILL.md +3 -4
  22. package/skills/softr-sdk/auth/SKILL.md +31 -129
  23. package/skills/softr-sdk/charts-and-metrics/SKILL.md +1 -1
  24. package/skills/softr-sdk/current-user/SKILL.md +1 -1
  25. package/skills/softr-sdk/file-uploads/SKILL.md +1 -1
  26. package/skills/softr-sdk/formatting/SKILL.md +4 -4
  27. package/skills/softr-sdk/integrations/SKILL.md +1 -1
  28. package/skills/softr-sdk/mcp/SKILL.md +14 -7
  29. package/skills/softr-sdk/pwa/SKILL.md +3 -3
  30. package/skills/softr-sdk/record-mutations/SKILL.md +1 -1
  31. package/skills/softr-sdk/record-queries/SKILL.md +1 -1
  32. package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
  33. package/skills/softr-sdk/routes-and-visibility/SKILL.md +12 -24
  34. package/skills/softr-sdk/workflows/SKILL.md +1 -1
  35. package/dist/auth/methods/passwordLink.d.ts +0 -47
  36. package/dist/auth/methods/passwordLink.js +0 -86
  37. package/dist/auth/methods/settings.d.ts +0 -11
  38. package/dist/auth/methods/settings.js +0 -17
  39. package/dist/auth/platform.d.ts +0 -46
  40. package/dist/softr-config.d.ts +0 -1
@@ -9,7 +9,7 @@ description: >-
9
9
  metadata:
10
10
  type: sub-skill
11
11
  library: softr-sdk
12
- library_version: 0.0.64-alpha.0
12
+ library_version: 0.0.64
13
13
  ---
14
14
 
15
15
  **Boundary.** `Audience` and `defineVisibility` come from `@softr/sdk` and work anywhere.
@@ -13,7 +13,7 @@ description: >-
13
13
  metadata:
14
14
  type: sub-skill
15
15
  library: softr-sdk
16
- library_version: 0.0.64-alpha.0
16
+ library_version: 0.0.64
17
17
  ---
18
18
 
19
19
  **Boundary.** `defineVisibility`, the access functions and the error predicates come from `@softr/sdk` and work anywhere.
@@ -115,12 +115,6 @@ Every app declares where a denied visitor goes in `softr.json`, at the app root,
115
115
  {
116
116
  "$schema": "./node_modules/@softr/sdk/schema/softr.schema.json",
117
117
  "auth": {
118
- "routes": {
119
- "login": { "path": "/login", "redirectParam": "redirect" },
120
- "resetPassword": { "path": "/reset-password", "tokenParam": "token" },
121
- "acceptInvitation": { "path": "/accept-invitation", "tokenParam": "token" },
122
- "magicLink": { "path": "/magic-authentication", "tokenParam": "magic-token" }
123
- },
124
118
  "guard": {
125
119
  "unauthenticated": "/login",
126
120
  "alreadyAuthenticated": "/",
@@ -130,17 +124,13 @@ Every app declares where a denied visitor goes in `softr.json`, at the app root,
130
124
  }
131
125
  ```
132
126
 
133
- Every key is required, because nothing fills in one the file leaves out:
127
+ All three reasons are required, because nothing fills in a reason the file leaves out:
134
128
 
135
- | Reason | Condition | Goes to |
136
- | --- | --- | --- |
137
- | `unauthenticated` | Route needs a visitor, none present | `guard.unauthenticated` |
138
- | `forbidden` | Signed in, in none of the named groups | `guard.forbidden` |
139
- | `alreadyAuthenticated` | Signed-in visitor on a signed-out-only route | `guard.alreadyAuthenticated` |
140
-
141
- `guard.unauthenticated` is where signed-out visitors go. It is usually the same as `routes.login.path`, but it can be another page, such as a public landing page. Every `unauthenticated` redirect, an override's included, carries the page they wanted under `routes.login.redirectParam`. The login page reads it back with `authRedirectTarget`. See [auth/SKILL.md](../auth/SKILL.md).
142
-
143
- `auth.routes` names the app's sign-in pages. To move one, rename its route file and edit its `path` in `auth.routes` to match. When `guard.unauthenticated` points at the login page, edit it too.
129
+ | Reason | Condition |
130
+ | --- | --- |
131
+ | `unauthenticated` | Route needs a visitor, none present |
132
+ | `forbidden` | Signed in, in none of the named groups |
133
+ | `alreadyAuthenticated` | Signed-in visitor on a signed-out-only route |
144
134
 
145
135
  That file covers every route at once; a route's own overrides win over it. A rule naming both audiences with no `customGroups` admits everyone, so it never redirects and never throws. Only `strategy: 'throw'` reaches an `errorComponent`. A redirect navigates away and the route never renders.
146
136
 
@@ -172,22 +162,20 @@ Use the predicates: `isAccessError` for any of the three, and `isUnauthenticated
172
162
 
173
163
  ### Run the check and then keep going
174
164
 
175
- `guardRoute` covers almost everything. When a route needs its own logic afterwards, `assertRouteAccess(visibility, opts)` applies the same rule to the whole `beforeLoad` argument and throws the same access error, using the default behaviour for each reason. It takes no overrides.
165
+ `guardRoute` covers almost everything. When a route needs its own logic afterwards, `assertRouteAccess(visibility, context)` applies the same rule and throws the same access error, using the default behaviour for each reason. It takes no overrides.
176
166
 
177
- It also narrows `opts.context.user` for the rest of the handler, where the set names one audience and no other: `User` under `['LOGGED_IN_USERS']`, `null` under `['NON_LOGGED_IN_USERS']`. A set naming both admits either kind of visitor, so it narrows to neither. `guardRoute` cannot, because it hands `beforeLoad` a function rather than running inside one.
167
+ It also narrows `context.user` for the rest of the handler, where the set names one audience and no other: `User` under `['LOGGED_IN_USERS']`, `null` under `['NON_LOGGED_IN_USERS']`. A set naming both admits either kind of visitor, so it narrows to neither. `guardRoute` cannot, because it hands `beforeLoad` a function rather than running inside one.
178
168
 
179
169
  ```ts
180
170
  import { redirect } from '@tanstack/react-router'
181
171
  import { assertRouteAccess } from '@softr/sdk'
182
172
 
183
- beforeLoad: (opts) => {
184
- assertRouteAccess(visibility, opts)
185
- if (opts.context.user.email.endsWith('@example.com')) throw redirect({ to: '/internal' })
173
+ beforeLoad: ({ context }) => {
174
+ assertRouteAccess(visibility, context)
175
+ if (context.user.email.endsWith('@example.com')) throw redirect({ to: '/internal' })
186
176
  },
187
177
  ```
188
178
 
189
- Take `opts` whole. A `context` destructured in the parameter list does not narrow.
190
-
191
179
  Reach for `assertRouteAccess` when the route wants the error thrown for it, `isAuthorized(visibility, context)` when it wants the same narrowing without a throw, and `checkAccess(visibility, context)` when it needs to branch on why. `checkAccess` returns `{ ok: true } | { ok: false, reason }` and throws nothing. All of them are synchronous and make no request.
192
180
 
193
181
  `guardRoute` and `assertRouteAccess` throw router redirects, so they belong in a route and nowhere else. That is what `Route` in their names marks. `isAuthorized` and `checkAccess` throw nothing and answer anywhere, in a loader, a component or a server function.
@@ -9,7 +9,7 @@ description: >-
9
9
  metadata:
10
10
  type: sub-skill
11
11
  library: softr-sdk
12
- library_version: 0.0.64-alpha.0
12
+ library_version: 0.0.64
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.
@@ -1,47 +0,0 @@
1
- import { User } from "../user.js";
2
- import { AuthError, Result } from "./utils.js";
3
-
4
- //#region src/auth/methods/passwordLink.d.ts
5
-
6
- /**
7
- * Emails a link to set a new password.
8
- *
9
- * Succeeds whether or not the address has an account, and sends nothing when
10
- * it has none.
11
- */
12
- declare function requestPasswordReset(details: {
13
- email: string;
14
- }): Promise<Result<null, AuthError>>;
15
- /**
16
- * Checks a password reset or invitation link, and returns the address it was
17
- * mailed to. The link stays usable.
18
- */
19
- declare function checkPasswordLink(details: {
20
- token: string;
21
- }): Promise<Result<{
22
- email: string;
23
- }, AuthError>>;
24
- /**
25
- * Sets a new password with a password reset or invitation link, and signs the
26
- * visitor in.
27
- *
28
- * Any success uses up the link, `authenticatorRequired` included, and ends
29
- * the visitor's other sessions.
30
- */
31
- declare function setPasswordWithLink(details: {
32
- token: string;
33
- password: string;
34
- }): Promise<Result<{
35
- status: "signedIn";
36
- user: User;
37
- } | {
38
- status: "authenticatorRequired";
39
- /**
40
- * `verify`: they have an authenticator app and owe a code.
41
- * `setup`: the app requires one and they have none yet.
42
- * `setupOptional`: same, except they may skip it instead.
43
- */
44
- step: "verify" | "setup" | "setupOptional";
45
- }, AuthError>>;
46
- //#endregion
47
- export { checkPasswordLink, requestPasswordReset, setPasswordWithLink };
@@ -1,86 +0,0 @@
1
- import { assert, panic } from "../../assertions.js";
2
- import { writeCookies } from "../../cookies.js";
3
- import { sessionConfig } from "../config.js";
4
- import { clearPendingAuth, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
5
- import { makePlatform } from "../platform.js";
6
- import { authError } from "./utils.js";
7
-
8
- //#region src/auth/methods/passwordLink.ts
9
- /**
10
- * Emails a link to set a new password.
11
- *
12
- * Succeeds whether or not the address has an account, and sends nothing when
13
- * it has none.
14
- */
15
- async function requestPasswordReset(details) {
16
- const result = await makePlatform(sessionConfig().applicationId).forgotPassword(details);
17
- if (!result.ok) {
18
- if (result.status === 429) return authError("tooManyAttempts", result.message);
19
- panic(`Expected the password reset link to be emailed, but got ${result.status}: ${result.message}.`);
20
- }
21
- return {
22
- success: true,
23
- data: null
24
- };
25
- }
26
- /**
27
- * Checks a password reset or invitation link, and returns the address it was
28
- * mailed to. The link stays usable.
29
- */
30
- async function checkPasswordLink(details) {
31
- const result = await makePlatform(sessionConfig().applicationId).checkPasswordToken(details);
32
- if (!result.ok) {
33
- if (result.status === 429) return authError("tooManyAttempts", result.message);
34
- if (result.status === 400 || result.status === 401) return authError("linkExpired", result.message);
35
- panic(`Expected the link to be checked, but got ${result.status}: ${result.message}.`);
36
- }
37
- return {
38
- success: true,
39
- data: result.data
40
- };
41
- }
42
- /**
43
- * Sets a new password with a password reset or invitation link, and signs the
44
- * visitor in.
45
- *
46
- * Any success uses up the link, `authenticatorRequired` included, and ends
47
- * the visitor's other sessions.
48
- */
49
- async function setPasswordWithLink(details) {
50
- const result = await makePlatform(sessionConfig().applicationId).setPassword(details);
51
- if (!result.ok) {
52
- if (result.status === 429) return authError("tooManyAttempts", result.message);
53
- if (result.status === 400 && result.message === "Password must be at least 8 characters long.") return authError("invalidDetails", result.message);
54
- if (result.status === 400 || result.status === 401) return authError("linkExpired", result.message);
55
- panic(`Expected the password to be set, but got ${result.status}: ${result.message}.`);
56
- }
57
- if (result.data.status === "MFA_REQUIRED") {
58
- const { mfa } = result.data;
59
- assert(mfa, "Expected a challenge from setting a password, but got none");
60
- writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
61
- return {
62
- success: true,
63
- data: {
64
- status: "authenticatorRequired",
65
- step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
66
- }
67
- };
68
- }
69
- const { session } = result.data;
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)]);
75
- clearPendingAuth();
76
- return {
77
- success: true,
78
- data: {
79
- status: "signedIn",
80
- user: session.user
81
- }
82
- };
83
- }
84
-
85
- //#endregion
86
- export { checkPasswordLink, requestPasswordReset, setPasswordWithLink };
@@ -1,11 +0,0 @@
1
- import { AuthSettings } from "../platform.js";
2
-
3
- //#region src/auth/methods/settings.d.ts
4
-
5
- /**
6
- * How visitors may sign in and sign up to this app, as configured in Studio.
7
- * A login or sign-up page renders only the methods it lists.
8
- */
9
- declare function settings(): Promise<AuthSettings>;
10
- //#endregion
11
- export { settings };
@@ -1,17 +0,0 @@
1
- import { panic } from "../../assertions.js";
2
- import { sessionConfig } from "../config.js";
3
- import { makePlatform } from "../platform.js";
4
-
5
- //#region src/auth/methods/settings.ts
6
- /**
7
- * How visitors may sign in and sign up to this app, as configured in Studio.
8
- * A login or sign-up page renders only the methods it lists.
9
- */
10
- async function settings() {
11
- const result = await makePlatform(sessionConfig().applicationId).authSettings();
12
- if (!result.ok) panic(`Expected the auth settings, but got ${result.status}: ${result.message}`);
13
- return result.data;
14
- }
15
-
16
- //#endregion
17
- export { settings };
@@ -1,46 +0,0 @@
1
- import "./user.js";
2
- import "./context.js";
3
- import { z } from "zod";
4
-
5
- //#region src/auth/platform.d.ts
6
-
7
- declare const authSettingsSchema: z.ZodObject<{
8
- loginEnabled: z.ZodBoolean;
9
- authenticationMethods: z.ZodArray<z.ZodEnum<{
10
- EMAIL: "EMAIL";
11
- SSO: "SSO";
12
- GOOGLE: "GOOGLE";
13
- MAGIC_LINK: "MAGIC_LINK";
14
- }>>;
15
- signUpPolicy: z.ZodNullable<z.ZodEnum<{
16
- DISABLED: "DISABLED";
17
- OPEN_TO_PUBLIC: "OPEN_TO_PUBLIC";
18
- DOMAIN_RESTRICTED: "DOMAIN_RESTRICTED";
19
- }>>;
20
- sessionExpirationDays: z.ZodNullable<z.ZodNumber>;
21
- inactivityTimeoutMinutes: z.ZodNullable<z.ZodNumber>;
22
- emailAuth: z.ZodNullable<z.ZodObject<{
23
- loginMethods: z.ZodArray<z.ZodEnum<{
24
- PASSWORD: "PASSWORD";
25
- EMAIL_CODE: "EMAIL_CODE";
26
- }>>;
27
- strongPasswords: z.ZodBoolean;
28
- emailCodeExpirationMinutes: z.ZodNullable<z.ZodNumber>;
29
- }, z.core.$strip>>;
30
- googleAuth: z.ZodNullable<z.ZodObject<{
31
- clientId: z.ZodNullable<z.ZodString>;
32
- userType: z.ZodNullable<z.ZodEnum<{
33
- INTERNAL: "INTERNAL";
34
- EXTERNAL: "EXTERNAL";
35
- }>>;
36
- }, z.core.$strip>>;
37
- sso: z.ZodNullable<z.ZodObject<{
38
- type: z.ZodNullable<z.ZodEnum<{
39
- SAML: "SAML";
40
- OPEN_ID: "OPEN_ID";
41
- }>>;
42
- }, z.core.$strip>>;
43
- }, z.core.$strip>;
44
- type AuthSettings = z.infer<typeof authSettingsSchema>;
45
- //#endregion
46
- export { AuthSettings };
@@ -1 +0,0 @@
1
- import { z } from "zod";