@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.
- package/dist/action.d.ts +1 -45
- package/dist/action.js +1 -37
- package/dist/auth/context.d.ts +4 -1
- package/dist/auth/isomorphic.d.ts +1 -1
- package/dist/auth/isomorphic.js +2 -1
- package/dist/auth/methods/authenticator.js +3 -12
- package/dist/auth/methods/code.js +1 -4
- package/dist/auth/methods/emailCode2fa.js +1 -4
- package/dist/auth/methods/magicLink.js +1 -4
- package/dist/auth/methods/password.js +1 -4
- package/dist/auth/methods/passwordLink.js +1 -4
- package/dist/auth/methods/signup.js +1 -4
- package/dist/auth/methods/social.js +1 -4
- package/dist/auth/permissions.d.ts +0 -9
- package/dist/auth/platform.js +12 -27
- package/dist/default-error-overlay.d.ts +2 -2
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -2
- package/dist/package.js +1 -1
- package/dist/pwa.d.ts +2 -2
- package/dist/server.d.ts +1 -2
- package/dist/server.js +1 -2
- package/package.json +1 -1
- package/skills/softr-sdk/SKILL.md +4 -10
- package/skills/softr-sdk/ai/SKILL.md +3 -1
- package/skills/softr-sdk/auth/SKILL.md +1 -1
- package/skills/softr-sdk/charts-and-metrics/SKILL.md +21 -4
- package/skills/softr-sdk/current-user/SKILL.md +16 -3
- package/skills/softr-sdk/file-uploads/SKILL.md +1 -1
- package/skills/softr-sdk/formatting/SKILL.md +1 -1
- package/skills/softr-sdk/integrations/SKILL.md +1 -3
- 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 +41 -4
- package/skills/softr-sdk/record-queries/SKILL.md +132 -36
- package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +2 -2
- package/skills/softr-sdk/workflows/SKILL.md +8 -10
- package/dist/auth/permissions.js +0 -13
- package/dist/core/email.d.ts +0 -48
- package/dist/core/email.js +0 -100
- 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,
|
|
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,
|
|
72
|
+
export { defineAiAction, defineIntegration, defineMutation, defineQuery, defineWorkflow, isDatasourceAction };
|
package/dist/auth/context.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
|
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
|
*
|
package/dist/auth/isomorphic.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/auth/platform.js
CHANGED
|
@@ -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
|
|
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):
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
package/dist/pwa.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
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():
|
|
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,
|
|
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,
|
|
9
|
+
export { AiError, ai, auth, ds, integration, upload, workflow };
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: core
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.
|
|
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`, `
|
|
38
|
-
| `@softr/sdk/server` | Server only | `ds`, `auth`, `
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
7
|
-
|
|
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.
|
|
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.
|