@softr/sdk 0.0.54-alpha.0 → 0.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.d.ts +1 -31
- package/dist/action.js +1 -26
- package/dist/auth/isomorphic.d.ts +1 -1
- package/dist/auth/isomorphic.js +2 -1
- package/dist/boot-error.js +1 -0
- package/dist/client.d.ts +1 -2
- package/dist/client.js +1 -2
- package/dist/default-error-overlay.d.ts +2 -2
- package/dist/default-error-overlay.js +86 -2
- package/dist/fetch.js +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/mcp.d.ts +2 -2
- package/dist/package.js +1 -1
- package/dist/pwa.d.ts +54 -0
- package/dist/pwa.js +152 -0
- package/dist/server.d.ts +1 -2
- package/dist/server.js +1 -2
- package/package.json +1 -3
- package/skills/softr-sdk/SKILL.md +7 -7
- 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 +77 -0
- package/skills/softr-sdk/record-mutations/SKILL.md +1 -1
- package/skills/softr-sdk/record-queries/SKILL.md +2 -2
- 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
- package/dist/comments/comments.d.ts +0 -137
- package/dist/comments/comments.js +0 -329
- package/dist/comments/use-comment-composer.d.ts +0 -37
- package/dist/comments/use-comment-composer.js +0 -127
- package/skills/softr-sdk/comments/SKILL.md +0 -149
package/dist/action.d.ts
CHANGED
|
@@ -234,35 +234,5 @@ type IntegrationDefinition<TId extends string> = {
|
|
|
234
234
|
* const res = await listEvents({ query: { timeMin: now, singleEvents: "true" } })
|
|
235
235
|
*/
|
|
236
236
|
declare function defineIntegration<const TId extends string>(definition: IntegrationDefinition<TId>): IntegrationAction<TId>;
|
|
237
|
-
type CommentsResource = {
|
|
238
|
-
type: "COMMENTS";
|
|
239
|
-
datasourceId: SoftrDatasourceId;
|
|
240
|
-
};
|
|
241
|
-
/** Comment threads on the records of one datasource. It has no `permissions`. */
|
|
242
|
-
type CommentsAction<TId extends string = string> = {
|
|
243
|
-
id: TId;
|
|
244
|
-
resource: CommentsResource;
|
|
245
|
-
};
|
|
246
|
-
type CommentsDefinition<TId extends string> = {
|
|
247
|
-
id: TId;
|
|
248
|
-
datasourceId: SoftrDatasourceId;
|
|
249
|
-
};
|
|
250
|
-
/**
|
|
251
|
-
* Declares comment threads on the records of a datasource. Each record has one thread.
|
|
252
|
-
*
|
|
253
|
-
* Who may read, post and delete comes from the app's comment settings in Softr, not from code. Two
|
|
254
|
-
* declarations on the same datasource share the same threads.
|
|
255
|
-
*
|
|
256
|
-
* Pass the returned object to `comments.*` from `@softr/sdk/server`.
|
|
257
|
-
*
|
|
258
|
-
* @example
|
|
259
|
-
* const orderComments = defineComments({
|
|
260
|
-
* id: "orderComments",
|
|
261
|
-
* datasourceId: "8f14e45f-ceea-467a-9d0b-2c1a0e3b5d77",
|
|
262
|
-
* })
|
|
263
|
-
*
|
|
264
|
-
* const thread = await comments.list(orderComments, { recordId })
|
|
265
|
-
*/
|
|
266
|
-
declare function defineComments<const TId extends string>(definition: CommentsDefinition<TId>): CommentsAction<TId>;
|
|
267
237
|
//#endregion
|
|
268
|
-
export { ActionData, ActionPermissions, ActionRecord, ActionShape,
|
|
238
|
+
export { ActionData, ActionPermissions, ActionRecord, ActionShape, DatasourceAction, FieldTypeOf, IntegrationAction, ReadOf, ReadShape, SoftrDatasources, WorkflowAction, defineIntegration, defineMutation, defineQuery, defineWorkflow };
|
package/dist/action.js
CHANGED
|
@@ -50,31 +50,6 @@ function defineIntegration(definition) {
|
|
|
50
50
|
permissions
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Declares comment threads on the records of a datasource. Each record has one thread.
|
|
55
|
-
*
|
|
56
|
-
* Who may read, post and delete comes from the app's comment settings in Softr, not from code. Two
|
|
57
|
-
* declarations on the same datasource share the same threads.
|
|
58
|
-
*
|
|
59
|
-
* Pass the returned object to `comments.*` from `@softr/sdk/server`.
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* const orderComments = defineComments({
|
|
63
|
-
* id: "orderComments",
|
|
64
|
-
* datasourceId: "8f14e45f-ceea-467a-9d0b-2c1a0e3b5d77",
|
|
65
|
-
* })
|
|
66
|
-
*
|
|
67
|
-
* const thread = await comments.list(orderComments, { recordId })
|
|
68
|
-
*/
|
|
69
|
-
function defineComments(definition) {
|
|
70
|
-
return {
|
|
71
|
-
id: definition.id,
|
|
72
|
-
resource: {
|
|
73
|
-
type: "COMMENTS",
|
|
74
|
-
datasourceId: definition.datasourceId
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
53
|
function isDatasourceAction(value) {
|
|
79
54
|
if (typeof value !== "object" || value === null) return false;
|
|
80
55
|
const candidate = value;
|
|
@@ -83,4 +58,4 @@ function isDatasourceAction(value) {
|
|
|
83
58
|
}
|
|
84
59
|
|
|
85
60
|
//#endregion
|
|
86
|
-
export {
|
|
61
|
+
export { defineIntegration, defineMutation, defineQuery, defineWorkflow, isDatasourceAction };
|
|
@@ -17,7 +17,7 @@ import * as _tanstack_react_start0 from "@tanstack/react-start";
|
|
|
17
17
|
* requestMiddleware: [softrAuthMiddleware()],
|
|
18
18
|
* }))
|
|
19
19
|
*/
|
|
20
|
-
declare function softrAuthMiddleware(): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, readonly [_tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>], {
|
|
20
|
+
declare function softrAuthMiddleware(): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, readonly [_tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>, _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>], {
|
|
21
21
|
user: User | null;
|
|
22
22
|
permissions: Permissions;
|
|
23
23
|
}>;
|
package/dist/auth/isomorphic.js
CHANGED
|
@@ -4,6 +4,7 @@ import { sessionConfig } from "./config.js";
|
|
|
4
4
|
import { clearSession, makeSessionDataCookie, readSessionData, readSessionToken } from "./session.js";
|
|
5
5
|
import { makePlatform } from "./platform.js";
|
|
6
6
|
import { bootErrorMiddleware } from "../boot-error.js";
|
|
7
|
+
import { pwaMiddleware } from "../pwa.js";
|
|
7
8
|
import { createMiddleware, createServerFn, getGlobalStartContext } from "@tanstack/react-start";
|
|
8
9
|
import { rootRouteId, useRouteContext } from "@tanstack/react-router";
|
|
9
10
|
|
|
@@ -21,7 +22,7 @@ import { rootRouteId, useRouteContext } from "@tanstack/react-router";
|
|
|
21
22
|
* }))
|
|
22
23
|
*/
|
|
23
24
|
function softrAuthMiddleware() {
|
|
24
|
-
return createMiddleware({ type: "request" }).middleware([bootErrorMiddleware()]).server(async ({ next }) => {
|
|
25
|
+
return createMiddleware({ type: "request" }).middleware([bootErrorMiddleware(), pwaMiddleware()]).server(async ({ next }) => {
|
|
25
26
|
async function getSession() {
|
|
26
27
|
const token = readSessionToken();
|
|
27
28
|
const cached = readSessionData(token);
|
package/dist/boot-error.js
CHANGED
|
@@ -16,6 +16,7 @@ function bootErrorMiddleware() {
|
|
|
16
16
|
return await next();
|
|
17
17
|
} catch (error) {
|
|
18
18
|
if (handlerType === "serverFn" || error instanceof Response || isRedirect(error) || isNotFound(error)) throw error;
|
|
19
|
+
console.error("[@softr/sdk] boot error:", error);
|
|
19
20
|
return new Response(errorPage(error), {
|
|
20
21
|
status: 500,
|
|
21
22
|
headers: { "content-type": "text/html; charset=utf-8" }
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { FileItem, useFileUpload } from "./core/use-file-upload.js";
|
|
2
|
-
import { useCommentComposer } from "./comments/use-comment-composer.js";
|
|
3
2
|
import "@tanstack/react-start/client-only";
|
|
4
|
-
export { type FileItem,
|
|
3
|
+
export { type FileItem, useFileUpload };
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react1 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): react1.JSX.Element;
|
|
12
12
|
//#endregion
|
|
13
13
|
export { DefaultErrorOverlay };
|
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
import { useRef, useState } from "react";
|
|
1
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
2
|
+
import { useRouter } from "@tanstack/react-router";
|
|
2
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
4
|
|
|
4
5
|
//#region src/default-error-overlay.tsx
|
|
6
|
+
/** Set by Studio's canvas engine, which ships apart from the SDK each app pins, so renaming it breaks older apps. */
|
|
7
|
+
const AGENT_TURN_ATTRIBUTE = "data-agent-turn";
|
|
8
|
+
function subscribeToAgentTurn(listener) {
|
|
9
|
+
const observer = new MutationObserver(listener);
|
|
10
|
+
observer.observe(document.documentElement, {
|
|
11
|
+
attributes: true,
|
|
12
|
+
attributeFilter: [AGENT_TURN_ATTRIBUTE]
|
|
13
|
+
});
|
|
14
|
+
return () => observer.disconnect();
|
|
15
|
+
}
|
|
16
|
+
function isAgentTurnRunning() {
|
|
17
|
+
return document.documentElement.getAttribute(AGENT_TURN_ATTRIBUTE) === "running";
|
|
18
|
+
}
|
|
5
19
|
function DefaultErrorOverlay({ error, reset }) {
|
|
6
20
|
if (process.env.NODE_ENV === "production") return /* @__PURE__ */ jsx("div", {
|
|
7
21
|
style: s.overlay,
|
|
22
|
+
"data-softr-error-overlay": "error",
|
|
8
23
|
children: /* @__PURE__ */ jsxs("div", {
|
|
9
24
|
style: s.card,
|
|
10
25
|
children: [
|
|
@@ -26,8 +41,33 @@ function DefaultErrorOverlay({ error, reset }) {
|
|
|
26
41
|
});
|
|
27
42
|
}
|
|
28
43
|
function DevView({ error, reset }) {
|
|
44
|
+
const running = useSyncExternalStore(subscribeToAgentTurn, isAgentTurnRunning, () => false);
|
|
45
|
+
const [retryPending, setRetryPending] = useState(running);
|
|
46
|
+
const router = useRouter();
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (running) {
|
|
49
|
+
setRetryPending(true);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!retryPending) return;
|
|
53
|
+
const showError = () => setRetryPending(false);
|
|
54
|
+
const timer = setTimeout(() => {
|
|
55
|
+
router.invalidate().then(() => {
|
|
56
|
+
reset?.();
|
|
57
|
+
showError();
|
|
58
|
+
}, showError);
|
|
59
|
+
}, 1e3);
|
|
60
|
+
return () => clearTimeout(timer);
|
|
61
|
+
}, [
|
|
62
|
+
running,
|
|
63
|
+
retryPending,
|
|
64
|
+
reset,
|
|
65
|
+
router
|
|
66
|
+
]);
|
|
67
|
+
if (running || retryPending) return /* @__PURE__ */ jsx(UpdatingView, {});
|
|
29
68
|
return /* @__PURE__ */ jsx("div", {
|
|
30
69
|
style: s.overlay,
|
|
70
|
+
"data-softr-error-overlay": "error",
|
|
31
71
|
children: /* @__PURE__ */ jsxs("div", {
|
|
32
72
|
style: s.card,
|
|
33
73
|
children: [
|
|
@@ -56,6 +96,39 @@ function DevView({ error, reset }) {
|
|
|
56
96
|
})
|
|
57
97
|
});
|
|
58
98
|
}
|
|
99
|
+
function UpdatingView() {
|
|
100
|
+
return /* @__PURE__ */ jsx("div", {
|
|
101
|
+
style: s.overlay,
|
|
102
|
+
"data-softr-error-overlay": "updating",
|
|
103
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
104
|
+
style: s.card,
|
|
105
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
106
|
+
style: s.pending,
|
|
107
|
+
children: /* @__PURE__ */ jsxs("svg", {
|
|
108
|
+
style: icon.md,
|
|
109
|
+
viewBox: "0 0 24 24",
|
|
110
|
+
fill: "none",
|
|
111
|
+
stroke: "currentColor",
|
|
112
|
+
strokeWidth: "2",
|
|
113
|
+
strokeLinecap: "round",
|
|
114
|
+
strokeLinejoin: "round",
|
|
115
|
+
children: [
|
|
116
|
+
/* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
|
|
117
|
+
/* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" }),
|
|
118
|
+
/* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
|
|
119
|
+
/* @__PURE__ */ jsx("path", { d: "M8 16H3v5" })
|
|
120
|
+
]
|
|
121
|
+
})
|
|
122
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
123
|
+
style: s.heading,
|
|
124
|
+
children: "Updating the app…"
|
|
125
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
126
|
+
style: s.body,
|
|
127
|
+
children: "The agent is still working on this change. The preview will recover once it is complete."
|
|
128
|
+
})] })]
|
|
129
|
+
})
|
|
130
|
+
});
|
|
131
|
+
}
|
|
59
132
|
function cleanStack(stack) {
|
|
60
133
|
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");
|
|
61
134
|
}
|
|
@@ -228,6 +301,17 @@ const s = {
|
|
|
228
301
|
color: "var(--destructive, oklch(0.577 0.245 27.325))",
|
|
229
302
|
flexShrink: 0
|
|
230
303
|
},
|
|
304
|
+
pending: {
|
|
305
|
+
display: "flex",
|
|
306
|
+
alignItems: "center",
|
|
307
|
+
justifyContent: "center",
|
|
308
|
+
width: "2.5rem",
|
|
309
|
+
height: "2.5rem",
|
|
310
|
+
borderRadius: "var(--radius, 0.625rem)",
|
|
311
|
+
background: "var(--muted, oklch(0.97 0 0))",
|
|
312
|
+
color: "var(--muted-foreground, oklch(0.556 0 0))",
|
|
313
|
+
flexShrink: 0
|
|
314
|
+
},
|
|
231
315
|
heading: {
|
|
232
316
|
margin: 0,
|
|
233
317
|
fontSize: "1rem",
|
|
@@ -267,7 +351,7 @@ const s = {
|
|
|
267
351
|
border: "1px solid var(--input, oklch(0.922 0 0))",
|
|
268
352
|
borderRadius: "var(--radius, 0.625rem)",
|
|
269
353
|
background: "var(--input-fill, oklch(1 0 0))",
|
|
270
|
-
color: "var(--
|
|
354
|
+
color: "var(--foreground, oklch(0.145 0 0))",
|
|
271
355
|
fontFamily: "inherit",
|
|
272
356
|
fontSize: "0.875rem",
|
|
273
357
|
fontWeight: 500,
|
package/dist/fetch.js
CHANGED
|
@@ -8,11 +8,11 @@ const fetch = async (input, init) => {
|
|
|
8
8
|
const started = Date.now();
|
|
9
9
|
try {
|
|
10
10
|
const response = await globalThis.fetch(input, init);
|
|
11
|
-
logger.
|
|
11
|
+
if (!response.ok) logger.warn(`${method} ${response.status} ${url} ${Date.now() - started}ms`);
|
|
12
12
|
return response;
|
|
13
13
|
} catch (error) {
|
|
14
14
|
const label = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
15
|
-
logger.
|
|
15
|
+
logger.error(`${method} ERR ${url} ${label} in ${Date.now() - started}ms`);
|
|
16
16
|
throw error;
|
|
17
17
|
}
|
|
18
18
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { SoftrUserGroupId, SoftrUserGroups, User } from "./auth/user.js";
|
|
2
|
-
import { ActionPermissions, SoftrDatasources,
|
|
2
|
+
import { ActionPermissions, SoftrDatasources, 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
|
-
import { CommentMention, CommentThread, CommentView } from "./comments/comments.js";
|
|
6
5
|
import { SoftrContext } from "./auth/context.js";
|
|
7
6
|
import { checkAccess, defineVisibility, isAuthorized } from "./visibility/visibility.js";
|
|
8
7
|
import { assertRouteAccess, guardRoute, isAccessError, isAlreadyAuthenticatedError, isForbiddenError, isUnauthenticatedError } from "./visibility/route.js";
|
|
9
8
|
import { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser } from "./auth/isomorphic.js";
|
|
10
9
|
import { softrMcpMiddleware } from "./mcp.js";
|
|
11
10
|
import { useUserTimezone } from "./timezone.js";
|
|
11
|
+
import { PwaInstall, PwaPlatform, SoftrPwaHead, usePwaInstall } from "./pwa.js";
|
|
12
12
|
import { DefaultErrorOverlay } from "./default-error-overlay.js";
|
|
13
13
|
import { Audience } from "./visibility/audience.js";
|
|
14
|
-
export { type ActionPermissions, Audience,
|
|
14
|
+
export { type ActionPermissions, Audience, DefaultErrorOverlay, type FileAttachment, type FileItem, type PwaInstall, type PwaPlatform, type SoftrContext, type SoftrDatasources, SoftrPwaHead, type SoftrUserGroupId, type SoftrUserGroups, type User, assertRouteAccess, checkAccess, defineIntegration, defineMutation, defineQuery, defineVisibility, defineWorkflow, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, softrAuthMiddleware, softrMcpMiddleware, useCurrentUser, useFileUpload, usePwaInstall, useUserTimezone };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useFileUpload } from "./core/use-file-upload.js";
|
|
2
|
-
import {
|
|
2
|
+
import { defineIntegration, defineMutation, defineQuery, defineWorkflow } from "./action.js";
|
|
3
3
|
import { checkAccess, defineVisibility, isAuthorized } from "./visibility/visibility.js";
|
|
4
4
|
import { assertRouteAccess, guardRoute, isAccessError, isAlreadyAuthenticatedError, isForbiddenError, isUnauthenticatedError } from "./visibility/route.js";
|
|
5
|
+
import { SoftrPwaHead, usePwaInstall } from "./pwa.js";
|
|
5
6
|
import { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser } from "./auth/isomorphic.js";
|
|
6
7
|
import { softrMcpMiddleware } from "./mcp.js";
|
|
7
8
|
import { useUserTimezone } from "./timezone.js";
|
|
8
9
|
import { DefaultErrorOverlay } from "./default-error-overlay.js";
|
|
9
10
|
import { Audience } from "./visibility/audience.js";
|
|
10
11
|
|
|
11
|
-
export { Audience, DefaultErrorOverlay, assertRouteAccess, checkAccess,
|
|
12
|
+
export { Audience, DefaultErrorOverlay, SoftrPwaHead, assertRouteAccess, checkAccess, defineIntegration, defineMutation, defineQuery, defineVisibility, defineWorkflow, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, softrAuthMiddleware, softrMcpMiddleware, useCurrentUser, useFileUpload, 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
|
|
|
@@ -37,6 +37,6 @@ type SoftrMcpOptions = {
|
|
|
37
37
|
* ],
|
|
38
38
|
* }))
|
|
39
39
|
*/
|
|
40
|
-
declare function softrMcpMiddleware(options?: SoftrMcpOptions):
|
|
40
|
+
declare function softrMcpMiddleware(options?: SoftrMcpOptions): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>;
|
|
41
41
|
//#endregion
|
|
42
42
|
export { softrMcpMiddleware };
|
package/dist/package.js
CHANGED
package/dist/pwa.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as react0 from "react";
|
|
2
|
+
import "@tanstack/react-start";
|
|
3
|
+
|
|
4
|
+
//#region src/pwa.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The links that make the app installable: its manifest and its home screen
|
|
8
|
+
* icon. Render it once, in `<head>` in the root route's `RootDocument`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* function RootDocument({ children }: { children: React.ReactNode }) {
|
|
12
|
+
* return (
|
|
13
|
+
* <html lang="en">
|
|
14
|
+
* <head>
|
|
15
|
+
* <HeadContent />
|
|
16
|
+
* <SoftrPwaHead />
|
|
17
|
+
* </head>
|
|
18
|
+
* <body>
|
|
19
|
+
* {children}
|
|
20
|
+
* <Scripts />
|
|
21
|
+
* </body>
|
|
22
|
+
* </html>
|
|
23
|
+
* )
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
declare function SoftrPwaHead(): react0.FunctionComponentElement<react0.FragmentProps>;
|
|
27
|
+
type PwaPlatform = "ios" | "android" | "desktop";
|
|
28
|
+
type PwaInstall = {
|
|
29
|
+
available: false;
|
|
30
|
+
} | {
|
|
31
|
+
available: true;
|
|
32
|
+
/** Which steps install the app on this device. */
|
|
33
|
+
platform: PwaPlatform;
|
|
34
|
+
/** The name the home screen will show. */
|
|
35
|
+
name: string;
|
|
36
|
+
/** The icon the home screen will show. */
|
|
37
|
+
iconUrl: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Whether to offer installing the app, and on which platform. Render an
|
|
41
|
+
* install button only while this says `available`: it is unavailable while
|
|
42
|
+
* loading, when the builder has not turned the PWA on, when Softr has switched
|
|
43
|
+
* it off, and when the app already runs installed.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* function InstallButton() {
|
|
47
|
+
* const install = usePwaInstall()
|
|
48
|
+
* if (!install.available) return null
|
|
49
|
+
* return <button onClick={() => openSteps(install.platform)}>Install {install.name}</button>
|
|
50
|
+
* }
|
|
51
|
+
*/
|
|
52
|
+
declare function usePwaInstall(): PwaInstall;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { PwaInstall, PwaPlatform, SoftrPwaHead, usePwaInstall };
|
package/dist/pwa.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { logger } from "./logger.js";
|
|
2
|
+
import { fetch } from "./fetch.js";
|
|
3
|
+
import { sessionConfig } from "./auth/config.js";
|
|
4
|
+
import { platformApiUrl } from "./auth/platform.js";
|
|
5
|
+
import { Fragment, createElement, useEffect, useState } from "react";
|
|
6
|
+
import { createMiddleware } from "@tanstack/react-start";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
//#region src/pwa.ts
|
|
10
|
+
const MANIFEST_PATH = "/manifest.webmanifest";
|
|
11
|
+
const ICON_PATH = "/apple-touch-icon.png";
|
|
12
|
+
const INSTALL_PATH = "/_softr/pwa.json";
|
|
13
|
+
const TTL_MS = 6e4;
|
|
14
|
+
const CACHE_CONTROL = "public, max-age=60";
|
|
15
|
+
const pwaSchema = z.object({
|
|
16
|
+
manifest: z.record(z.string(), z.unknown()),
|
|
17
|
+
install: z.object({
|
|
18
|
+
name: z.string(),
|
|
19
|
+
iconUrl: z.string()
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
let cached;
|
|
23
|
+
/**
|
|
24
|
+
* The app's PWA, or `null` when it is not installable right now. Never
|
|
25
|
+
* rejects: a failure reads as not installable and is not remembered, so the
|
|
26
|
+
* next request tries again.
|
|
27
|
+
*/
|
|
28
|
+
function readPwa() {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
if (cached !== void 0 && cached.expires > now) return cached.pwa;
|
|
31
|
+
const entry = {
|
|
32
|
+
expires: now + TTL_MS,
|
|
33
|
+
pwa: fetchPwa().catch((error) => {
|
|
34
|
+
logger.warn("Could not read the PWA settings:", error);
|
|
35
|
+
if (cached === entry) cached = void 0;
|
|
36
|
+
return null;
|
|
37
|
+
})
|
|
38
|
+
};
|
|
39
|
+
cached = entry;
|
|
40
|
+
return entry.pwa;
|
|
41
|
+
}
|
|
42
|
+
async function fetchPwa() {
|
|
43
|
+
const { applicationId } = sessionConfig();
|
|
44
|
+
const response = await fetch(`${await platformApiUrl()}/v1/vibe/applications/${applicationId}/pwa`, { headers: { accept: "application/json" } });
|
|
45
|
+
if (response.status === 404) return null;
|
|
46
|
+
if (!response.ok) throw new Error(`The platform answered ${response.status}`);
|
|
47
|
+
return pwaSchema.parse(await response.json());
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Serves the app's web app manifest, its home screen icon, and what an install
|
|
51
|
+
* button needs to know, from the app's own origin. Every other request passes
|
|
52
|
+
* through untouched.
|
|
53
|
+
*
|
|
54
|
+
* Answers 404, never an error page, when the app is not installable: a browser
|
|
55
|
+
* silently drops a manifest it cannot parse, so a 500 there would be invisible.
|
|
56
|
+
*/
|
|
57
|
+
function pwaMiddleware() {
|
|
58
|
+
return createMiddleware({ type: "request" }).server(async ({ next, pathname }) => {
|
|
59
|
+
if (pathname !== MANIFEST_PATH && pathname !== ICON_PATH && pathname !== INSTALL_PATH) return next();
|
|
60
|
+
const pwa = await readPwa();
|
|
61
|
+
if (pwa === null) return new Response(null, { status: 404 });
|
|
62
|
+
if (pathname === MANIFEST_PATH) return new Response(JSON.stringify(pwa.manifest), { headers: {
|
|
63
|
+
"content-type": "application/manifest+json",
|
|
64
|
+
"cache-control": CACHE_CONTROL
|
|
65
|
+
} });
|
|
66
|
+
if (pathname === INSTALL_PATH) return Response.json(pwa.install, { headers: { "cache-control": CACHE_CONTROL } });
|
|
67
|
+
try {
|
|
68
|
+
const iconResponse = await fetch(pwa.install.iconUrl);
|
|
69
|
+
if (!iconResponse.ok) return new Response(null, { status: 404 });
|
|
70
|
+
return new Response(iconResponse.body, { headers: {
|
|
71
|
+
"content-type": "image/png",
|
|
72
|
+
"cache-control": CACHE_CONTROL
|
|
73
|
+
} });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
logger.warn("Could not fetch the PWA icon:", error);
|
|
76
|
+
return new Response(null, { status: 404 });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The links that make the app installable: its manifest and its home screen
|
|
82
|
+
* icon. Render it once, in `<head>` in the root route's `RootDocument`.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* function RootDocument({ children }: { children: React.ReactNode }) {
|
|
86
|
+
* return (
|
|
87
|
+
* <html lang="en">
|
|
88
|
+
* <head>
|
|
89
|
+
* <HeadContent />
|
|
90
|
+
* <SoftrPwaHead />
|
|
91
|
+
* </head>
|
|
92
|
+
* <body>
|
|
93
|
+
* {children}
|
|
94
|
+
* <Scripts />
|
|
95
|
+
* </body>
|
|
96
|
+
* </html>
|
|
97
|
+
* )
|
|
98
|
+
* }
|
|
99
|
+
*/
|
|
100
|
+
function SoftrPwaHead() {
|
|
101
|
+
return createElement(Fragment, null, createElement("link", {
|
|
102
|
+
rel: "manifest",
|
|
103
|
+
href: MANIFEST_PATH
|
|
104
|
+
}), createElement("link", {
|
|
105
|
+
rel: "apple-touch-icon",
|
|
106
|
+
href: ICON_PATH
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
const installSchema = pwaSchema.shape.install;
|
|
110
|
+
/**
|
|
111
|
+
* Whether to offer installing the app, and on which platform. Render an
|
|
112
|
+
* install button only while this says `available`: it is unavailable while
|
|
113
|
+
* loading, when the builder has not turned the PWA on, when Softr has switched
|
|
114
|
+
* it off, and when the app already runs installed.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* function InstallButton() {
|
|
118
|
+
* const install = usePwaInstall()
|
|
119
|
+
* if (!install.available) return null
|
|
120
|
+
* return <button onClick={() => openSteps(install.platform)}>Install {install.name}</button>
|
|
121
|
+
* }
|
|
122
|
+
*/
|
|
123
|
+
function usePwaInstall() {
|
|
124
|
+
const [install, setInstall] = useState({ available: false });
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (window.matchMedia("(display-mode: standalone)").matches || "standalone" in navigator && navigator.standalone === true) return;
|
|
127
|
+
let cancelled = false;
|
|
128
|
+
fetch(INSTALL_PATH, { headers: { accept: "application/json" } }).then(async (response) => {
|
|
129
|
+
if (!response.ok) return;
|
|
130
|
+
const parsed = installSchema.safeParse(await response.json());
|
|
131
|
+
if (!parsed.success || cancelled) return;
|
|
132
|
+
setInstall({
|
|
133
|
+
available: true,
|
|
134
|
+
platform: detectPlatform(),
|
|
135
|
+
...parsed.data
|
|
136
|
+
});
|
|
137
|
+
}).catch(() => {});
|
|
138
|
+
return () => {
|
|
139
|
+
cancelled = true;
|
|
140
|
+
};
|
|
141
|
+
}, []);
|
|
142
|
+
return install;
|
|
143
|
+
}
|
|
144
|
+
function detectPlatform() {
|
|
145
|
+
const userAgent = navigator.userAgent;
|
|
146
|
+
if (/iPhone|iPad|iPod/.test(userAgent) || /Macintosh/.test(userAgent) && navigator.maxTouchPoints > 1) return "ios";
|
|
147
|
+
if (/Android/.test(userAgent)) return "android";
|
|
148
|
+
return "desktop";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
//#endregion
|
|
152
|
+
export { SoftrPwaHead, pwaMiddleware, usePwaInstall };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { upload } from "./core/file-upload.js";
|
|
2
|
-
import { comments } from "./comments/comments.js";
|
|
3
2
|
import { auth } from "./auth/server.js";
|
|
4
3
|
import { ds } from "./datasource/datasource.js";
|
|
5
4
|
import { workflow } from "./core/workflow.js";
|
|
6
5
|
import { integration } from "./core/integration.js";
|
|
7
6
|
import "@tanstack/react-start/server-only";
|
|
8
|
-
export { auth,
|
|
7
|
+
export { auth, ds, integration, upload, workflow };
|
package/dist/server.js
CHANGED
|
@@ -3,7 +3,6 @@ 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 { comments } from "./comments/comments.js";
|
|
7
6
|
import "@tanstack/react-start/server-only";
|
|
8
7
|
|
|
9
|
-
export { auth,
|
|
8
|
+
export { auth, ds, integration, upload, workflow };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softr/sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.55",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
|
|
@@ -17,8 +17,6 @@
|
|
|
17
17
|
"typecheck": "tsc --noEmit"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"marked": "^18.0.14",
|
|
21
|
-
"xss": "^1.0.15",
|
|
22
20
|
"zod": "^4.4.3"
|
|
23
21
|
},
|
|
24
22
|
"devDependencies": {
|
|
@@ -11,7 +11,7 @@ description: >-
|
|
|
11
11
|
metadata:
|
|
12
12
|
type: core
|
|
13
13
|
library: softr-sdk
|
|
14
|
-
library_version: 0.0.
|
|
14
|
+
library_version: 0.0.55
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
# Softr SDK core
|
|
@@ -29,9 +29,9 @@ Each sub-skill stands alone for its task and repeats the boundary above, so most
|
|
|
29
29
|
|
|
30
30
|
| Import | Runs | Holds |
|
|
31
31
|
| --- | --- | --- |
|
|
32
|
-
| `@softr/sdk` | Both | `defineQuery`, `defineMutation`, `
|
|
33
|
-
| `@softr/sdk/server` | Server only | `ds`, `auth`, `workflow`, `integration
|
|
34
|
-
| `@softr/sdk/client` | Browser only |
|
|
32
|
+
| `@softr/sdk` | Both | `defineQuery`, `defineMutation`, `defineVisibility`, `guardRoute`, `assertRouteAccess`, `isAuthorized`, `checkAccess`, `Audience`, `useCurrentUser`, `useUserTimezone`, `SoftrPwaHead`, `usePwaInstall`, `getSoftrContext`, `initialSoftrContext`, `softrAuthMiddleware`, the error predicates, and every type |
|
|
33
|
+
| `@softr/sdk/server` | Server only | `ds`, `auth`, `workflow`, `integration` |
|
|
34
|
+
| `@softr/sdk/client` | Browser only | Nothing yet, reserved for browser-only utilities |
|
|
35
35
|
| `@softr/sdk/vite` | Build | `softr()`, already in `vite.config.ts` |
|
|
36
36
|
|
|
37
37
|
## Sub-Skills
|
|
@@ -48,7 +48,7 @@ Each sub-skill stands alone for its task and repeats the boundary above, so most
|
|
|
48
48
|
| Sign a visitor in, out, or up | [auth/SKILL.md](./auth/SKILL.md) |
|
|
49
49
|
| Call a connected third-party API (Slack, Stripe, Gmail, …) | [integrations/SKILL.md](./integrations/SKILL.md) |
|
|
50
50
|
| Open the app to AI assistants over MCP | [mcp/SKILL.md](./mcp/SKILL.md) |
|
|
51
|
-
|
|
|
51
|
+
| Let visitors install the app on their home screen | [pwa/SKILL.md](./pwa/SKILL.md) |
|
|
52
52
|
|
|
53
53
|
## Quick Decision Tree
|
|
54
54
|
|
|
@@ -83,8 +83,8 @@ Calling a connected third-party API (Slack, Stripe, Gmail, Google Calendar, …)
|
|
|
83
83
|
Opening the app to an AI assistant over MCP?
|
|
84
84
|
load softr-sdk/mcp
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
load softr-sdk/
|
|
86
|
+
Adding an install button, or making the app installable?
|
|
87
|
+
load softr-sdk/pwa
|
|
88
88
|
|
|
89
89
|
Writing a server function, loader, or middleware in general?
|
|
90
90
|
not a Softr concern. Use the TanStack Start skills.
|
|
@@ -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.55
|
|
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.55
|
|
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.
|