@softr/sdk 0.0.56 → 0.0.58-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/codegen/datasource-docs.js +3 -3
- package/dist/codegen/datasource-schema.js +65 -0
- package/dist/codegen/datasource-types.js +13 -4
- package/dist/codegen/fetch-metadata.js +3 -1
- package/dist/codegen/write-datasource-docs.js +7 -2
- package/dist/package.js +1 -1
- package/package.json +1 -1
- package/skills/softr-sdk/SKILL.md +1 -1
- 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 +1 -1
- 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/pwa/SKILL.md +1 -1
- package/skills/softr-sdk/record-mutations/SKILL.md +1 -1
- package/skills/softr-sdk/record-queries/SKILL.md +1 -1
- package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
- package/skills/softr-sdk/routes-and-visibility/SKILL.md +1 -1
- package/skills/softr-sdk/workflows/SKILL.md +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assert, requireDefined } from "../assertions.js";
|
|
2
|
-
import { choiceEntries, fieldTypeLiteral, isWritable, option, readExample, resolvedShape, scalarExample, underlyingType } from "./datasource-types.js";
|
|
2
|
+
import { choiceEntries, fieldTypeLiteral, isWritable, linkedDatasource, option, readExample, resolvedShape, scalarExample, underlyingType } from "./datasource-types.js";
|
|
3
3
|
|
|
4
4
|
//#region src/codegen/datasource-docs.ts
|
|
5
5
|
const DOC_NOTE = "> This document is generated automatically when a change is made in the datasource.";
|
|
@@ -35,7 +35,7 @@ function relatedLinks(datasource, context) {
|
|
|
35
35
|
const linkedId = option(field.options, "linkedTableId");
|
|
36
36
|
if (typeof linkedId !== "string" || seen.has(linkedId)) continue;
|
|
37
37
|
seen.add(linkedId);
|
|
38
|
-
const linked = context.datasources
|
|
38
|
+
const linked = linkedDatasource(linkedId, context.datasources);
|
|
39
39
|
if (linked === void 0 || linked.id === datasource.id) continue;
|
|
40
40
|
links.push(docLink(linked, context));
|
|
41
41
|
}
|
|
@@ -138,7 +138,7 @@ function optionLines(field) {
|
|
|
138
138
|
function linkLine(field, context) {
|
|
139
139
|
const linkedId = option(field.options, "linkedTableId");
|
|
140
140
|
if (typeof linkedId !== "string") return [];
|
|
141
|
-
const linked = context.datasources
|
|
141
|
+
const linked = linkedDatasource(linkedId, context.datasources);
|
|
142
142
|
if (linked === void 0) return [`- Links to: ${linkedId}, not registered`];
|
|
143
143
|
return [`- Links to: ${docLink(linked, context)} (${linked.id})`];
|
|
144
144
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { linkedDatasource, resolvedShape } from "./datasource-types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/codegen/datasource-schema.ts
|
|
4
|
+
/**
|
|
5
|
+
* The types a `contains` filter accepts, so a linked picker knows whether it can search its label.
|
|
6
|
+
* Kept in step with `TextFieldType` in ../datasource/fields.ts, which is not exported.
|
|
7
|
+
*/
|
|
8
|
+
const SEARCHABLE_TYPES = new Set([
|
|
9
|
+
"SINGLE_LINE_TEXT",
|
|
10
|
+
"LONG_TEXT",
|
|
11
|
+
"URL",
|
|
12
|
+
"EMAIL",
|
|
13
|
+
"PHONE",
|
|
14
|
+
"ADDRESS",
|
|
15
|
+
"RECORD_ID",
|
|
16
|
+
"LOCATION",
|
|
17
|
+
"TIME",
|
|
18
|
+
"BUTTON"
|
|
19
|
+
]);
|
|
20
|
+
/**
|
|
21
|
+
* One datasource's metadata as JSON, for tools that generate code from it.
|
|
22
|
+
*
|
|
23
|
+
* A linked field carries its target already resolved: the registration id and the field a person
|
|
24
|
+
* recognises a row by. Readers never map table ids themselves. `datasources` is keyed by
|
|
25
|
+
* registration id.
|
|
26
|
+
*/
|
|
27
|
+
function renderDatasourceSchema(datasource, datasources) {
|
|
28
|
+
const schema = {
|
|
29
|
+
id: datasource.id,
|
|
30
|
+
name: datasource.name,
|
|
31
|
+
primaryFieldId: datasource.primaryFieldId,
|
|
32
|
+
fields: datasource.fields.map((field) => schemaField(field, datasources))
|
|
33
|
+
};
|
|
34
|
+
return `${JSON.stringify(schema, null, 2)}\n`;
|
|
35
|
+
}
|
|
36
|
+
function schemaField(field, datasources) {
|
|
37
|
+
const base = {
|
|
38
|
+
id: field.id,
|
|
39
|
+
name: field.name,
|
|
40
|
+
type: field.type,
|
|
41
|
+
allowMultipleEntries: field.allowMultipleEntries,
|
|
42
|
+
required: field.required,
|
|
43
|
+
readonly: field.readonly,
|
|
44
|
+
createOnly: field.createOnly,
|
|
45
|
+
aiOnly: field.aiOnly === true,
|
|
46
|
+
defaultValue: field.defaultValue,
|
|
47
|
+
options: field.options
|
|
48
|
+
};
|
|
49
|
+
if (field.type !== "LINKED_RECORD") return base;
|
|
50
|
+
const linkedTableId = field.options?.["linkedTableId"];
|
|
51
|
+
const linked = typeof linkedTableId === "string" ? linkedDatasource(linkedTableId, datasources) : void 0;
|
|
52
|
+
if (linked === void 0 || linked.primaryFieldId === "") return base;
|
|
53
|
+
const primary = linked.fields.find((entry) => entry.id === linked.primaryFieldId);
|
|
54
|
+
return {
|
|
55
|
+
...base,
|
|
56
|
+
linked: {
|
|
57
|
+
datasourceId: linked.id,
|
|
58
|
+
labelFieldId: linked.primaryFieldId,
|
|
59
|
+
labelSearchable: primary !== void 0 && SEARCHABLE_TYPES.has(resolvedShape(primary).type)
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { renderDatasourceSchema };
|
|
@@ -46,6 +46,15 @@ function byId(datasources) {
|
|
|
46
46
|
return new Map(datasources.map((datasource) => [datasource.id, datasource]));
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
+
* The registered datasource a `LINKED_RECORD`'s `linkedTableId` names. That id is the underlying
|
|
50
|
+
* table's own id, kept as `tableId`, while `datasources` is keyed by registration id. The
|
|
51
|
+
* registration id is the fallback, in case a link names that one instead.
|
|
52
|
+
*/
|
|
53
|
+
function linkedDatasource(linkedTableId, datasources) {
|
|
54
|
+
for (const datasource of datasources.values()) if (datasource.tableId === linkedTableId) return datasource;
|
|
55
|
+
return datasources.get(linkedTableId);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
49
58
|
* A TypeScript name per datasource, derived from its own name, with the id standing in wherever
|
|
50
59
|
* the name cannot: two datasources can share a name, and a name can be blank or hold nothing an
|
|
51
60
|
* identifier can keep. The id is unique by construction, so it settles both.
|
|
@@ -238,7 +247,7 @@ function union(members) {
|
|
|
238
247
|
function linkedRecord(options, context) {
|
|
239
248
|
const linkedTableId = option(options, "linkedTableId");
|
|
240
249
|
if (typeof linkedTableId !== "string") return "LinkedRecord";
|
|
241
|
-
const linked = context.datasources
|
|
250
|
+
const linked = linkedDatasource(linkedTableId, context.datasources);
|
|
242
251
|
if (linked === void 0 || linked.primaryFieldId === "") return "LinkedRecord";
|
|
243
252
|
return `LinkedRecord<NonNullable<${`${nameOf(linked.id, context)}Read[${quote(linked.primaryFieldId)}]`}>>`;
|
|
244
253
|
}
|
|
@@ -339,7 +348,7 @@ function lookupTag(field, datasource, context) {
|
|
|
339
348
|
const link = datasource.fields.find((candidate) => candidate.id === linkId);
|
|
340
349
|
if (link === void 0) return null;
|
|
341
350
|
const linkedTableId = option(link.options, "linkedTableId");
|
|
342
|
-
const target = (typeof linkedTableId === "string" ? context.datasources
|
|
351
|
+
const target = (typeof linkedTableId === "string" ? linkedDatasource(linkedTableId, context.datasources) : void 0)?.fields.find((candidate) => candidate.id === targetId);
|
|
343
352
|
if (target === void 0) return null;
|
|
344
353
|
return `@lookup ${link.name} -> ${target.name}`;
|
|
345
354
|
}
|
|
@@ -396,7 +405,7 @@ function readExample(shape, datasources) {
|
|
|
396
405
|
function linkedLabelExample(options, datasources) {
|
|
397
406
|
const linkedTableId = option(options, "linkedTableId");
|
|
398
407
|
if (typeof linkedTableId !== "string") return "null";
|
|
399
|
-
const linked =
|
|
408
|
+
const linked = linkedDatasource(linkedTableId, datasources);
|
|
400
409
|
const primary = linked?.fields.find((field) => field.id === linked.primaryFieldId);
|
|
401
410
|
if (linked === void 0 || primary === void 0) return "null";
|
|
402
411
|
const rest = new Map(datasources);
|
|
@@ -485,4 +494,4 @@ function indent(block) {
|
|
|
485
494
|
}
|
|
486
495
|
|
|
487
496
|
//#endregion
|
|
488
|
-
export { choiceEntries, fieldTypeLiteral, isWritable, option, readExample, renderDatasourceTypes, resolvedShape, scalarExample, underlyingType };
|
|
497
|
+
export { choiceEntries, fieldTypeLiteral, isWritable, linkedDatasource, option, readExample, renderDatasourceTypes, resolvedShape, scalarExample, underlyingType };
|
|
@@ -22,9 +22,11 @@ async function fetchSoftrEnvInput(source) {
|
|
|
22
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
|
+
const metadata = await apiFetch(`${datasourceApiUrl}/v1/datasource/vibe/studio/applications/${applicationId}/datasources/${datasource.id}/metadata`, token);
|
|
25
26
|
return {
|
|
26
|
-
...
|
|
27
|
+
...metadata,
|
|
27
28
|
id: datasource.id,
|
|
29
|
+
tableId: metadata.id,
|
|
28
30
|
name
|
|
29
31
|
};
|
|
30
32
|
})),
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { requireDefined } from "../assertions.js";
|
|
2
2
|
import { renderDatasourceDoc, renderDatasourceIndex } from "./datasource-docs.js";
|
|
3
|
+
import { renderDatasourceSchema } from "./datasource-schema.js";
|
|
3
4
|
import { docFileNames } from "./doc-file-names.js";
|
|
4
5
|
import { writeIfChanged } from "./write-if-changed.js";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
|
|
7
8
|
//#region src/codegen/write-datasource-docs.ts
|
|
8
9
|
/**
|
|
9
|
-
* Writes the index and one
|
|
10
|
+
* Writes the index, and one doc and one schema JSON per datasource, and touches nothing else in
|
|
10
11
|
* `docsDir`: the caller resolves it against `process.cwd()`, which can be any
|
|
11
12
|
* directory a developer ran the bin from. `changed` is true when any file it
|
|
12
13
|
* wrote now reads differently.
|
|
@@ -16,7 +17,11 @@ function writeDatasourceDocs(datasources, docsDir) {
|
|
|
16
17
|
datasources: new Map(datasources.map((datasource) => [datasource.id, datasource])),
|
|
17
18
|
fileNames: docFileNames(datasources)
|
|
18
19
|
};
|
|
19
|
-
const writes = [
|
|
20
|
+
const writes = [
|
|
21
|
+
writeIfChanged(path.join(docsDir, "README.md"), renderDatasourceIndex(datasources, context)),
|
|
22
|
+
...datasources.map((datasource) => writeIfChanged(path.join(docsDir, requireDefined(context.fileNames.get(datasource.id))), renderDatasourceDoc(datasource, context))),
|
|
23
|
+
...datasources.map((datasource) => writeIfChanged(path.join(docsDir, requireDefined(context.fileNames.get(datasource.id)).replace(/\.md$/, ".json")), renderDatasourceSchema(datasource, context.datasources)))
|
|
24
|
+
];
|
|
20
25
|
return {
|
|
21
26
|
fileNames: context.fileNames,
|
|
22
27
|
changed: writes.includes(true)
|
package/dist/package.js
CHANGED
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ description: >-
|
|
|
16
16
|
metadata:
|
|
17
17
|
type: sub-skill
|
|
18
18
|
library: softr-sdk
|
|
19
|
-
library_version: 0.0.
|
|
19
|
+
library_version: 0.0.58-alpha.0
|
|
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.
|
|
15
|
+
library_version: 0.0.58-alpha.0
|
|
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.
|
|
@@ -10,7 +10,7 @@ description: >-
|
|
|
10
10
|
metadata:
|
|
11
11
|
type: sub-skill
|
|
12
12
|
library: softr-sdk
|
|
13
|
-
library_version: 0.0.
|
|
13
|
+
library_version: 0.0.58-alpha.0
|
|
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.
|
|
13
|
+
library_version: 0.0.58-alpha.0
|
|
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.
|
|
12
|
+
library_version: 0.0.58-alpha.0
|
|
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.
|
|
14
|
+
library_version: 0.0.58-alpha.0
|
|
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.
|
|
16
|
+
library_version: 0.0.58-alpha.0
|
|
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. 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.
|
|
@@ -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.58-alpha.0
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
**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.58-alpha.0
|
|
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.
|