@softr/sdk 0.0.76 → 0.0.78-alpha.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 +34 -1
- package/dist/action.js +12 -1
- package/dist/core/ai.d.ts +62 -0
- package/dist/core/ai.js +61 -0
- package/dist/default-error-overlay.d.ts +2 -2
- package/dist/formatting.d.ts +3 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/mcp.d.ts +2 -2
- package/dist/package.js +1 -1
- package/dist/pwa.d.ts +2 -2
- package/dist/server.d.ts +2 -1
- package/dist/server.js +2 -1
- package/package.json +1 -1
- package/skills/softr-sdk/SKILL.md +16 -12
- package/skills/softr-sdk/ai/SKILL.md +138 -0
- package/skills/softr-sdk/auth/SKILL.md +6 -15
- package/skills/softr-sdk/charts-and-metrics/SKILL.md +6 -9
- package/skills/softr-sdk/comments/SKILL.md +5 -8
- package/skills/softr-sdk/current-user/SKILL.md +1 -1
- package/skills/softr-sdk/file-uploads/SKILL.md +4 -7
- package/skills/softr-sdk/formatting/SKILL.md +4 -6
- package/skills/softr-sdk/integrations/SKILL.md +7 -10
- 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 +5 -8
- package/skills/softr-sdk/record-queries/SKILL.md +6 -10
- package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +5 -10
- package/skills/softr-sdk/workflows/SKILL.md +1 -1
package/dist/action.d.ts
CHANGED
|
@@ -264,5 +264,38 @@ type CommentsDefinition<TId extends string> = {
|
|
|
264
264
|
* const thread = await comments.list(orderComments, { recordId })
|
|
265
265
|
*/
|
|
266
266
|
declare function defineComments<const TId extends string>(definition: CommentsDefinition<TId>): CommentsAction<TId>;
|
|
267
|
+
type AiResource = {
|
|
268
|
+
type: "AI";
|
|
269
|
+
model: string;
|
|
270
|
+
};
|
|
271
|
+
type AiAction<TId extends string = string> = Action<AiResource> & {
|
|
272
|
+
id: TId;
|
|
273
|
+
};
|
|
274
|
+
type AiDefinition<TId extends string> = {
|
|
275
|
+
id: TId;
|
|
276
|
+
model: string;
|
|
277
|
+
permissions: ActionPermissions;
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* Declares an AI action: a model call run by Softr on its own provider keys and billed to the
|
|
281
|
+
* workspace's AI credits, so nothing has to be connected. `model` is the id of a model from
|
|
282
|
+
* Softr's AI catalog, chosen when the app is built: read the catalog with the `list_ai_models`
|
|
283
|
+
* tool and copy the id exactly. An id outside the catalog is refused when the action is saved.
|
|
284
|
+
*
|
|
285
|
+
* Every call spends the workspace's credits, so keep the audience to signed-in users unless a
|
|
286
|
+
* public page really needs the call.
|
|
287
|
+
*
|
|
288
|
+
* Pass the returned object to `ai.generate()` or `ai.stream()` at runtime.
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
* const summarizeDeal = defineAiAction({
|
|
292
|
+
* id: "summarizeDeal",
|
|
293
|
+
* model: "anthropic-claude-sonnet-4-6",
|
|
294
|
+
* permissions: { userGroupTypes: ["LOGGED_IN_USERS"] },
|
|
295
|
+
* })
|
|
296
|
+
*
|
|
297
|
+
* const answer = await ai.generate(summarizeDeal, { system: "...", prompt: facts })
|
|
298
|
+
*/
|
|
299
|
+
declare function defineAiAction<const TId extends string>(definition: AiDefinition<TId>): AiAction<TId>;
|
|
267
300
|
//#endregion
|
|
268
|
-
export { ActionData, ActionPermissions, ActionRecord, ActionShape, CommentsAction, DatasourceAction, FieldTypeOf, IntegrationAction, ReadOf, ReadShape, SoftrDatasources, WorkflowAction, defineComments, defineIntegration, defineMutation, defineQuery, defineWorkflow };
|
|
301
|
+
export { ActionData, ActionPermissions, ActionRecord, ActionShape, AiAction, CommentsAction, DatasourceAction, FieldTypeOf, IntegrationAction, ReadOf, ReadShape, SoftrDatasources, WorkflowAction, defineAiAction, defineComments, defineIntegration, defineMutation, defineQuery, defineWorkflow };
|
package/dist/action.js
CHANGED
|
@@ -81,6 +81,17 @@ function isDatasourceAction(value) {
|
|
|
81
81
|
const resource = candidate.resource;
|
|
82
82
|
return typeof candidate.id === "string" && typeof resource === "object" && resource !== null && resource.type === "DATASOURCE";
|
|
83
83
|
}
|
|
84
|
+
function defineAiAction(definition) {
|
|
85
|
+
const { id, model, permissions } = definition;
|
|
86
|
+
return {
|
|
87
|
+
id,
|
|
88
|
+
resource: {
|
|
89
|
+
type: "AI",
|
|
90
|
+
model
|
|
91
|
+
},
|
|
92
|
+
permissions
|
|
93
|
+
};
|
|
94
|
+
}
|
|
84
95
|
|
|
85
96
|
//#endregion
|
|
86
|
-
export { defineComments, defineIntegration, defineMutation, defineQuery, defineWorkflow, isDatasourceAction };
|
|
97
|
+
export { defineAiAction, defineComments, defineIntegration, defineMutation, defineQuery, defineWorkflow, isDatasourceAction };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { AiAction } from "../action.js";
|
|
2
|
+
|
|
3
|
+
//#region src/core/ai.d.ts
|
|
4
|
+
/** A document or image the model reads, by the URL the app holds it under: one of its own uploads, as a rule. */
|
|
5
|
+
interface AiFile {
|
|
6
|
+
/** An https URL Softr may fetch. */
|
|
7
|
+
url: string;
|
|
8
|
+
/** The file's type when the URL does not say, such as `application/pdf`. */
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
}
|
|
11
|
+
interface AiGenerateInit {
|
|
12
|
+
/** What the model is and the rules it answers under. */
|
|
13
|
+
system?: string;
|
|
14
|
+
/** What it is asked, with the facts it is given. */
|
|
15
|
+
prompt: string;
|
|
16
|
+
/**
|
|
17
|
+
* A JSON schema the answer has to match. With it, the model is asked for JSON only and the
|
|
18
|
+
* answer comes back parsed in `json` as well as written in `text`.
|
|
19
|
+
*/
|
|
20
|
+
schema?: Record<string, unknown>;
|
|
21
|
+
/** Documents and images the model reads alongside the prompt. */
|
|
22
|
+
files?: ReadonlyArray<AiFile>;
|
|
23
|
+
/** A cap on the answer's length, in tokens; Softr caps it further. */
|
|
24
|
+
maxOutputTokens?: number;
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
}
|
|
27
|
+
interface AiUsage {
|
|
28
|
+
inputTokens: number;
|
|
29
|
+
outputTokens: number;
|
|
30
|
+
}
|
|
31
|
+
interface AiAnswer {
|
|
32
|
+
/** The answer as the model wrote it. */
|
|
33
|
+
text: string;
|
|
34
|
+
/** The answer parsed as JSON when `schema` was given and the model kept to it; `null` otherwise. */
|
|
35
|
+
json: unknown;
|
|
36
|
+
usage: AiUsage;
|
|
37
|
+
/** The model that answered. */
|
|
38
|
+
model: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Thrown when the model call was refused or failed. `status` is the HTTP status Softr answered
|
|
42
|
+
* with; 402 means the workspace has no AI credits left.
|
|
43
|
+
*/
|
|
44
|
+
declare class AiError extends Error {
|
|
45
|
+
readonly status: number;
|
|
46
|
+
constructor(status: number, message: string);
|
|
47
|
+
}
|
|
48
|
+
/** Runs the action once and answers when the model is done. */
|
|
49
|
+
declare function aiGenerate(action: AiAction, init: AiGenerateInit): Promise<AiAnswer>;
|
|
50
|
+
/**
|
|
51
|
+
* Runs the action and answers while the model writes. The returned `Response` body is a stream of
|
|
52
|
+
* Server-Sent Events, one JSON object per `data:` line: `{ "text": "..." }` for each piece of the
|
|
53
|
+
* answer, then `{ "done": true, "usage": {...}, "model": "..." }`, or `{ "error": "..." }` in its
|
|
54
|
+
* place. Hand it to the browser as a `Response` with `Content-Type: text/event-stream`.
|
|
55
|
+
*/
|
|
56
|
+
declare function aiStream(action: AiAction, init: AiGenerateInit): Promise<Response>;
|
|
57
|
+
declare const ai: {
|
|
58
|
+
generate: typeof aiGenerate;
|
|
59
|
+
stream: typeof aiStream;
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { AiAnswer, AiError, AiFile, AiGenerateInit, AiUsage, ai };
|
package/dist/core/ai.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { fetch } from "../fetch.js";
|
|
2
|
+
import { readSessionToken } from "../auth/session.js";
|
|
3
|
+
import { describeFailure, dsUrl, refererHeader } from "../datasource/record.js";
|
|
4
|
+
|
|
5
|
+
//#region src/core/ai.ts
|
|
6
|
+
/**
|
|
7
|
+
* Thrown when the model call was refused or failed. `status` is the HTTP status Softr answered
|
|
8
|
+
* with; 402 means the workspace has no AI credits left.
|
|
9
|
+
*/
|
|
10
|
+
var AiError = class extends Error {
|
|
11
|
+
status;
|
|
12
|
+
constructor(status, message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "AiError";
|
|
15
|
+
this.status = status;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function body(init) {
|
|
19
|
+
return JSON.stringify({
|
|
20
|
+
system: init.system,
|
|
21
|
+
prompt: init.prompt,
|
|
22
|
+
schema: init.schema,
|
|
23
|
+
files: init.files,
|
|
24
|
+
maxOutputTokens: init.maxOutputTokens
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async function post(action, path, init) {
|
|
28
|
+
if (init.signal?.aborted) throw init.signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
29
|
+
const res = await fetch(`${dsUrl(action.id)}/${path}`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
"Authorization": `Bearer ${readSessionToken()}`,
|
|
34
|
+
...refererHeader()
|
|
35
|
+
},
|
|
36
|
+
body: body(init),
|
|
37
|
+
signal: init.signal
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) throw new AiError(res.status, `AI call "${action.id}" failed: ${await describeFailure(res)}`);
|
|
40
|
+
return res;
|
|
41
|
+
}
|
|
42
|
+
/** Runs the action once and answers when the model is done. */
|
|
43
|
+
async function aiGenerate(action, init) {
|
|
44
|
+
return await (await post(action, "ai/generate", init)).json();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Runs the action and answers while the model writes. The returned `Response` body is a stream of
|
|
48
|
+
* Server-Sent Events, one JSON object per `data:` line: `{ "text": "..." }` for each piece of the
|
|
49
|
+
* answer, then `{ "done": true, "usage": {...}, "model": "..." }`, or `{ "error": "..." }` in its
|
|
50
|
+
* place. Hand it to the browser as a `Response` with `Content-Type: text/event-stream`.
|
|
51
|
+
*/
|
|
52
|
+
async function aiStream(action, init) {
|
|
53
|
+
return post(action, "ai/generate/stream", init);
|
|
54
|
+
}
|
|
55
|
+
const ai = {
|
|
56
|
+
generate: aiGenerate,
|
|
57
|
+
stream: aiStream
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { AiError, ai };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react0 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): react0.JSX.Element;
|
|
12
12
|
//#endregion
|
|
13
13
|
export { DefaultErrorOverlay };
|
package/dist/formatting.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react3 from "react";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
@@ -94,7 +94,7 @@ declare function FormattingScope({
|
|
|
94
94
|
/** A BCP 47 locale, such as `de-DE`. It drops the app's fixed date format inside the scope. */
|
|
95
95
|
locale?: string;
|
|
96
96
|
children?: ReactNode;
|
|
97
|
-
}):
|
|
97
|
+
}): react3.FunctionComponentElement<react3.ProviderProps<Scope>>;
|
|
98
98
|
/**
|
|
99
99
|
* A date in the app's format, safe to render on the server. A timestamp whose
|
|
100
100
|
* zone is not known yet renders `fallback` until the browser supplies it, so
|
|
@@ -115,6 +115,6 @@ declare function FormattedDate({
|
|
|
115
115
|
time?: boolean;
|
|
116
116
|
/** What renders while the visitor's zone is unknown. */
|
|
117
117
|
fallback?: ReactNode;
|
|
118
|
-
}):
|
|
118
|
+
}): react3.FunctionComponentElement<react3.FragmentProps>;
|
|
119
119
|
//#endregion
|
|
120
120
|
export { DateOptions, DateParts, DateSeparator, DateValue, FixedDateFormat, FormattedDate, Formatting, FormattingScope, WeekDay, formatDate, formatDateTime, formatNumber, useFormatting };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SoftrUserGroupId, SoftrUserGroups, User } from "./auth/user.js";
|
|
2
|
-
import { ActionPermissions, SoftrDatasources, defineComments, defineIntegration, defineMutation, defineQuery, defineWorkflow } from "./action.js";
|
|
2
|
+
import { ActionPermissions, SoftrDatasources, defineAiAction, defineComments, 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 { CommentAttachmentInput, CommentMention, CommentThread, CommentView } from "./comments/comments.js";
|
|
6
|
+
import { AiAnswer, AiFile, AiGenerateInit, AiUsage } from "./core/ai.js";
|
|
6
7
|
import { DateOptions, DateParts, DateSeparator, DateValue, FixedDateFormat, FormattedDate, Formatting, FormattingScope, WeekDay, formatDate, formatDateTime, formatNumber, useFormatting } from "./formatting.js";
|
|
7
8
|
import { SoftrContext } from "./auth/context.js";
|
|
8
9
|
import { AuthSettings } from "./auth/platform.js";
|
|
@@ -18,4 +19,4 @@ import { useUserTimezone } from "./timezone.js";
|
|
|
18
19
|
import { PwaInstall, PwaPlatform, SoftrPwaHead, usePwaInstall } from "./pwa.js";
|
|
19
20
|
import { DefaultErrorOverlay } from "./default-error-overlay.js";
|
|
20
21
|
import { Audience } from "./route/audience.js";
|
|
21
|
-
export { type ActionPermissions, Audience, type AuthOptions, type AuthSettings, AuthSettingsProvider, type CommentAttachmentInput, type CommentMention, type CommentThread, type CommentView, 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 User, type WeekDay, assertRouteAccess, checkAccess, defineComments, 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 };
|
|
22
|
+
export { type ActionPermissions, type AiAnswer, type AiFile, type AiGenerateInit, type AiUsage, Audience, type AuthOptions, type AuthSettings, AuthSettingsProvider, type CommentAttachmentInput, type CommentMention, type CommentThread, type CommentView, 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 User, type WeekDay, assertRouteAccess, checkAccess, defineAiAction, defineComments, 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,5 +1,5 @@
|
|
|
1
1
|
import { useFileUpload } from "./core/use-file-upload.js";
|
|
2
|
-
import { defineComments, defineIntegration, defineMutation, defineQuery, defineWorkflow } from "./action.js";
|
|
2
|
+
import { defineAiAction, defineComments, defineIntegration, defineMutation, defineQuery, defineWorkflow } from "./action.js";
|
|
3
3
|
import { checkAccess, defineVisibility, isAuthorized } from "./route/visibility.js";
|
|
4
4
|
import { assertRouteAccess, guardRoute, isAccessError, isAlreadyAuthenticatedError, isForbiddenError, isUnauthenticatedError } from "./route/route.js";
|
|
5
5
|
import { definePage, pageHead } from "./route/page.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, defineComments, 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, defineComments, 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/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatasourceAction } from "./action.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as _tanstack_react_start0 from "@tanstack/react-start";
|
|
3
3
|
|
|
4
4
|
//#region src/mcp.d.ts
|
|
5
5
|
|
|
@@ -31,6 +31,6 @@ type SoftrMcpOptions = {
|
|
|
31
31
|
* ],
|
|
32
32
|
* }))
|
|
33
33
|
*/
|
|
34
|
-
declare function softrMcpMiddleware(options?: SoftrMcpOptions):
|
|
34
|
+
declare function softrMcpMiddleware(options?: SoftrMcpOptions): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>;
|
|
35
35
|
//#endregion
|
|
36
36
|
export { softrMcpMiddleware };
|
package/dist/package.js
CHANGED
package/dist/pwa.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react1 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(): react1.FunctionComponentElement<react1.FragmentProps>;
|
|
27
27
|
type PwaPlatform = "ios" | "android" | "desktop";
|
|
28
28
|
type PwaInstall = {
|
|
29
29
|
available: false;
|
package/dist/server.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { upload } from "./core/file-upload.js";
|
|
2
2
|
import { comments } from "./comments/comments.js";
|
|
3
|
+
import { AiError, ai } from "./core/ai.js";
|
|
3
4
|
import { auth } from "./auth/server.js";
|
|
4
5
|
import { FieldChoice, RecordOption, RecordOptions } from "./core/options.js";
|
|
5
6
|
import { ds } from "./datasource/datasource.js";
|
|
6
7
|
import { workflow } from "./core/workflow.js";
|
|
7
8
|
import { integration } from "./core/integration.js";
|
|
8
9
|
import "@tanstack/react-start/server-only";
|
|
9
|
-
export { type FieldChoice, type RecordOption, type RecordOptions, auth, comments, ds, integration, upload, workflow };
|
|
10
|
+
export { AiError, type FieldChoice, type RecordOption, type RecordOptions, ai, auth, comments, ds, integration, upload, workflow };
|
package/dist/server.js
CHANGED
|
@@ -3,7 +3,8 @@ import { ds } from "./datasource/datasource.js";
|
|
|
3
3
|
import { auth } from "./auth/server.js";
|
|
4
4
|
import { workflow } from "./core/workflow.js";
|
|
5
5
|
import { integration } from "./core/integration.js";
|
|
6
|
+
import { AiError, ai } from "./core/ai.js";
|
|
6
7
|
import { comments } from "./comments/comments.js";
|
|
7
8
|
import "@tanstack/react-start/server-only";
|
|
8
9
|
|
|
9
|
-
export { auth, comments, ds, integration, upload, workflow };
|
|
10
|
+
export { AiError, ai, auth, comments, ds, integration, upload, workflow };
|
package/package.json
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: softr-sdk
|
|
3
3
|
description: >-
|
|
4
|
-
Entry point for @softr/sdk
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
visibility, the current user, reading records, charts and metrics, writing
|
|
9
|
-
records, auth, or showing dates and numbers. Load this when unsure which
|
|
10
|
-
sub-skill applies, or when touching the wiring; each sub-skill stands alone
|
|
11
|
-
for its own task.
|
|
4
|
+
Entry point for @softr/sdk: its four entrypoints (@softr/sdk, /server,
|
|
5
|
+
/client, /vite) and their client-server boundary, softrAuthMiddleware and the
|
|
6
|
+
Softr context, and which sub-skill covers each task. Load it when unsure which
|
|
7
|
+
sub-skill applies, or when touching the wiring.
|
|
12
8
|
metadata:
|
|
13
9
|
type: core
|
|
14
10
|
library: softr-sdk
|
|
15
|
-
library_version: 0.0.
|
|
11
|
+
library_version: 0.0.77
|
|
16
12
|
---
|
|
17
13
|
|
|
18
14
|
# Softr SDK core
|
|
@@ -22,6 +18,7 @@ metadata:
|
|
|
22
18
|
> **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.
|
|
23
19
|
> **CRITICAL**: Every route file must declare its audience with `defineVisibility` and guard itself with that rule in `beforeLoad`. There is no default and nothing is inherited from a sibling.
|
|
24
20
|
> **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.
|
|
21
|
+
> **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.
|
|
25
22
|
> **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.
|
|
26
23
|
|
|
27
24
|
Each sub-skill stands alone for its task and repeats the boundary above, so most work needs **one** sub-skill and not this file. Load this one when you are unsure which sub-skill applies, or when the wiring below is what you are touching. Add a second sub-skill only when the work genuinely crosses that boundary: a page that lists records behind a group restriction needs `record-queries` plus `routes-and-visibility`.
|
|
@@ -30,8 +27,8 @@ Each sub-skill stands alone for its task and repeats the boundary above, so most
|
|
|
30
27
|
|
|
31
28
|
| Import | Runs | Holds |
|
|
32
29
|
| --- | --- | --- |
|
|
33
|
-
| `@softr/sdk` | Both | `defineQuery`, `defineMutation`, `defineComments`, `defineVisibility`, `guardRoute`, `assertRouteAccess`, `isAuthorized`, `checkAccess`, `Audience`, `useCurrentUser`, `useUserTimezone`, `FormattedDate`, `FormattingScope`, `useFormatting`, `formatDate`, `formatDateTime`, `formatNumber`, `formatValue`, `formatField`, `fieldShape`, `registerSoftrFields`, `useOverrides`, `SoftrPwaHead`, `usePwaInstall`, `getSoftrContext`, `initialSoftrContext`, `softrAuthMiddleware`, the error predicates, and every type |
|
|
34
|
-
| `@softr/sdk/server` | Server only | `ds`, `auth`, `workflow`, `integration`, `comments` |
|
|
30
|
+
| `@softr/sdk` | Both | `defineQuery`, `defineMutation`, `defineComments`, `defineAiAction`, `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 |
|
|
31
|
+
| `@softr/sdk/server` | Server only | `ds`, `auth`, `workflow`, `integration`, `comments`, `ai` |
|
|
35
32
|
| `@softr/sdk/client` | Browser only | `useFileUpload`, `useCommentComposer` |
|
|
36
33
|
| `@softr/sdk/vite` | Build | `softr()`, already in `vite.config.ts` |
|
|
37
34
|
|
|
@@ -47,7 +44,8 @@ Each sub-skill stands alone for its task and repeats the boundary above, so most
|
|
|
47
44
|
| Create, update or delete a record | [record-mutations/SKILL.md](./record-mutations/SKILL.md) |
|
|
48
45
|
| Trigger a Softr workflow | [workflows/SKILL.md](./workflows/SKILL.md) |
|
|
49
46
|
| Sign a visitor in, out, or up | [auth/SKILL.md](./auth/SKILL.md) |
|
|
50
|
-
|
|
|
47
|
+
| Ask an AI model: a summary, an extraction from a document, a classification, a draft, a chat | [ai/SKILL.md](./ai/SKILL.md) |
|
|
48
|
+
| 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) |
|
|
51
49
|
| Open the app to AI assistants over MCP | [mcp/SKILL.md](./mcp/SKILL.md) |
|
|
52
50
|
| Let visitors install the app on their home screen | [pwa/SKILL.md](./pwa/SKILL.md) |
|
|
53
51
|
| Show a date, a time or a number | [formatting/SKILL.md](./formatting/SKILL.md) |
|
|
@@ -80,9 +78,15 @@ Triggering a Softr workflow from a button or form?
|
|
|
80
78
|
Wiring a login, signup, or sign-out?
|
|
81
79
|
load softr-sdk/auth
|
|
82
80
|
|
|
81
|
+
Asking an AI model for anything: a summary, fields out of a document, a classification, a draft, a chat?
|
|
82
|
+
load softr-sdk/ai — Softr runs the model on its own keys, nothing to connect; pick the model from ai_list_models
|
|
83
|
+
|
|
83
84
|
Calling a connected third-party API (Slack, Stripe, Gmail, Google Calendar, …)?
|
|
84
85
|
load softr-sdk/integrations
|
|
85
86
|
|
|
87
|
+
Running a model on the user's OWN provider account, because they asked for that?
|
|
88
|
+
load softr-sdk/integrations: its "Calling an AI model" section
|
|
89
|
+
|
|
86
90
|
Opening the app to an AI assistant over MCP?
|
|
87
91
|
load softr-sdk/mcp
|
|
88
92
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ai
|
|
3
|
+
description: >-
|
|
4
|
+
Run an AI model from the app on Softr's own keys, billed to the workspace's
|
|
5
|
+
AI credits, with nothing to connect: defineAiAction from @softr/sdk declares
|
|
6
|
+
the action with the catalog model it runs on and its permissions;
|
|
7
|
+
ai.generate from @softr/sdk/server runs it with a system text, a prompt, an
|
|
8
|
+
optional JSON schema for an answer that is data, and optional files;
|
|
9
|
+
ai.stream runs it for an answer the user watches being written. Load this
|
|
10
|
+
for a summary, an extraction, a classification, a draft, or a chat the app
|
|
11
|
+
asks a model for.
|
|
12
|
+
metadata:
|
|
13
|
+
type: sub-skill
|
|
14
|
+
library: softr-sdk
|
|
15
|
+
library_version: 0.0.78-alpha.0
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
**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.
|
|
19
|
+
|
|
20
|
+
# AI
|
|
21
|
+
|
|
22
|
+
An app asks a model through an AI action. Softr runs the call on its own provider keys and bills the workspace's AI credits, so there is no key to ask the user for and nothing to connect. This is the path for every AI feature of the app's own — a summary of a record, fields extracted from a document, a classification, a draft, a chat — unless the user explicitly asked to use their own provider account, which is the `integrations` sub-skill's path.
|
|
23
|
+
|
|
24
|
+
## Choose the model first
|
|
25
|
+
|
|
26
|
+
The model is chosen when the app is built and frozen in its code. Before you declare an action, read Softr's catalog with the `ai_list_models` tool. Each entry carries the `id` the action names, the `provider`, and `bestFor`, what to pick it for:
|
|
27
|
+
|
|
28
|
+
- a summary, a briefing, a draft, reasoning over many facts: the strongest model listed;
|
|
29
|
+
- an extraction or a classification over one document: the cheapest model that reads that document's kind — a PDF needs a model whose `bestFor` says so.
|
|
30
|
+
|
|
31
|
+
Copy the `id` exactly. An id outside the catalog is refused when the action is saved, and a model spelled from memory never is in it.
|
|
32
|
+
|
|
33
|
+
## Declare the action
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const summarizeDeal = defineAiAction({
|
|
37
|
+
id: 'summarizeDeal',
|
|
38
|
+
model: 'anthropic-claude-sonnet-4-6',
|
|
39
|
+
permissions: { userGroupTypes: ['LOGGED_IN_USERS'] },
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Write the object inline in `defineAiAction`. Passing it through a variable widens the types.
|
|
44
|
+
|
|
45
|
+
**Every call spends the workspace's credits.** Keep the audience to `['LOGGED_IN_USERS']`, narrowed with `customGroups` where the feature is for some users only. Open an AI action to visitors only when a public page really needs it, and say so to the user: anyone on the internet can then bill the workspace.
|
|
46
|
+
|
|
47
|
+
Declare one action per feature — the summary and the extraction are two actions, each with its own `id`, its own model and its own audience.
|
|
48
|
+
|
|
49
|
+
## Core pattern: an answer the server reads
|
|
50
|
+
|
|
51
|
+
`ai.generate(action, init)` runs the action and answers when the model is done. `init` takes:
|
|
52
|
+
|
|
53
|
+
- `prompt` — what the model is asked, with the facts it is given. Required.
|
|
54
|
+
- `system` — what the model is and the rules it answers under.
|
|
55
|
+
- `schema` — a JSON schema the answer has to match. With it the model is asked for JSON only, and the answer comes back parsed in `json` as well as written in `text`.
|
|
56
|
+
- `files` — documents and images the model reads, each as `{ url, mimeType? }`: an `https` URL Softr may fetch, one of the app's own uploads as a rule.
|
|
57
|
+
- `maxOutputTokens` — a cap on the answer's length. Softr caps it further.
|
|
58
|
+
|
|
59
|
+
The answer is `{ text, json, usage, model }`: `text` as the model wrote it, `json` parsed when a schema was given and the model kept to it, `null` otherwise.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const summarize = createServerFn({ method: 'POST' })
|
|
63
|
+
.validator(z.object({ recordId: z.string() }))
|
|
64
|
+
.handler(async ({ data }) => {
|
|
65
|
+
const deal = await ds.get(dealQuery, { id: data.recordId })
|
|
66
|
+
if (deal === null) throw new Error('This deal no longer exists')
|
|
67
|
+
const facts = [`Name: ${fieldText(deal.fields.name)}`, `Stage: ${fieldText(deal.fields.stage)}`].join('\n')
|
|
68
|
+
const answer = await ai.generate(summarizeDeal, {
|
|
69
|
+
system: 'You brief a sales team. Use only the facts given; say what is missing rather than invent it.',
|
|
70
|
+
prompt: facts,
|
|
71
|
+
maxOutputTokens: 800,
|
|
72
|
+
})
|
|
73
|
+
return answer.text
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Extraction: an answer that is data
|
|
78
|
+
|
|
79
|
+
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.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const InvoiceFields = z.object({ vendor: z.string(), total: z.number(), issuedOn: z.string() })
|
|
83
|
+
|
|
84
|
+
const extractInvoice = createServerFn({ method: 'POST' })
|
|
85
|
+
.validator(z.object({ fileUrl: z.string().url() }))
|
|
86
|
+
.handler(async ({ data }) => {
|
|
87
|
+
const answer = await ai.generate(readInvoice, {
|
|
88
|
+
system: 'Read the invoice and answer with its fields. Dates as YYYY-MM-DD, amounts as numbers.',
|
|
89
|
+
prompt: 'Extract the vendor, the total and the issue date.',
|
|
90
|
+
schema: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: { vendor: { type: 'string' }, total: { type: 'number' }, issuedOn: { type: 'string' } },
|
|
93
|
+
required: ['vendor', 'total', 'issuedOn'],
|
|
94
|
+
},
|
|
95
|
+
files: [{ url: data.fileUrl, mimeType: 'application/pdf' }],
|
|
96
|
+
})
|
|
97
|
+
const parsed = InvoiceFields.safeParse(answer.json)
|
|
98
|
+
if (!parsed.success) throw new Error('The invoice could not be read. Try a clearer scan.')
|
|
99
|
+
return ds.create(createInvoice, { data: parsed.data })
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The file's URL comes from the app's own upload: load the Softr SDK `file-uploads` guidance for that half. `readInvoice` names a model whose `bestFor` reads PDF documents.
|
|
104
|
+
|
|
105
|
+
## Streaming: an answer the user watches being written
|
|
106
|
+
|
|
107
|
+
A chat, an assistant, a long draft shown as it is typed — never buffer these: the user would watch a blank panel until the last token. `ai.stream(action, init)` takes the same `init` and answers with a `Response` whose body is a stream of Server-Sent Events. Hand it to the browser from the server function, unchanged:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const draftReply = createServerFn({ method: 'POST' })
|
|
111
|
+
.validator(z.object({ prompt: z.string() }))
|
|
112
|
+
.handler(async ({ data }) => {
|
|
113
|
+
const res = await ai.stream(draftAction, { system: 'You write replies for a support team.', prompt: data.prompt })
|
|
114
|
+
return new Response(res.body, { headers: { 'Content-Type': 'text/event-stream' } })
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Each event is one JSON object on a `data:` line:
|
|
119
|
+
|
|
120
|
+
- `{ "text": "..." }` — the next piece of the answer; append it.
|
|
121
|
+
- `{ "done": true, "usage": { "inputTokens", "outputTokens" }, "model": "..." }` — the answer is complete.
|
|
122
|
+
- `{ "error": "..." }` — in place of `done`, when the call ended before its answer: show the message.
|
|
123
|
+
|
|
124
|
+
On the client, call the server function's URL with `fetch`, read `response.body` with a stream reader, split on blank lines, and parse the JSON after `data:`.
|
|
125
|
+
|
|
126
|
+
## Failure modes
|
|
127
|
+
|
|
128
|
+
**Calling from client code.** `ai` is server-only. Importing it into a component or calling it outside a `createServerFn` handler fails the build or has no request context to act on.
|
|
129
|
+
|
|
130
|
+
**Spelling a model id.** `model` is copied from `ai_list_models`, never written from memory. An id outside the catalog fails the save of the action, so the app cannot be published with it.
|
|
131
|
+
|
|
132
|
+
**No credits left.** `ai.generate` and `ai.stream` throw an `AiError` whose `status` is `402` when the workspace has no AI credits. Show its message to the user; the workspace owner tops the credits up in Softr. Any other refusal carries its status and reason the same way.
|
|
133
|
+
|
|
134
|
+
**Trusting `json` without checking it.** A model may stray from the schema; `json` is then `null`, or a shape close to the one asked. Validate it with `zod` before writing it to a record, and keep `text` as the fallback the user can read.
|
|
135
|
+
|
|
136
|
+
**Reading a streamed body with `res.json()`.** The body of `ai.stream` is an event stream, not one JSON document. Return `res.body` in a `Response` or read it with a stream reader.
|
|
137
|
+
|
|
138
|
+
**Asking for the user's API key.** An AI feature of the app's own needs none. Only when the user asks to run the app on their own provider account does the call go through a connected integration — load the `integrations` sub-skill then.
|
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: auth
|
|
3
3
|
description: >-
|
|
4
|
-
Sign a
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
authenticator from a settings page (sendAuthenticatorResetCode,
|
|
10
|
-
resetAuthenticator, removeAuthenticator), updateProfile for the visitor's own
|
|
11
|
-
name and photo, the emailed password links (requestPasswordReset,
|
|
12
|
-
checkPasswordLink, setPasswordWithLink), and getAuthSettings,
|
|
13
|
-
AuthSettingsProvider and useAuthSettings for which sign-in and sign-up methods
|
|
14
|
-
the app has enabled. Covers the Result shape they answer with and its error
|
|
15
|
-
codes (invalidCredentials, emailTaken, notAuthenticated, notAnImage and the
|
|
16
|
-
rest), the two-step sign-up, why a successful sign-in does not always mean the
|
|
17
|
-
visitor is signed in, and toReturnPath for where to land after signing in.
|
|
4
|
+
Sign a visitor in, out or up from a server function with auth from
|
|
5
|
+
@softr/sdk/server: password, code, Google and magic link, sign-up and its
|
|
6
|
+
confirmation, the authenticator-app steps, password reset links,
|
|
7
|
+
updateProfile, and getAuthSettings and useAuthSettings for the methods the app
|
|
8
|
+
enables. Covers the Result shape, its error codes, and toReturnPath.
|
|
18
9
|
metadata:
|
|
19
10
|
type: sub-skill
|
|
20
11
|
library: softr-sdk
|
|
21
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.77
|
|
22
13
|
---
|
|
23
14
|
|
|
24
15
|
**Boundary.** `auth` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build.
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: charts-and-metrics
|
|
3
3
|
description: >-
|
|
4
|
-
Aggregate
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
number, when filling a dropdown from a real column, or when a chart comes back
|
|
10
|
-
scrambled or with the wrong rows. Aggregation only: fetching individual
|
|
11
|
-
records is record-queries, and changing them is record-mutations.
|
|
4
|
+
Aggregate records in a server function: ds.chart with groupBy, metrics, where,
|
|
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.
|
|
12
9
|
metadata:
|
|
13
10
|
type: sub-skill
|
|
14
11
|
library: softr-sdk
|
|
15
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.77
|
|
16
13
|
---
|
|
17
14
|
|
|
18
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.
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: comments
|
|
3
3
|
description: >-
|
|
4
|
-
Comment threads on records: defineComments
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
and useFileUpload from @softr/sdk/client keep a textarea's text, its
|
|
9
|
-
@mentions and its attachments in step. Bodies come back as sanitised
|
|
10
|
-
bodyHtml. A null thread means render nothing.
|
|
4
|
+
Comment threads on records: defineComments, the comments calls from
|
|
5
|
+
@softr/sdk/server to list, write, react to and mention in one record's thread,
|
|
6
|
+
and useCommentComposer and useFileUpload from @softr/sdk/client for the
|
|
7
|
+
composer. Bodies come back as sanitised bodyHtml.
|
|
11
8
|
metadata:
|
|
12
9
|
type: sub-skill
|
|
13
10
|
library: softr-sdk
|
|
14
|
-
library_version: 0.0.
|
|
11
|
+
library_version: 0.0.77
|
|
15
12
|
---
|
|
16
13
|
|
|
17
14
|
**Boundary.** `comments` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler. Importing it into a component fails the build. `useCommentComposer` lives on `@softr/sdk/client` and runs in components. `defineComments` comes from `@softr/sdk` and can be imported anywhere.
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: file-uploads
|
|
3
3
|
description: >-
|
|
4
|
-
Upload files from a form or edit view: useFileUpload
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
ships its own server function. For server-side uploads or plain forms, use
|
|
8
|
-
upload(actionId, file) from @softr/sdk/server. For edit forms pass
|
|
9
|
-
initialAttachments to pre-load existing attachments.
|
|
4
|
+
Upload files from a form or edit view: useFileUpload from @softr/sdk, with
|
|
5
|
+
progress, cancel and isUploading to hold the submit, and initialAttachments
|
|
6
|
+
for an edit form; upload from @softr/sdk/server for a server-side upload.
|
|
10
7
|
metadata:
|
|
11
8
|
type: sub-skill
|
|
12
9
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.
|
|
10
|
+
library_version: 0.0.77
|
|
14
11
|
---
|
|
15
12
|
|
|
16
13
|
**Boundary.** `upload` lives on `@softr/sdk/server` and runs inside server functions. `useFileUpload` and `FileItem` live on `@softr/sdk` and run in components. `FileAttachment` comes from `@softr/sdk` and can be imported anywhere.
|
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
name: formatting
|
|
3
3
|
description: >-
|
|
4
4
|
Show dates, times and numbers in the app's format without hydration
|
|
5
|
-
mismatches: FormattedDate
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
toLocaleDateString, toLocaleString and Intl formatters with the runtime's
|
|
9
|
-
defaults must never run in render.
|
|
5
|
+
mismatches: FormattedDate, useFormatting with formatDate, formatDateTime and
|
|
6
|
+
formatNumber, FormattingScope and timeZone, and why toLocaleDateString and
|
|
7
|
+
Intl defaults must never run in render.
|
|
10
8
|
metadata:
|
|
11
9
|
type: sub-skill
|
|
12
10
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.
|
|
11
|
+
library_version: 0.0.77
|
|
14
12
|
---
|
|
15
13
|
|
|
16
14
|
# Dates, times and numbers
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: integrations
|
|
3
3
|
description: >-
|
|
4
|
-
Call a third-party service connected
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
integration.createFetcher from @softr/sdk/server binds it and returns a
|
|
8
|
-
fetcher function you call with optional params, query, headers, and body.
|
|
9
|
-
Check res.ok before res.json().
|
|
4
|
+
Call a third-party service connected as an integration: defineIntegration from
|
|
5
|
+
@softr/sdk declares the one method and endpoint it allows, and
|
|
6
|
+
integration.createFetcher from @softr/sdk/server calls it.
|
|
10
7
|
metadata:
|
|
11
8
|
type: sub-skill
|
|
12
9
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.
|
|
10
|
+
library_version: 0.0.77
|
|
14
11
|
---
|
|
15
12
|
|
|
16
13
|
**Boundary.** `integration` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineIntegration` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
@@ -129,9 +126,9 @@ Do not add an `Authorization` key to `headers` — Softr injects the integration
|
|
|
129
126
|
|
|
130
127
|
## Calling an AI model
|
|
131
128
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
**This is the path for the user's own provider account only.** An AI feature of the app's own — a summary, an extraction, a chat — runs on Softr's keys through an AI action, with nothing to connect and no key to ask for: load the `ai` sub-skill for it. Call a model through an integration only when the user asked to use their own OpenAI, Anthropic or Gemini account, and had it connected.
|
|
130
|
+
|
|
131
|
+
An AI integration is called like any other. For `ANTHROPIC_AI`, the action is `POST` on
|
|
135
132
|
`https://api.anthropic.com/v1/messages`, the request carries the `anthropic-version` header and a body of
|
|
136
133
|
`model`, `max_tokens`, `system` and `messages`, and the answer's text is in `content[].text`:
|
|
137
134
|
|
|
@@ -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.77
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
**Boundary.** `softrMcpMiddleware` lives on `@softr/sdk` and is registered once, in `src/start.ts`. The actions you hand it are the same `defineQuery` / `defineMutation` values the app's own pages use — declared wherever they already live, not duplicated for MCP.
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: record-mutations
|
|
3
3
|
description: >-
|
|
4
|
-
Create, update and delete records
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
edit or deleting a record, or when a write throws, writes the wrong field or
|
|
9
|
-
silently changes nothing. Writes only: fetching records is record-queries, and
|
|
10
|
-
aggregating them is charts-and-metrics.
|
|
4
|
+
Create, update and delete records with defineMutation, ds.create, ds.update
|
|
5
|
+
and ds.delete: the payload keyed by your own field names and what each field
|
|
6
|
+
type accepts. Use when submitting a form, saving an edit or deleting a record,
|
|
7
|
+
or when a write fails or changes nothing. Reads are record-queries.
|
|
11
8
|
metadata:
|
|
12
9
|
type: sub-skill
|
|
13
10
|
library: softr-sdk
|
|
14
|
-
library_version: 0.0.
|
|
11
|
+
library_version: 0.0.77
|
|
15
12
|
---
|
|
16
13
|
|
|
17
14
|
**Boundary.** `ds` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build. `defineMutation` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: record-queries
|
|
3
3
|
description: >-
|
|
4
|
-
Fetch records
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
filling a filter or form dropdown with all options from a linked table, or
|
|
10
|
-
when a read comes back empty, unsorted or missing a field. Reads only:
|
|
11
|
-
creating, updating and deleting records is record-mutations, and aggregating
|
|
12
|
-
them into totals or chart rows is charts-and-metrics.
|
|
4
|
+
Fetch records with defineQuery, ds.list and ds.get: where and orderBy, field
|
|
5
|
+
names of your own choosing, pagination and its limits, and scoping a read to
|
|
6
|
+
the signed-in visitor. Use to put records on a page or fill a dropdown, or
|
|
7
|
+
when a read comes back empty, unsorted or missing a field. Writes are
|
|
8
|
+
record-mutations; totals and chart rows are charts-and-metrics.
|
|
13
9
|
metadata:
|
|
14
10
|
type: sub-skill
|
|
15
11
|
library: softr-sdk
|
|
16
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.77
|
|
17
13
|
---
|
|
18
14
|
|
|
19
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. The same holds for any helper that calls `ds`: declare it inside the handler. A function at module level that calls `ds` keeps the server-only import in the browser bundle, and every page stops loading until it moves. `defineQuery` and every type come from `@softr/sdk` and can be imported anywhere.
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: routes-and-visibility
|
|
3
3
|
description: >-
|
|
4
|
-
Add a route file
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
overrides, the redirect and throw strategies, rendering an access error with
|
|
9
|
-
isAccessError, isUnauthenticatedError, isForbiddenError and
|
|
10
|
-
isAlreadyAuthenticatedError, guarding a layout, and the lint rule that
|
|
11
|
-
requires a declaration alongside the rules that reject a duplicate, computed
|
|
12
|
-
or root-level one.
|
|
4
|
+
Add a route file and declare who may see it: defineVisibility with
|
|
5
|
+
LOGGED_IN_USERS, NON_LOGGED_IN_USERS and customGroups, guardRoute and
|
|
6
|
+
assertRouteAccess in a route, isAuthorized and checkAccess anywhere, rendering
|
|
7
|
+
an access error, and the lint rule that requires the declaration.
|
|
13
8
|
metadata:
|
|
14
9
|
type: sub-skill
|
|
15
10
|
library: softr-sdk
|
|
16
|
-
library_version: 0.0.
|
|
11
|
+
library_version: 0.0.77
|
|
17
12
|
---
|
|
18
13
|
|
|
19
14
|
**Boundary.** `defineVisibility`, the access functions and the error predicates come from `@softr/sdk` and work anywhere.
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.
|
|
12
|
+
library_version: 0.0.77
|
|
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.
|