@softr/sdk 0.0.50-alpha.0 → 0.0.50-alpha.1
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/auth/user.d.ts +5 -12
- package/dist/cli.js +29 -9
- package/dist/codegen/fetch-metadata.js +2 -2
- package/dist/codegen/softr-env.js +4 -2
- package/dist/codegen/user-groups.js +24 -3
- package/dist/codegen/write-datasource-docs.js +8 -6
- package/dist/codegen/write-if-changed.js +22 -0
- package/dist/default-error-overlay.js +2 -84
- package/dist/package.js +1 -1
- package/package.json +10 -33
- package/skills/softr-sdk/SKILL.md +2 -2
- package/skills/softr-sdk/auth/SKILL.md +1 -1
- package/skills/softr-sdk/charts-and-metrics/SKILL.md +1 -1
- package/skills/softr-sdk/current-user/SKILL.md +12 -3
- package/skills/softr-sdk/file-uploads/SKILL.md +1 -1
- package/skills/softr-sdk/integrations/SKILL.md +1 -1
- package/skills/softr-sdk/mcp/SKILL.md +1 -1
- package/skills/softr-sdk/record-mutations/SKILL.md +1 -1
- package/skills/softr-sdk/record-queries/SKILL.md +3 -3
- package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +3 -1
- package/skills/softr-sdk/workflows/SKILL.md +2 -2
package/dist/auth/user.d.ts
CHANGED
|
@@ -3,20 +3,13 @@ import { z } from "zod";
|
|
|
3
3
|
//#region src/auth/user.d.ts
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* declare module "@softr/sdk" {
|
|
11
|
-
* interface SoftrUserGroups {
|
|
12
|
-
* ids:
|
|
13
|
-
* | "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
|
|
14
|
-
* | "f9e8d7c6-5b4a-4938-8271-605f4e3d2c1b"
|
|
15
|
-
* }
|
|
16
|
-
* }
|
|
6
|
+
* The app's group ids. The app's generated types declare them, the predefined
|
|
7
|
+
* groups included. Every group id the SDK takes and returns narrows to them.
|
|
8
|
+
* Never declare it by hand: a second declaration does not merge with the
|
|
9
|
+
* generated one.
|
|
17
10
|
*/
|
|
18
11
|
interface SoftrUserGroups {}
|
|
19
|
-
/** A Softr user group id, narrowed to
|
|
12
|
+
/** A Softr user group id, narrowed to the app's own ids by {@link SoftrUserGroups}. */
|
|
20
13
|
type SoftrUserGroupId = SoftrUserGroups extends {
|
|
21
14
|
ids: infer Id extends string;
|
|
22
15
|
} ? Id : string;
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { version } from "./package.js";
|
|
3
3
|
import { requireDefined } from "./assertions.js";
|
|
4
4
|
import { fetchSoftrEnvInput, metadataSourceFromEnv } from "./codegen/fetch-metadata.js";
|
|
5
|
+
import { renderUserGroupsDoc } from "./codegen/user-groups.js";
|
|
5
6
|
import { renderSoftrEnv } from "./codegen/softr-env.js";
|
|
7
|
+
import { writeIfChanged } from "./codegen/write-if-changed.js";
|
|
6
8
|
import { writeDatasourceDocs } from "./codegen/write-datasource-docs.js";
|
|
7
9
|
import { writeFileSync } from "node:fs";
|
|
8
10
|
import path from "node:path";
|
|
@@ -11,26 +13,44 @@ import { parseArgs } from "node:util";
|
|
|
11
13
|
//#region src/cli.ts
|
|
12
14
|
const OUTPUT = "softr-env.d.ts";
|
|
13
15
|
const DOCS_DIR = "docs/datasources";
|
|
16
|
+
const USER_GROUPS_DOC = "docs/user-groups.md";
|
|
14
17
|
async function generate({ json }) {
|
|
15
18
|
if (json) console.info = console.error;
|
|
16
19
|
const input = await fetchSoftrEnvInput(metadataSourceFromEnv());
|
|
17
20
|
writeFileSync(path.resolve(process.cwd(), OUTPUT), renderSoftrEnv(input, version), "utf8");
|
|
18
|
-
const
|
|
19
|
-
|
|
21
|
+
const report = writeDocs(input);
|
|
22
|
+
if (json) {
|
|
23
|
+
process.stdout.write(`${JSON.stringify(report)}\n`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
printReport(report);
|
|
27
|
+
}
|
|
28
|
+
function writeDocs(input) {
|
|
29
|
+
const datasourceDocs = writeDatasourceDocs(input.datasources, path.resolve(process.cwd(), DOCS_DIR));
|
|
30
|
+
return {
|
|
20
31
|
types: OUTPUT,
|
|
21
|
-
|
|
32
|
+
docs: [{
|
|
33
|
+
subject: "datasources",
|
|
34
|
+
path: `${DOCS_DIR}/README.md`,
|
|
35
|
+
changed: datasourceDocs.changed
|
|
36
|
+
}, {
|
|
37
|
+
subject: "user groups",
|
|
38
|
+
path: USER_GROUPS_DOC,
|
|
39
|
+
changed: writeIfChanged(path.resolve(process.cwd(), USER_GROUPS_DOC), renderUserGroupsDoc(input.userGroups))
|
|
40
|
+
}],
|
|
22
41
|
datasources: input.datasources.map(({ id, name }) => ({
|
|
23
42
|
id,
|
|
24
43
|
name,
|
|
25
|
-
doc: `${DOCS_DIR}/${requireDefined(
|
|
44
|
+
doc: `${DOCS_DIR}/${requireDefined(datasourceDocs.fileNames.get(id))}`
|
|
26
45
|
}))
|
|
27
46
|
};
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
47
|
+
}
|
|
48
|
+
function printReport(report) {
|
|
32
49
|
console.log(`[softr] wrote ${report.types}`);
|
|
33
|
-
|
|
50
|
+
for (const doc of report.docs) {
|
|
51
|
+
const state = doc.changed ? "updated" : "unchanged";
|
|
52
|
+
console.log(`[softr] ${state} ${doc.path} for ${doc.subject}`);
|
|
53
|
+
}
|
|
34
54
|
for (const datasource of report.datasources) console.log(`[softr] wrote ${datasource.doc} for ${datasource.name} (${datasource.id})`);
|
|
35
55
|
}
|
|
36
56
|
const { positionals, values } = parseArgs({
|
|
@@ -19,7 +19,7 @@ function metadataSourceFromEnv(env = process.env) {
|
|
|
19
19
|
}
|
|
20
20
|
async function fetchSoftrEnvInput(source) {
|
|
21
21
|
const { studioApiUrl, datasourceApiUrl, applicationId, token } = source;
|
|
22
|
-
const registered = await apiFetch(`${studioApiUrl}/v1/applications/${applicationId}/datasources`, token);
|
|
22
|
+
const [registered, userGroups] = await Promise.all([apiFetch(`${studioApiUrl}/v1/applications/${applicationId}/datasources`, token), apiFetch(`${studioApiUrl}/v1/applications/${applicationId}/access-control/user-groups`, token)]);
|
|
23
23
|
return {
|
|
24
24
|
datasources: await Promise.all(registered.map(async ({ name, datasource }) => {
|
|
25
25
|
return {
|
|
@@ -28,7 +28,7 @@ async function fetchSoftrEnvInput(source) {
|
|
|
28
28
|
name
|
|
29
29
|
};
|
|
30
30
|
})),
|
|
31
|
-
|
|
31
|
+
userGroups
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
async function apiFetch(url, token) {
|
|
@@ -13,7 +13,8 @@ function renderSoftrEnv(input, sdkVersion) {
|
|
|
13
13
|
"",
|
|
14
14
|
"declare module \"@softr/sdk\" {",
|
|
15
15
|
indent(interfaces),
|
|
16
|
-
|
|
16
|
+
"",
|
|
17
|
+
indent(renderUserGroups(input.userGroups)),
|
|
17
18
|
"}",
|
|
18
19
|
""
|
|
19
20
|
].join("\n");
|
|
@@ -33,10 +34,11 @@ function header(sdkVersion) {
|
|
|
33
34
|
* THIS FILE IS AUTO GENERATED, DO NOT EDIT THIS MANUALLY
|
|
34
35
|
*
|
|
35
36
|
* This file exists for type checking. See docs/datasources/ for field reference
|
|
36
|
-
* and guide how to use it.
|
|
37
|
+
* and guide how to use it, and docs/user-groups.md for the app's user groups.
|
|
37
38
|
*
|
|
38
39
|
* Generated by @softr/sdk@${sdkVersion}
|
|
39
40
|
* This file is regenerated automatically when a change is made to the datasource
|
|
41
|
+
* or to the app's user groups
|
|
40
42
|
*/
|
|
41
43
|
|
|
42
44
|
export {};`;
|
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
//#region src/codegen/user-groups.ts
|
|
2
|
-
|
|
2
|
+
const DOC_NOTE = "> This document is generated automatically when a change is made in the app's user groups.";
|
|
3
|
+
function renderUserGroups(groups) {
|
|
3
4
|
return [
|
|
4
5
|
"interface SoftrUserGroups {",
|
|
5
|
-
` ids: ${
|
|
6
|
+
` ids: ${groups.length === 0 ? "never" : groups.map(({ id }) => JSON.stringify(id)).join(" | ")};`,
|
|
6
7
|
"}"
|
|
7
8
|
].join("\n");
|
|
8
9
|
}
|
|
10
|
+
function renderUserGroupsDoc(groups) {
|
|
11
|
+
const heading = [
|
|
12
|
+
"# User groups",
|
|
13
|
+
"",
|
|
14
|
+
DOC_NOTE,
|
|
15
|
+
""
|
|
16
|
+
];
|
|
17
|
+
if (groups.length === 0) return [
|
|
18
|
+
...heading,
|
|
19
|
+
"This app has no user groups.",
|
|
20
|
+
""
|
|
21
|
+
].join("\n");
|
|
22
|
+
return [
|
|
23
|
+
...heading,
|
|
24
|
+
"User groups available in this app:",
|
|
25
|
+
"",
|
|
26
|
+
...groups.map(({ id, name, type }) => `- ${name} (${id}), ${type.toLowerCase()}`),
|
|
27
|
+
""
|
|
28
|
+
].join("\n");
|
|
29
|
+
}
|
|
9
30
|
|
|
10
31
|
//#endregion
|
|
11
|
-
export { renderUserGroups };
|
|
32
|
+
export { renderUserGroups, renderUserGroupsDoc };
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import { requireDefined } from "../assertions.js";
|
|
2
2
|
import { renderDatasourceDoc, renderDatasourceIndex } from "./datasource-docs.js";
|
|
3
3
|
import { docFileNames } from "./doc-file-names.js";
|
|
4
|
-
import {
|
|
4
|
+
import { writeIfChanged } from "./write-if-changed.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
7
|
//#region src/codegen/write-datasource-docs.ts
|
|
8
8
|
/**
|
|
9
9
|
* Writes the index and one file per datasource, and touches nothing else in
|
|
10
10
|
* `docsDir`: the caller resolves it against `process.cwd()`, which can be any
|
|
11
|
-
* directory a developer ran the bin from.
|
|
11
|
+
* directory a developer ran the bin from. `changed` is true when any file it
|
|
12
|
+
* wrote now reads differently.
|
|
12
13
|
*/
|
|
13
14
|
function writeDatasourceDocs(datasources, docsDir) {
|
|
14
15
|
const context = {
|
|
15
16
|
datasources: new Map(datasources.map((datasource) => [datasource.id, datasource])),
|
|
16
17
|
fileNames: docFileNames(datasources)
|
|
17
18
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const writes = [writeIfChanged(path.join(docsDir, "README.md"), renderDatasourceIndex(datasources, context)), ...datasources.map((datasource) => writeIfChanged(path.join(docsDir, requireDefined(context.fileNames.get(datasource.id))), renderDatasourceDoc(datasource, context)))];
|
|
20
|
+
return {
|
|
21
|
+
fileNames: context.fileNames,
|
|
22
|
+
changed: writes.includes(true)
|
|
23
|
+
};
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
//#endregion
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region src/codegen/write-if-changed.ts
|
|
5
|
+
/** Returns whether it had to write. */
|
|
6
|
+
function writeIfChanged(file, content) {
|
|
7
|
+
if (readOrNull(file) === content) return false;
|
|
8
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
9
|
+
writeFileSync(file, content, "utf8");
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
function readOrNull(file) {
|
|
13
|
+
try {
|
|
14
|
+
return readFileSync(file, "utf8");
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { writeIfChanged };
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useRouter } from "@tanstack/react-router";
|
|
1
|
+
import { useRef, useState } from "react";
|
|
3
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
3
|
|
|
5
4
|
//#region src/default-error-overlay.tsx
|
|
6
|
-
function subscribeToAgentTurn(listener) {
|
|
7
|
-
const observer = new MutationObserver(listener);
|
|
8
|
-
observer.observe(document.documentElement, {
|
|
9
|
-
attributes: true,
|
|
10
|
-
attributeFilter: ["data-agent-turn"]
|
|
11
|
-
});
|
|
12
|
-
return () => observer.disconnect();
|
|
13
|
-
}
|
|
14
|
-
function isAgentTurnRunning() {
|
|
15
|
-
return document.documentElement.dataset.agentTurn === "running";
|
|
16
|
-
}
|
|
17
5
|
function DefaultErrorOverlay({ error, reset }) {
|
|
18
6
|
if (process.env.NODE_ENV === "production") return /* @__PURE__ */ jsx("div", {
|
|
19
7
|
style: s.overlay,
|
|
20
|
-
"data-softr-error-overlay": "error",
|
|
21
8
|
children: /* @__PURE__ */ jsxs("div", {
|
|
22
9
|
style: s.card,
|
|
23
10
|
children: [
|
|
@@ -39,33 +26,8 @@ function DefaultErrorOverlay({ error, reset }) {
|
|
|
39
26
|
});
|
|
40
27
|
}
|
|
41
28
|
function DevView({ error, reset }) {
|
|
42
|
-
const running = useSyncExternalStore(subscribeToAgentTurn, isAgentTurnRunning, () => false);
|
|
43
|
-
const [caughtDuringTurn, setCaughtDuringTurn] = useState(running);
|
|
44
|
-
const router = useRouter();
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (running) {
|
|
47
|
-
setCaughtDuringTurn(true);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (!caughtDuringTurn) return;
|
|
51
|
-
const showError = () => setCaughtDuringTurn(false);
|
|
52
|
-
const timer = setTimeout(() => {
|
|
53
|
-
router.invalidate().then(() => {
|
|
54
|
-
reset?.();
|
|
55
|
-
showError();
|
|
56
|
-
}, showError);
|
|
57
|
-
}, 1e3);
|
|
58
|
-
return () => clearTimeout(timer);
|
|
59
|
-
}, [
|
|
60
|
-
running,
|
|
61
|
-
caughtDuringTurn,
|
|
62
|
-
reset,
|
|
63
|
-
router
|
|
64
|
-
]);
|
|
65
|
-
if (running || caughtDuringTurn) return /* @__PURE__ */ jsx(UpdatingView, {});
|
|
66
29
|
return /* @__PURE__ */ jsx("div", {
|
|
67
30
|
style: s.overlay,
|
|
68
|
-
"data-softr-error-overlay": "error",
|
|
69
31
|
children: /* @__PURE__ */ jsxs("div", {
|
|
70
32
|
style: s.card,
|
|
71
33
|
children: [
|
|
@@ -94,39 +56,6 @@ function DevView({ error, reset }) {
|
|
|
94
56
|
})
|
|
95
57
|
});
|
|
96
58
|
}
|
|
97
|
-
function UpdatingView() {
|
|
98
|
-
return /* @__PURE__ */ jsx("div", {
|
|
99
|
-
style: s.overlay,
|
|
100
|
-
"data-softr-error-overlay": "updating",
|
|
101
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
102
|
-
style: s.card,
|
|
103
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
104
|
-
style: s.pending,
|
|
105
|
-
children: /* @__PURE__ */ jsxs("svg", {
|
|
106
|
-
style: icon.md,
|
|
107
|
-
viewBox: "0 0 24 24",
|
|
108
|
-
fill: "none",
|
|
109
|
-
stroke: "currentColor",
|
|
110
|
-
strokeWidth: "2",
|
|
111
|
-
strokeLinecap: "round",
|
|
112
|
-
strokeLinejoin: "round",
|
|
113
|
-
children: [
|
|
114
|
-
/* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
|
|
115
|
-
/* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" }),
|
|
116
|
-
/* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
|
|
117
|
-
/* @__PURE__ */ jsx("path", { d: "M8 16H3v5" })
|
|
118
|
-
]
|
|
119
|
-
})
|
|
120
|
-
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
121
|
-
style: s.heading,
|
|
122
|
-
children: "Updating the app…"
|
|
123
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
124
|
-
style: s.body,
|
|
125
|
-
children: "The agent is still working on this change. The preview will recover once it is complete."
|
|
126
|
-
})] })]
|
|
127
|
-
})
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
59
|
function cleanStack(stack) {
|
|
131
60
|
return stack.split("\n").slice(1).filter((line) => line.trim().startsWith("at ") && !line.includes("/node_modules/")).map((line) => line.replace(/https?:\/\/[^/]+\//, "").replace(/\?[^):]+/g, "")).join("\n");
|
|
132
61
|
}
|
|
@@ -299,17 +228,6 @@ const s = {
|
|
|
299
228
|
color: "var(--destructive, oklch(0.577 0.245 27.325))",
|
|
300
229
|
flexShrink: 0
|
|
301
230
|
},
|
|
302
|
-
pending: {
|
|
303
|
-
display: "flex",
|
|
304
|
-
alignItems: "center",
|
|
305
|
-
justifyContent: "center",
|
|
306
|
-
width: "2.5rem",
|
|
307
|
-
height: "2.5rem",
|
|
308
|
-
borderRadius: "var(--radius, 0.625rem)",
|
|
309
|
-
background: "var(--muted, oklch(0.97 0 0))",
|
|
310
|
-
color: "var(--muted-foreground, oklch(0.556 0 0))",
|
|
311
|
-
flexShrink: 0
|
|
312
|
-
},
|
|
313
231
|
heading: {
|
|
314
232
|
margin: 0,
|
|
315
233
|
fontSize: "1rem",
|
|
@@ -349,7 +267,7 @@ const s = {
|
|
|
349
267
|
border: "1px solid var(--input, oklch(0.922 0 0))",
|
|
350
268
|
borderRadius: "var(--radius, 0.625rem)",
|
|
351
269
|
background: "var(--input-fill, oklch(1 0 0))",
|
|
352
|
-
color: "var(--foreground, oklch(0.
|
|
270
|
+
color: "var(--primary-foreground, oklch(0.985 0 0))",
|
|
353
271
|
fontFamily: "inherit",
|
|
354
272
|
fontSize: "0.875rem",
|
|
355
273
|
fontWeight: 500,
|
package/dist/package.js
CHANGED
package/package.json
CHANGED
|
@@ -1,37 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softr/sdk",
|
|
3
|
-
"version": "0.0.50-alpha.
|
|
3
|
+
"version": "0.0.50-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
|
-
".": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"./server": {
|
|
15
|
-
"types": "./dist/server.d.ts",
|
|
16
|
-
"import": "./dist/server.js"
|
|
17
|
-
},
|
|
18
|
-
"./vite": {
|
|
19
|
-
"types": "./dist/vite.d.ts",
|
|
20
|
-
"import": "./dist/vite.js"
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
"bin": {
|
|
24
|
-
"softr-sdk": "./dist/cli.js"
|
|
25
|
-
},
|
|
26
|
-
"files": [
|
|
27
|
-
"dist",
|
|
28
|
-
"schema",
|
|
29
|
-
"skills",
|
|
30
|
-
"!skills/_artifacts"
|
|
31
|
-
],
|
|
32
|
-
"publishConfig": {
|
|
33
|
-
"access": "public"
|
|
34
|
-
},
|
|
6
|
+
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
|
|
7
|
+
"./client": { "types": "./dist/client.d.ts", "import": "./dist/client.js" },
|
|
8
|
+
"./server": { "types": "./dist/server.d.ts", "import": "./dist/server.js" },
|
|
9
|
+
"./vite": { "types": "./dist/vite.d.ts", "import": "./dist/vite.js" }
|
|
10
|
+
},
|
|
11
|
+
"bin": { "softr-sdk": "./dist/cli.js" },
|
|
12
|
+
"files": ["dist", "schema", "skills", "!skills/_artifacts"],
|
|
13
|
+
"publishConfig": { "access": "public" },
|
|
35
14
|
"scripts": {
|
|
36
15
|
"build": "tsdown",
|
|
37
16
|
"test": "node --import ./src/test-utils/setup.ts --test",
|
|
@@ -63,9 +42,7 @@
|
|
|
63
42
|
"optional": true
|
|
64
43
|
}
|
|
65
44
|
},
|
|
66
|
-
"keywords": [
|
|
67
|
-
"tanstack-intent"
|
|
68
|
-
],
|
|
45
|
+
"keywords": ["tanstack-intent"],
|
|
69
46
|
"intent": {
|
|
70
47
|
"version": 1,
|
|
71
48
|
"repo": "@softr/sdk",
|
|
@@ -11,7 +11,7 @@ description: >-
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: softr-sdk
|
|
14
|
-
library_version: 0.0.50
|
|
14
|
+
library_version: 0.0.50-alpha.1
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
# Softr SDK core
|
|
@@ -137,6 +137,6 @@ type User = {
|
|
|
137
137
|
id: string
|
|
138
138
|
email: string
|
|
139
139
|
fullName: string
|
|
140
|
-
userGroupIds: Array<SoftrUserGroupId> //
|
|
140
|
+
userGroupIds: Array<SoftrUserGroupId> // the app's own group ids
|
|
141
141
|
}
|
|
142
142
|
```
|
|
@@ -16,7 +16,7 @@ description: >-
|
|
|
16
16
|
metadata:
|
|
17
17
|
type: sub-skill
|
|
18
18
|
library: softr-sdk
|
|
19
|
-
library_version: 0.0.50
|
|
19
|
+
library_version: 0.0.50-alpha.1
|
|
20
20
|
---
|
|
21
21
|
|
|
22
22
|
**Boundary.** `auth` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build.
|
|
@@ -12,7 +12,7 @@ description: >-
|
|
|
12
12
|
metadata:
|
|
13
13
|
type: sub-skill
|
|
14
14
|
library: softr-sdk
|
|
15
|
-
library_version: 0.0.50
|
|
15
|
+
library_version: 0.0.50-alpha.1
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
**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.
|
|
@@ -4,11 +4,12 @@ description: >-
|
|
|
4
4
|
Read the signed-in Softr visitor: useCurrentUser and the User type,
|
|
5
5
|
context.user in a route's beforeLoad, and context.user inside a server
|
|
6
6
|
function handler, where Start types it from the app's registered request
|
|
7
|
-
middleware. Covers the null guest
|
|
7
|
+
middleware. Covers the null guest and the generated SoftrUserGroups that
|
|
8
|
+
narrows group ids to the app's own.
|
|
8
9
|
metadata:
|
|
9
10
|
type: sub-skill
|
|
10
11
|
library: softr-sdk
|
|
11
|
-
library_version: 0.0.50
|
|
12
|
+
library_version: 0.0.50-alpha.1
|
|
12
13
|
---
|
|
13
14
|
|
|
14
15
|
# The current user
|
|
@@ -29,7 +30,7 @@ type User = {
|
|
|
29
30
|
id: string
|
|
30
31
|
email: string
|
|
31
32
|
fullName: string
|
|
32
|
-
userGroupIds: Array<SoftrUserGroupId> //
|
|
33
|
+
userGroupIds: Array<SoftrUserGroupId> // the app's own group ids
|
|
33
34
|
}
|
|
34
35
|
```
|
|
35
36
|
|
|
@@ -83,6 +84,14 @@ const getMyInvoices = createServerFn().handler(async ({ context }) => {
|
|
|
83
84
|
|
|
84
85
|
`context.user` is typed. Start derives a handler's context from the app's registered request middleware as well as the server function's own, so there is nothing to import and nothing to cast.
|
|
85
86
|
|
|
87
|
+
### Group ids are the app's own
|
|
88
|
+
|
|
89
|
+
The generated `softr-env.d.ts` declares `SoftrUserGroups` with every group id the app has, the predefined groups included. So `user.userGroupIds` and every `userGroupIds` the SDK accepts narrow to those ids, and an id the app does not have stops compiling. A new group appears there once it exists on the app.
|
|
90
|
+
|
|
91
|
+
For every group's name and id, read `docs/user-groups.md`. It lists every group the app has, the predefined ones included. Copy an id from there; never derive one from a group's name.
|
|
92
|
+
|
|
93
|
+
Never declare `SoftrUserGroups` yourself. A second declaration does not merge with the generated one. TypeScript keeps one of the two without an error, so the ids you wrote are either ignored or hide the real ones.
|
|
94
|
+
|
|
86
95
|
## References
|
|
87
96
|
|
|
88
97
|
- [routes-and-visibility/SKILL.md](../routes-and-visibility/SKILL.md): `isAuthorized` narrows `context.user` while applying the route's rule
|
|
@@ -10,7 +10,7 @@ description: >-
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: sub-skill
|
|
12
12
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.50
|
|
13
|
+
library_version: 0.0.50-alpha.1
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
**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.
|
|
@@ -10,7 +10,7 @@ description: >-
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: sub-skill
|
|
12
12
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.50
|
|
13
|
+
library_version: 0.0.50-alpha.1
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
**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.
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.50
|
|
12
|
+
library_version: 0.0.50-alpha.1
|
|
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.
|
|
@@ -11,7 +11,7 @@ description: >-
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: sub-skill
|
|
13
13
|
library: softr-sdk
|
|
14
|
-
library_version: 0.0.50
|
|
14
|
+
library_version: 0.0.50-alpha.1
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
**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.
|
|
@@ -13,7 +13,7 @@ description: >-
|
|
|
13
13
|
metadata:
|
|
14
14
|
type: sub-skill
|
|
15
15
|
library: softr-sdk
|
|
16
|
-
library_version: 0.0.50
|
|
16
|
+
library_version: 0.0.50-alpha.1
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
**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.
|
|
@@ -51,8 +51,8 @@ const publishedPosts = defineQuery({
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
- `id` is the name the person building the app sees, so `publishedPosts`, not `q1`. It must be unique across the app: no two declarations may share one, queries and mutations counted together.
|
|
54
|
-
- `datasourceId` is the opaque UUID from the table's registration on the application (Softr Studio's Data tab, or the `
|
|
55
|
-
- The keys of `fields` are names you choose. The values are field ids, opaque strings the table's schema gives you: `
|
|
54
|
+
- `datasourceId` is the opaque UUID from the table's registration on the application (Softr Studio's Data tab, or the `application_register_data_source` tool when an agent does it). It is not the table id and is never derived from the table's name.
|
|
55
|
+
- The keys of `fields` are names you choose. The values are field ids, opaque strings the table's schema gives you: `database_get_table` returns them for a Softr table, `integration_list_table_fields` for a table on a connected database. Everything downstream speaks your names: the returned records, `where`, `orderBy`.
|
|
56
56
|
- `permissions` declares who may run the query, and the SDK enforces it:
|
|
57
57
|
- `{ userGroup: 'ALL_USERS' }`: everyone, signed out included.
|
|
58
58
|
- `{ userGroup: 'NON_LOGGED_IN_USERS' }`: signed-out visitors only.
|
|
@@ -12,7 +12,7 @@ description: >-
|
|
|
12
12
|
metadata:
|
|
13
13
|
type: sub-skill
|
|
14
14
|
library: softr-sdk
|
|
15
|
-
library_version: 0.0.50
|
|
15
|
+
library_version: 0.0.50-alpha.1
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
**Boundary.** `defineVisibility`, the access functions and the error predicates come from `@softr/sdk` and work anywhere.
|
|
@@ -64,6 +64,8 @@ defineVisibility({ // those groups only
|
|
|
64
64
|
|
|
65
65
|
Absent or empty `userGroupIds` means **any** signed-in visitor, not "nobody".
|
|
66
66
|
|
|
67
|
+
Take every id in `userGroupIds` from `docs/user-groups.md`, which lists each group the app has with its name and id. An id the app does not have is a type error.
|
|
68
|
+
|
|
67
69
|
## Core Patterns
|
|
68
70
|
|
|
69
71
|
### Guard a whole section with one layout route
|
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: sub-skill
|
|
11
11
|
library: softr-sdk
|
|
12
|
-
library_version: 0.0.50
|
|
12
|
+
library_version: 0.0.50-alpha.1
|
|
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.
|
|
@@ -45,7 +45,7 @@ const sendWelcomeEmail = defineWorkflow({
|
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
- `id` is a name you choose, unique across the app.
|
|
48
|
-
- `workflowId` and `triggerId` are opaque UUIDs identifying the workflow and its **Run custom workflow** trigger. `
|
|
48
|
+
- `workflowId` and `triggerId` are opaque UUIDs identifying the workflow and its **Run custom workflow** trigger. `workflow_list` returns both. Pass them as inline string literals — never derive them at runtime.
|
|
49
49
|
- `permissions` declares who may run the trigger. Uses the same format as queries and mutations.
|
|
50
50
|
- `body` is hardcoded data the server resolves before calling the workflow. It is **not** passed by the caller at runtime — the workflow always receives it. Template values draw from the signed-in user and the current time (see table below).
|
|
51
51
|
|