@softr/sdk 0.0.69-alpha.0 → 0.0.70

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.
Files changed (38) hide show
  1. package/dist/auth/methods/utils.d.ts +1 -1
  2. package/dist/auth/platform.js +2 -9
  3. package/dist/auth/server.d.ts +0 -2
  4. package/dist/auth/server.js +0 -2
  5. package/dist/auth/session.js +1 -11
  6. package/dist/auth/user.d.ts +0 -4
  7. package/dist/auth/user.js +1 -3
  8. package/dist/codegen/datasource-docs.js +1 -1
  9. package/dist/codegen/write-datasource-docs.js +13 -6
  10. package/dist/default-error-overlay.d.ts +2 -2
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +2 -1
  13. package/dist/mcp.d.ts +2 -2
  14. package/dist/package.js +1 -1
  15. package/dist/pwa.d.ts +2 -2
  16. package/dist/visibility/page.d.ts +72 -0
  17. package/dist/visibility/page.js +79 -0
  18. package/dist/visibility/route.d.ts +12 -6
  19. package/dist/visibility/route.js +12 -8
  20. package/dist/visibility/visibility.d.ts +4 -9
  21. package/dist/visibility/visibility.js +11 -12
  22. package/package.json +1 -1
  23. package/skills/softr-sdk/SKILL.md +1 -1
  24. package/skills/softr-sdk/auth/SKILL.md +10 -29
  25. package/skills/softr-sdk/charts-and-metrics/SKILL.md +1 -1
  26. package/skills/softr-sdk/current-user/SKILL.md +1 -3
  27. package/skills/softr-sdk/file-uploads/SKILL.md +1 -1
  28. package/skills/softr-sdk/formatting/SKILL.md +1 -1
  29. package/skills/softr-sdk/integrations/SKILL.md +1 -1
  30. package/skills/softr-sdk/mcp/SKILL.md +1 -1
  31. package/skills/softr-sdk/pwa/SKILL.md +1 -1
  32. package/skills/softr-sdk/record-mutations/SKILL.md +1 -1
  33. package/skills/softr-sdk/record-queries/SKILL.md +1 -1
  34. package/skills/softr-sdk/region-visibility/SKILL.md +1 -1
  35. package/skills/softr-sdk/routes-and-visibility/SKILL.md +1 -1
  36. package/skills/softr-sdk/workflows/SKILL.md +1 -1
  37. package/dist/auth/methods/profile.d.ts +0 -26
  38. package/dist/auth/methods/profile.js +0 -52
@@ -10,6 +10,6 @@ type AuthError = {
10
10
  code: AuthErrorCode;
11
11
  message: string;
12
12
  };
13
- type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode" | "notAuthenticated" | "emailDomainNotAllowed" | "linkExpired" | "invalidName" | "fileTooLarge" | "notAnImage" | "avatarNotSupported";
13
+ type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode" | "notAuthenticated" | "emailDomainNotAllowed" | "linkExpired";
14
14
  //#endregion
15
15
  export { AuthError, Result };
@@ -1,4 +1,4 @@
1
- import { assert, panic } from "../assertions.js";
1
+ import { panic } from "../assertions.js";
2
2
  import { fetch } from "../fetch.js";
3
3
  import { permissionsSchema } from "./permissions.js";
4
4
  import { sessionConfig } from "./config.js";
@@ -267,11 +267,6 @@ function makePlatform(applicationId) {
267
267
  token,
268
268
  json: { code }
269
269
  }, z.null()),
270
- updateProfile: (token, form) => request(`/v1/vibe/applications/${applicationId}/users/me/profile`, {
271
- method: "PATCH",
272
- token,
273
- form
274
- }, userSchema),
275
270
  signOut: (token) => request(`/v1/applications/${applicationId}/users/sign-out`, {
276
271
  method: "POST",
277
272
  token
@@ -280,7 +275,6 @@ function makePlatform(applicationId) {
280
275
  }
281
276
  const errorSchema = z.object({ message: z.string() });
282
277
  async function request(path, init, schema, parse = (response$1) => response$1.json()) {
283
- assert(init.json === void 0 || init.form === void 0, "A platform request carries a JSON body or a form, not both");
284
278
  const response = await fetch(`${await platformApiUrl()}${path}`, {
285
279
  method: init.method,
286
280
  headers: {
@@ -288,8 +282,7 @@ async function request(path, init, schema, parse = (response$1) => response$1.js
288
282
  ...init.json === void 0 ? {} : { "content-type": "application/json" },
289
283
  ...init.token ? { authorization: `Bearer ${init.token}` } : {}
290
284
  },
291
- ...init.json === void 0 ? {} : { body: JSON.stringify(init.json) },
292
- ...init.form === void 0 ? {} : { body: init.form }
285
+ ...init.json === void 0 ? {} : { body: JSON.stringify(init.json) }
293
286
  });
294
287
  if (!response.ok) {
295
288
  const error = errorSchema.safeParse(await response.json().catch(() => null));
@@ -6,7 +6,6 @@ import { checkPasswordLink, requestPasswordReset, setPasswordWithLink } from "./
6
6
  import { settings } from "./methods/settings.js";
7
7
  import { confirmSignUp, signUp } from "./methods/signup.js";
8
8
  import { signInWithGoogle } from "./methods/social.js";
9
- import { updateProfile } from "./methods/profile.js";
10
9
  import { currentUserRow } from "./current-user-row.js";
11
10
  import "@tanstack/react-start/server-only";
12
11
 
@@ -35,7 +34,6 @@ declare const auth: {
35
34
  resetAuthenticator: typeof resetAuthenticator;
36
35
  removeAuthenticator: typeof removeAuthenticator;
37
36
  settings: typeof settings;
38
- updateProfile: typeof updateProfile;
39
37
  currentUserRow: typeof currentUserRow;
40
38
  };
41
39
  //#endregion
@@ -6,7 +6,6 @@ import { signIn, signOut } from "./methods/password.js";
6
6
  import { checkPasswordLink, requestPasswordReset, setPasswordWithLink } from "./methods/passwordLink.js";
7
7
  import { confirmSignUp, signUp } from "./methods/signup.js";
8
8
  import { signInWithGoogle } from "./methods/social.js";
9
- import { updateProfile } from "./methods/profile.js";
10
9
  import { currentUserRow } from "./current-user-row.js";
11
10
  import "@tanstack/react-start/server-only";
12
11
 
@@ -34,7 +33,6 @@ const auth = {
34
33
  resetAuthenticator,
35
34
  removeAuthenticator,
36
35
  settings,
37
- updateProfile,
38
36
  currentUserRow
39
37
  };
40
38
 
@@ -118,16 +118,6 @@ function clearSession() {
118
118
  PENDING_AUTH
119
119
  ]);
120
120
  }
121
- /**
122
- * Drops the cached visitor and leaves the session signed in, so the next
123
- * request needing a visitor re-reads one from the platform.
124
- *
125
- * For a mutation to what the visitor's own record holds — their name, their
126
- * groups — which the cache would otherwise answer stale for up to five minutes.
127
- */
128
- function clearSessionData() {
129
- clearCookies([SESSION_DATA]);
130
- }
131
121
  const standInToken = new AsyncLocalStorage();
132
122
  const sessionDataSchema = z.object({
133
123
  user: userSchema.nullable(),
@@ -220,4 +210,4 @@ function openEnvelope(raw, purpose) {
220
210
  }
221
211
 
222
212
  //#endregion
223
- export { clearPendingAuth, clearSession, clearSessionData, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie, readPendingAuthToken, readSessionData, readSessionToken, withSessionToken };
213
+ export { clearPendingAuth, clearSession, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie, readPendingAuthToken, readSessionData, readSessionToken, withSessionToken };
@@ -19,10 +19,6 @@ type User = {
19
19
  email: string;
20
20
  fullName: string;
21
21
  userGroupIds: Array<SoftrUserGroupId>;
22
- /** Their photo, or `null` when they have none. Show their initials then. */
23
- avatarUrl: string | null;
24
- /** Whether `auth.updateProfile` can change their photo in this app. */
25
- avatarEditable: boolean;
26
22
  };
27
23
  //#endregion
28
24
  export { SoftrUserGroupId, SoftrUserGroups, User };
package/dist/auth/user.js CHANGED
@@ -5,9 +5,7 @@ const userSchema = z.object({
5
5
  id: z.string(),
6
6
  email: z.string(),
7
7
  fullName: z.string(),
8
- userGroupIds: z.array(z.string()),
9
- avatarUrl: z.string().nullable().default(null),
10
- avatarEditable: z.boolean().default(false)
8
+ userGroupIds: z.array(z.string())
11
9
  });
12
10
 
13
11
  //#endregion
@@ -315,4 +315,4 @@ function words(value) {
315
315
  }
316
316
 
317
317
  //#endregion
318
- export { renderDatasourceDoc, renderDatasourceIndex };
318
+ export { DOC_NOTE, renderDatasourceDoc, renderDatasourceIndex };
@@ -1,22 +1,29 @@
1
1
  import { requireDefined } from "../assertions.js";
2
- import { renderDatasourceDoc, renderDatasourceIndex } from "./datasource-docs.js";
2
+ import { DOC_NOTE, renderDatasourceDoc, renderDatasourceIndex } from "./datasource-docs.js";
3
3
  import { renderDatasourceSchema } from "./datasource-schema.js";
4
4
  import { docFileNames } from "./doc-file-names.js";
5
5
  import { writeIfChanged } from "./write-if-changed.js";
6
+ import { existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
6
7
  import path from "node:path";
7
8
 
8
9
  //#region src/codegen/write-datasource-docs.ts
9
10
  /**
10
- * Writes the index, and one doc and one schema JSON per datasource, and touches nothing else in
11
- * `docsDir`: the caller resolves it against `process.cwd()`, which can be any
12
- * directory a developer ran the bin from. `changed` is true when any file it
13
- * wrote now reads differently.
11
+ * Writes the index, and one doc and one schema JSON per datasource, and removes the doc and schema
12
+ * of a datasource no longer registered. It removes only a doc that carries the generated note: the
13
+ * caller resolves `docsDir` against `process.cwd()`, which can be any directory a developer ran the
14
+ * bin from. `changed` is true when any file it wrote now reads differently, or it removed one.
14
15
  */
15
16
  function writeDatasourceDocs(datasources, docsDir) {
16
17
  const context = {
17
18
  datasources: new Map(datasources.map((datasource) => [datasource.id, datasource])),
18
19
  fileNames: docFileNames(datasources)
19
20
  };
21
+ const current = new Set(context.fileNames.values());
22
+ const stale = (existsSync(docsDir) ? readdirSync(docsDir, { withFileTypes: true }) : []).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).filter((name) => !current.has(name) && readFileSync(path.join(docsDir, name), "utf8").includes(DOC_NOTE));
23
+ for (const name of stale) {
24
+ rmSync(path.join(docsDir, name));
25
+ rmSync(path.join(docsDir, name.replace(/\.md$/, ".json")), { force: true });
26
+ }
20
27
  const writes = [
21
28
  writeIfChanged(path.join(docsDir, "README.md"), renderDatasourceIndex(datasources, context)),
22
29
  ...datasources.map((datasource) => writeIfChanged(path.join(docsDir, requireDefined(context.fileNames.get(datasource.id))), renderDatasourceDoc(datasource, context))),
@@ -24,7 +31,7 @@ function writeDatasourceDocs(datasources, docsDir) {
24
31
  ];
25
32
  return {
26
33
  fileNames: context.fileNames,
27
- changed: writes.includes(true)
34
+ changed: stale.length > 0 || writes.includes(true)
28
35
  };
29
36
  }
30
37
 
@@ -1,4 +1,4 @@
1
- import * as react4 from "react";
1
+ import * as react6 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): react4.JSX.Element;
11
+ }: Props): react6.JSX.Element;
12
12
  //#endregion
13
13
  export { DefaultErrorOverlay };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ import { DateOptions, DateParts, DateSeparator, DateValue, FixedDateFormat, Form
6
6
  import { SoftrContext } from "./auth/context.js";
7
7
  import { AuthSettings } from "./auth/platform.js";
8
8
  import { checkAccess, defineVisibility, isAuthorized } from "./visibility/visibility.js";
9
+ import { Page, PageSettings, definePage, pageHead } from "./visibility/page.js";
9
10
  import { assertRouteAccess, guardRoute, isAccessError, isAlreadyAuthenticatedError, isForbiddenError, isUnauthenticatedError } from "./visibility/route.js";
10
11
  import { toReturnPath } from "./auth/returnPath.js";
11
12
  import { AuthOptions, AuthSettingsProvider, getAuthSettings, getSoftrContext, initialSoftrContext, softrAuthMiddleware, useAuthSettings, useCurrentUser } from "./auth/isomorphic.js";
@@ -16,4 +17,4 @@ import { useUserTimezone } from "./timezone.js";
16
17
  import { PwaInstall, PwaPlatform, SoftrPwaHead, usePwaInstall } from "./pwa.js";
17
18
  import { DefaultErrorOverlay } from "./default-error-overlay.js";
18
19
  import { Audience } from "./visibility/audience.js";
19
- export { type ActionPermissions, Audience, type AuthOptions, type AuthSettings, AuthSettingsProvider, type DateOptions, type DateParts, type DateSeparator, type DateValue, DefaultErrorOverlay, type FieldShape, type FileAttachment, type FileItem, type FixedDateFormat, FormattedDate, type Formatting, FormattingScope, type PwaInstall, type PwaPlatform, type SoftrContext, type SoftrDatasources, type SoftrFieldRegistry, SoftrPwaHead, type SoftrUserGroupId, type SoftrUserGroups, type User, type WeekDay, assertRouteAccess, checkAccess, defineIntegration, defineMutation, defineQuery, defineVisibility, defineWorkflow, fieldShape, formatDate, formatDateTime, formatDuration, formatField, formatNumber, formatValue, getAuthSettings, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, registerSoftrFields, softrAuthMiddleware, softrMcpMiddleware, toReturnPath, useAuthSettings, useCurrentUser, useFileUpload, useFormatting, useOverrides, usePwaInstall, useUserTimezone };
20
+ export { type ActionPermissions, Audience, type AuthOptions, type AuthSettings, AuthSettingsProvider, type DateOptions, type DateParts, type DateSeparator, type DateValue, DefaultErrorOverlay, type FieldShape, type FileAttachment, type FileItem, type FixedDateFormat, FormattedDate, type Formatting, FormattingScope, type Page, type PageSettings, type PwaInstall, type PwaPlatform, type SoftrContext, type SoftrDatasources, type SoftrFieldRegistry, SoftrPwaHead, type SoftrUserGroupId, type SoftrUserGroups, type User, type WeekDay, assertRouteAccess, checkAccess, 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
@@ -2,6 +2,7 @@ import { useFileUpload } from "./core/use-file-upload.js";
2
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 { definePage, pageHead } from "./visibility/page.js";
5
6
  import { toReturnPath } from "./auth/returnPath.js";
6
7
  import { FormattedDate, FormattingScope, formatDate, formatDateTime, formatNumber, useFormatting } from "./formatting.js";
7
8
  import { SoftrPwaHead, usePwaInstall } from "./pwa.js";
@@ -13,4 +14,4 @@ import { useUserTimezone } from "./timezone.js";
13
14
  import { DefaultErrorOverlay } from "./default-error-overlay.js";
14
15
  import { Audience } from "./visibility/audience.js";
15
16
 
16
- export { Audience, AuthSettingsProvider, DefaultErrorOverlay, FormattedDate, FormattingScope, SoftrPwaHead, assertRouteAccess, checkAccess, defineIntegration, defineMutation, defineQuery, defineVisibility, defineWorkflow, fieldShape, formatDate, formatDateTime, formatDuration, formatField, formatNumber, formatValue, getAuthSettings, getSoftrContext, guardRoute, initialSoftrContext, isAccessError, isAlreadyAuthenticatedError, isAuthorized, isForbiddenError, isUnauthenticatedError, registerSoftrFields, softrAuthMiddleware, softrMcpMiddleware, toReturnPath, useAuthSettings, useCurrentUser, useFileUpload, useFormatting, useOverrides, usePwaInstall, useUserTimezone };
17
+ export { Audience, AuthSettingsProvider, DefaultErrorOverlay, FormattedDate, FormattingScope, SoftrPwaHead, assertRouteAccess, checkAccess, 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 _tanstack_react_start0 from "@tanstack/react-start";
2
+ import * as _tanstack_react_start4 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): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, undefined>;
34
+ declare function softrMcpMiddleware(options?: SoftrMcpOptions): _tanstack_react_start4.RequestMiddlewareAfterServer<{}, undefined, undefined>;
35
35
  //#endregion
36
36
  export { softrMcpMiddleware };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.0.69-alpha.0";
2
+ var version = "0.0.70";
3
3
 
4
4
  //#endregion
5
5
  export { version };
package/dist/pwa.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as react5 from "react";
1
+ import * as react4 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(): react5.FunctionComponentElement<react5.FragmentProps>;
26
+ declare function SoftrPwaHead(): react4.FunctionComponentElement<react4.FragmentProps>;
27
27
  type PwaPlatform = "ios" | "android" | "desktop";
28
28
  type PwaInstall = {
29
29
  available: false;
@@ -0,0 +1,72 @@
1
+ import { VisibilityRule } from "./visibility.js";
2
+ import { AnyRouteMatch } from "@tanstack/react-router";
3
+
4
+ //#region src/visibility/page.d.ts
5
+ /** The part of TanStack's `head` context a page setting can read. */
6
+ type HeadContext = {
7
+ match: {
8
+ id: string;
9
+ };
10
+ matches: ReadonlyArray<{
11
+ id: string;
12
+ }>;
13
+ loaderData?: AnyRouteMatch["loaderData"];
14
+ };
15
+ type HeadSetting<T> = T | ((ctx: HeadContext) => T | undefined);
16
+ type PageSettings = {
17
+ visibility?: VisibilityRule;
18
+ title?: HeadSetting<string>;
19
+ description?: HeadSetting<string>;
20
+ searchEngineIndexing?: HeadSetting<boolean>;
21
+ canonicalUrl?: HeadSetting<string>;
22
+ socialTitle?: HeadSetting<string>;
23
+ socialDescription?: HeadSetting<string>;
24
+ socialImage?: HeadSetting<string>;
25
+ };
26
+ declare const pageBrand: unique symbol;
27
+ /** The settings {@link definePage} has produced. */
28
+ type Page<S extends PageSettings = PageSettings> = S & {
29
+ readonly [pageBrand]: "page";
30
+ };
31
+ /**
32
+ * Declares a route's settings: who may open it, its title and its SEO tags.
33
+ *
34
+ * One call per route file, at module scope, with an object literal. Studio
35
+ * reads and rewrites each setting written out as a literal; a computed one is
36
+ * changed in the code. A head setting that depends on the route's data is a
37
+ * function of its head context. The route's `beforeLoad` and `head` apply it.
38
+ *
39
+ * @example
40
+ * const pageSettings = definePage({
41
+ * visibility: { userGroupTypes: ["LOGGED_IN_USERS"] },
42
+ * title: ({ loaderData }) => loaderData?.name,
43
+ * })
44
+ *
45
+ * export const Route = createFileRoute("/_app/projects/$projectId")({
46
+ * beforeLoad: guardRoute(pageSettings),
47
+ * loader: ({ params }) => loadProject({ data: params.projectId }),
48
+ * head: pageHead(pageSettings),
49
+ * component: Project,
50
+ * })
51
+ */
52
+ declare function definePage<const S extends PageSettings>(settings: S & { [K in Exclude<keyof S, keyof PageSettings>]: never }): Page<S>;
53
+ type Meta = NonNullable<AnyRouteMatch["meta"]>;
54
+ type Links = NonNullable<AnyRouteMatch["links"]>;
55
+ /**
56
+ * The router keeps the deepest route's tag for each title, name and property,
57
+ * so a layout's settings reach every page under it that does not state its own.
58
+ * A canonical URL names one page, so only the page the visitor is on renders
59
+ * its own.
60
+ *
61
+ * Returned rather than called with the route's context: a `head` written as
62
+ * `(ctx) => …` before `loader` stops TypeScript inferring the loader's types.
63
+ *
64
+ * @example
65
+ * head: pageHead(pageSettings)
66
+ */
67
+ declare function pageHead(page: Page): (ctx: HeadContext) => {
68
+ meta: Meta;
69
+ links: Links;
70
+ };
71
+ //#endregion
72
+ export { Page, PageSettings, definePage, pageHead };
@@ -0,0 +1,79 @@
1
+ //#region src/visibility/page.ts
2
+ /**
3
+ * Declares a route's settings: who may open it, its title and its SEO tags.
4
+ *
5
+ * One call per route file, at module scope, with an object literal. Studio
6
+ * reads and rewrites each setting written out as a literal; a computed one is
7
+ * changed in the code. A head setting that depends on the route's data is a
8
+ * function of its head context. The route's `beforeLoad` and `head` apply it.
9
+ *
10
+ * @example
11
+ * const pageSettings = definePage({
12
+ * visibility: { userGroupTypes: ["LOGGED_IN_USERS"] },
13
+ * title: ({ loaderData }) => loaderData?.name,
14
+ * })
15
+ *
16
+ * export const Route = createFileRoute("/_app/projects/$projectId")({
17
+ * beforeLoad: guardRoute(pageSettings),
18
+ * loader: ({ params }) => loadProject({ data: params.projectId }),
19
+ * head: pageHead(pageSettings),
20
+ * component: Project,
21
+ * })
22
+ */
23
+ function definePage(settings) {
24
+ return settings;
25
+ }
26
+ /**
27
+ * The router keeps the deepest route's tag for each title, name and property,
28
+ * so a layout's settings reach every page under it that does not state its own.
29
+ * A canonical URL names one page, so only the page the visitor is on renders
30
+ * its own.
31
+ *
32
+ * Returned rather than called with the route's context: a `head` written as
33
+ * `(ctx) => …` before `loader` stops TypeScript inferring the loader's types.
34
+ *
35
+ * @example
36
+ * head: pageHead(pageSettings)
37
+ */
38
+ function pageHead(page) {
39
+ return (ctx) => {
40
+ const resolve = (setting) => typeof setting === "function" ? setting(ctx) : setting;
41
+ const tag = (setting, make) => {
42
+ const value = resolve(setting);
43
+ return typeof value === "string" && value !== "" ? [make(value)] : [];
44
+ };
45
+ const indexing = resolve(page.searchEngineIndexing);
46
+ return {
47
+ meta: [
48
+ ...tag(page.title, (title) => ({ title })),
49
+ ...tag(page.description, (content) => ({
50
+ name: "description",
51
+ content
52
+ })),
53
+ ...tag(page.socialTitle, (content) => ({
54
+ property: "og:title",
55
+ content
56
+ })),
57
+ ...tag(page.socialDescription, (content) => ({
58
+ property: "og:description",
59
+ content
60
+ })),
61
+ ...tag(page.socialImage, (content) => ({
62
+ property: "og:image",
63
+ content
64
+ })),
65
+ ...typeof indexing === "boolean" ? [{
66
+ name: "robots",
67
+ content: indexing ? "index" : "noindex"
68
+ }] : []
69
+ ],
70
+ links: ctx.matches.at(-1)?.id === ctx.match.id ? tag(page.canonicalUrl, (href) => ({
71
+ rel: "canonical",
72
+ href
73
+ })) : []
74
+ };
75
+ };
76
+ }
77
+
78
+ //#endregion
79
+ export { definePage, pageHead };
@@ -1,4 +1,5 @@
1
- import { AccessContext, AccessReason, Narrowed, Visibility, VisibilityRule } from "./visibility.js";
1
+ import { AccessContext, AccessReason, Narrowed, VisibilityRule } from "./visibility.js";
2
+ import { Page, PageSettings } from "./page.js";
2
3
 
3
4
  //#region src/visibility/route.d.ts
4
5
 
@@ -42,8 +43,11 @@ declare function isUnauthenticatedError(error: unknown): error is AccessError;
42
43
  declare function isForbiddenError(error: unknown): error is AccessError;
43
44
  /** True for an access error thrown because a signed-out-only route saw a user. */
44
45
  declare function isAlreadyAuthenticatedError(error: unknown): error is AccessError;
46
+ type GuardedPage = Page<PageSettings & {
47
+ visibility: VisibilityRule;
48
+ }>;
45
49
  /**
46
- * A `beforeLoad` handler that denies a visitor who fails the rule.
50
+ * A `beforeLoad` handler that denies a visitor the page's rule does not admit.
47
51
  *
48
52
  * Each reason falls back to the app's configured behaviour. Pass `overrides` to
49
53
  * change what any one reason does. A redirected signed-out visitor carries the
@@ -52,9 +56,9 @@ declare function isAlreadyAuthenticatedError(error: unknown): error is AccessErr
52
56
  * Throws a router redirect, so it belongs in `beforeLoad` and nowhere else.
53
57
  *
54
58
  * @example
55
- * beforeLoad: guardRoute(visibility, { unauthenticated: "/sign-in" })
59
+ * beforeLoad: guardRoute(pageSettings, { unauthenticated: "/sign-in" })
56
60
  */
57
- declare function guardRoute(visibility: Visibility, overrides?: GuardOverrides): (opts: {
61
+ declare function guardRoute(page: GuardedPage, overrides?: GuardOverrides): (opts: {
58
62
  context: AccessContext;
59
63
  location: {
60
64
  href: string;
@@ -66,7 +70,7 @@ declare function guardRoute(visibility: Visibility, overrides?: GuardOverrides):
66
70
  *
67
71
  * @example
68
72
  * beforeLoad: (opts) => {
69
- * assertRouteAccess(visibility, opts)
73
+ * assertRouteAccess(pageSettings, opts)
70
74
  * if (opts.params.projectId === "demo") throw redirect({ to: "/projects" })
71
75
  * }
72
76
  */
@@ -75,7 +79,9 @@ declare function assertRouteAccess<R extends VisibilityRule, O extends {
75
79
  location: {
76
80
  href: string;
77
81
  };
78
- }>(visibility: Visibility<R>, opts: O): asserts opts is O & {
82
+ }>(page: Page<PageSettings & {
83
+ visibility: R;
84
+ }>, opts: O): asserts opts is O & {
79
85
  context: Narrowed<R>;
80
86
  };
81
87
  //#endregion
@@ -1,4 +1,5 @@
1
- import { ACCESS_REASONS, checkAccess } from "./visibility.js";
1
+ import { panic } from "../assertions.js";
2
+ import { ACCESS_REASONS, checkRule } from "./visibility.js";
2
3
  import { softrConfig } from "../softr-config.js";
3
4
  import { redirect } from "@tanstack/react-router";
4
5
 
@@ -52,8 +53,11 @@ function thrownFor(reason, overrides, location) {
52
53
  reason
53
54
  };
54
55
  }
56
+ function ruleOf(page) {
57
+ return page.visibility ?? panic("the page states no visibility rule to guard it with");
58
+ }
55
59
  /**
56
- * A `beforeLoad` handler that denies a visitor who fails the rule.
60
+ * A `beforeLoad` handler that denies a visitor the page's rule does not admit.
57
61
  *
58
62
  * Each reason falls back to the app's configured behaviour. Pass `overrides` to
59
63
  * change what any one reason does. A redirected signed-out visitor carries the
@@ -62,11 +66,11 @@ function thrownFor(reason, overrides, location) {
62
66
  * Throws a router redirect, so it belongs in `beforeLoad` and nowhere else.
63
67
  *
64
68
  * @example
65
- * beforeLoad: guardRoute(visibility, { unauthenticated: "/sign-in" })
69
+ * beforeLoad: guardRoute(pageSettings, { unauthenticated: "/sign-in" })
66
70
  */
67
- function guardRoute(visibility, overrides) {
71
+ function guardRoute(page, overrides) {
68
72
  return ({ context, location }) => {
69
- const result = checkAccess(visibility, context);
73
+ const result = checkRule(ruleOf(page), context);
70
74
  if (result.ok) return;
71
75
  throw thrownFor(result.reason, overrides, location);
72
76
  };
@@ -77,12 +81,12 @@ function guardRoute(visibility, overrides) {
77
81
  *
78
82
  * @example
79
83
  * beforeLoad: (opts) => {
80
- * assertRouteAccess(visibility, opts)
84
+ * assertRouteAccess(pageSettings, opts)
81
85
  * if (opts.params.projectId === "demo") throw redirect({ to: "/projects" })
82
86
  * }
83
87
  */
84
- function assertRouteAccess(visibility, opts) {
85
- const result = checkAccess(visibility, opts.context);
88
+ function assertRouteAccess(page, opts) {
89
+ const result = checkRule(ruleOf(page), opts.context);
86
90
  if (result.ok) return;
87
91
  throw thrownFor(result.reason, void 0, opts.location);
88
92
  }
@@ -48,18 +48,13 @@ type Narrowed<R extends VisibilityRule> = R["userGroupTypes"] extends ReadonlyAr
48
48
  user: null;
49
49
  } : AccessContext : AccessContext;
50
50
  /**
51
- * Declares who may see a route or a region of the page.
52
- *
53
- * At module scope in a route file, with a literal rule: the build reads these
54
- * declarations out of the source to publish the app's route visibility.
51
+ * Declares who may see a region of the page. A route's own rule is the
52
+ * `visibility` of its `definePage`.
55
53
  *
56
54
  * @example
57
- * const visibility = defineVisibility({ userGroupTypes: ["LOGGED_IN_USERS"] })
55
+ * const staffOnly = defineVisibility({ userGroupTypes: ["LOGGED_IN_USERS"], customGroups: ["…"] })
58
56
  *
59
- * export const Route = createFileRoute("/about")({
60
- * beforeLoad: guardRoute(visibility),
61
- * component: About,
62
- * })
57
+ * <Audience visibility={staffOnly} fallback={null}>…</Audience>
63
58
  */
64
59
  declare function defineVisibility<const R extends VisibilityRule>(rule: R): Visibility<R>;
65
60
  /**
@@ -13,8 +13,12 @@ const ACCESS_REASONS = [
13
13
  ];
14
14
  /** The decision and, when a visitor is denied, the reason. Throws nothing. */
15
15
  function checkAccess(visibility, context) {
16
+ return checkRule(visibility, context);
17
+ }
18
+ /** {@link checkAccess} for a page's rule, which {@link defineVisibility} has not produced. */
19
+ function checkRule(rule, context) {
16
20
  const { user } = context;
17
- const audiences = visibility.userGroupTypes;
21
+ const audiences = rule.userGroupTypes;
18
22
  if (user === null) return audiences.includes("NON_LOGGED_IN_USERS") ? { ok: true } : {
19
23
  ok: false,
20
24
  reason: "unauthenticated"
@@ -23,7 +27,7 @@ function checkAccess(visibility, context) {
23
27
  ok: false,
24
28
  reason: "alreadyAuthenticated"
25
29
  };
26
- const required = visibility.customGroups ?? [];
30
+ const required = rule.customGroups ?? [];
27
31
  if (required.length === 0) return { ok: true };
28
32
  return required.some((id) => user.userGroupIds.includes(id)) ? { ok: true } : {
29
33
  ok: false,
@@ -31,18 +35,13 @@ function checkAccess(visibility, context) {
31
35
  };
32
36
  }
33
37
  /**
34
- * Declares who may see a route or a region of the page.
35
- *
36
- * At module scope in a route file, with a literal rule: the build reads these
37
- * declarations out of the source to publish the app's route visibility.
38
+ * Declares who may see a region of the page. A route's own rule is the
39
+ * `visibility` of its `definePage`.
38
40
  *
39
41
  * @example
40
- * const visibility = defineVisibility({ userGroupTypes: ["LOGGED_IN_USERS"] })
42
+ * const staffOnly = defineVisibility({ userGroupTypes: ["LOGGED_IN_USERS"], customGroups: ["…"] })
41
43
  *
42
- * export const Route = createFileRoute("/about")({
43
- * beforeLoad: guardRoute(visibility),
44
- * component: About,
45
- * })
44
+ * <Audience visibility={staffOnly} fallback={null}>…</Audience>
46
45
  */
47
46
  function defineVisibility(rule) {
48
47
  return rule;
@@ -56,4 +55,4 @@ function isAuthorized(visibility, context) {
56
55
  }
57
56
 
58
57
  //#endregion
59
- export { ACCESS_REASONS, checkAccess, defineVisibility, isAuthorized };
58
+ export { ACCESS_REASONS, checkAccess, checkRule, defineVisibility, isAuthorized };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softr/sdk",
3
- "version": "0.0.69-alpha.0",
3
+ "version": "0.0.70",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
@@ -12,7 +12,7 @@ description: >-
12
12
  metadata:
13
13
  type: core
14
14
  library: softr-sdk
15
- library_version: 0.0.69-alpha.0
15
+ library_version: 0.0.69
16
16
  ---
17
17
 
18
18
  # Softr SDK core
@@ -7,18 +7,19 @@ description: >-
7
7
  sign-in (verifyAuthenticator, startAuthenticatorSetup,
8
8
  confirmAuthenticatorSetup, skipAuthenticatorSetup) and the ones that change an
9
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.
10
+ resetAuthenticator, removeAuthenticator), the emailed password links
11
+ (requestPasswordReset, checkPasswordLink, setPasswordWithLink), and
12
+ getAuthSettings, AuthSettingsProvider and useAuthSettings for which sign-in
13
+ and sign-up methods the app has enabled. Covers the Result shape they answer
14
+ with and its codes invalidCredentials, tooManyAttempts, emailTaken,
15
+ invalidDetails, invalidCode, notAuthenticated, emailDomainNotAllowed and
16
+ linkExpired, the two-step sign-up, why a successful sign-in does not always
17
+ mean the visitor is signed in, and toReturnPath for where to land after
18
+ signing in.
18
19
  metadata:
19
20
  type: sub-skill
20
21
  library: softr-sdk
21
- library_version: 0.0.69-alpha.0
22
+ library_version: 0.0.69
22
23
  ---
23
24
 
24
25
  **Boundary.** `auth` lives on `@softr/sdk/server` and runs only inside a `createServerFn` handler — importing it into a component fails the build.
@@ -81,12 +82,6 @@ Changing an authenticator from a settings page:
81
82
  | `auth.resetAuthenticator({ code })` | Unpairs the app, so a new one can be enrolled |
82
83
  | `auth.removeAuthenticator({ code })` | Turns the authenticator off |
83
84
 
84
- Changing the visitor's own profile:
85
-
86
- | Call | Does |
87
- | --- | --- |
88
- | `auth.updateProfile({ fullName?, avatar? })` | Changes their name, their photo, or both. `avatar: null` removes the photo |
89
-
90
85
  ## Setup
91
86
 
92
87
  `auth` is server-only, so every call sits inside a `createServerFn` handler:
@@ -456,20 +451,6 @@ All three need a signed-in visitor and answer `notAuthenticated` when there is n
456
451
 
457
452
  After a reset, enrol the new app with `startAuthenticatorSetup` and `confirmAuthenticatorSetup`, which is also where the new backup codes come from. After a removal, an app that requires an authenticator asks the visitor to set one up again on their next sign-in.
458
453
 
459
- ### Changing the visitor's name and photo
460
-
461
- ```ts
462
- const result = await auth.updateProfile({ fullName: 'Ann Lee' })
463
- const result = await auth.updateProfile({ avatar: file }) // a File from an <input type="file">
464
- const result = await auth.updateProfile({ avatar: null }) // removes the photo
465
- ```
466
-
467
- It answers with the updated `User`. Pass the file through a server function that takes `FormData`. The photo must be a PNG, JPEG or GIF of at most 5 MB, or it answers `notAnImage` or `fileTooLarge`. A blank or unacceptable name answers `invalidName`.
468
-
469
- Only some apps can store a photo. Show the photo controls only when `user.avatarEditable` is true; elsewhere a photo answers `avatarNotSupported`.
470
-
471
- The page keeps showing the old name and photo until its route context loads again, so call `router.invalidate()` after a success.
472
-
473
454
  ## References
474
455
 
475
456
  - [current-user/SKILL.md](../current-user/SKILL.md): reading the visitor these calls establish
@@ -12,7 +12,7 @@ description: >-
12
12
  metadata:
13
13
  type: sub-skill
14
14
  library: softr-sdk
15
- library_version: 0.0.69-alpha.0
15
+ library_version: 0.0.69
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.
@@ -9,7 +9,7 @@ description: >-
9
9
  metadata:
10
10
  type: sub-skill
11
11
  library: softr-sdk
12
- library_version: 0.0.69-alpha.0
12
+ library_version: 0.0.69
13
13
  ---
14
14
 
15
15
  # The current user
@@ -31,8 +31,6 @@ type User = {
31
31
  email: string
32
32
  fullName: string
33
33
  userGroupIds: Array<SoftrUserGroupId> // the app's own group ids
34
- avatarUrl: string | null // their photo; show their initials when null
35
- avatarEditable: boolean // whether auth.updateProfile can change the photo
36
34
  }
37
35
  ```
38
36
 
@@ -10,7 +10,7 @@ description: >-
10
10
  metadata:
11
11
  type: sub-skill
12
12
  library: softr-sdk
13
- library_version: 0.0.69-alpha.0
13
+ library_version: 0.0.69
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.69-alpha.0
13
+ library_version: 0.0.69
14
14
  ---
15
15
 
16
16
  # Dates, times and numbers
@@ -10,7 +10,7 @@ description: >-
10
10
  metadata:
11
11
  type: sub-skill
12
12
  library: softr-sdk
13
- library_version: 0.0.69-alpha.0
13
+ library_version: 0.0.69
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.69-alpha.0
12
+ library_version: 0.0.69
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.
@@ -9,7 +9,7 @@ description: >-
9
9
  metadata:
10
10
  type: sub-skill
11
11
  library: softr-sdk
12
- library_version: 0.0.69-alpha.0
12
+ library_version: 0.0.69
13
13
  ---
14
14
 
15
15
  # Installing the app
@@ -11,7 +11,7 @@ description: >-
11
11
  metadata:
12
12
  type: sub-skill
13
13
  library: softr-sdk
14
- library_version: 0.0.69-alpha.0
14
+ library_version: 0.0.69
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.69-alpha.0
16
+ library_version: 0.0.69
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.
@@ -9,7 +9,7 @@ description: >-
9
9
  metadata:
10
10
  type: sub-skill
11
11
  library: softr-sdk
12
- library_version: 0.0.69-alpha.0
12
+ library_version: 0.0.69
13
13
  ---
14
14
 
15
15
  **Boundary.** `Audience` and `defineVisibility` come from `@softr/sdk` and work anywhere.
@@ -13,7 +13,7 @@ description: >-
13
13
  metadata:
14
14
  type: sub-skill
15
15
  library: softr-sdk
16
- library_version: 0.0.69-alpha.0
16
+ library_version: 0.0.69
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.69-alpha.0
12
+ library_version: 0.0.69
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.
@@ -1,26 +0,0 @@
1
- import { User } from "../user.js";
2
- import { AuthError, Result } from "./utils.js";
3
-
4
- //#region src/auth/methods/profile.d.ts
5
-
6
- /**
7
- * Changes the signed-in visitor's own name, photo, or both. A field left out
8
- * stays as it is, and `avatar: null` removes the photo.
9
- *
10
- * The photo can change only where `user.avatarEditable` is true. Elsewhere a
11
- * photo answers `avatarNotSupported`, so hide the photo controls instead.
12
- *
13
- * Answers with the visitor as they are now. The page keeps showing what it
14
- * loaded until its route context loads again, so call `router.invalidate()`
15
- * after a success.
16
- *
17
- * @example
18
- * const result = await auth.updateProfile({ fullName: 'Ann Lee' })
19
- * const result = await auth.updateProfile({ avatar: file })
20
- */
21
- declare function updateProfile(changes: {
22
- fullName?: string;
23
- avatar?: File | null;
24
- }): Promise<Result<User, AuthError>>;
25
- //#endregion
26
- export { updateProfile };
@@ -1,52 +0,0 @@
1
- import { assert, panic } from "../../assertions.js";
2
- import { sessionConfig } from "../config.js";
3
- import { clearSessionData, readSessionToken } from "../session.js";
4
- import { makePlatform } from "../platform.js";
5
- import { authError } from "./utils.js";
6
-
7
- //#region src/auth/methods/profile.ts
8
- /**
9
- * Changes the signed-in visitor's own name, photo, or both. A field left out
10
- * stays as it is, and `avatar: null` removes the photo.
11
- *
12
- * The photo can change only where `user.avatarEditable` is true. Elsewhere a
13
- * photo answers `avatarNotSupported`, so hide the photo controls instead.
14
- *
15
- * Answers with the visitor as they are now. The page keeps showing what it
16
- * loaded until its route context loads again, so call `router.invalidate()`
17
- * after a success.
18
- *
19
- * @example
20
- * const result = await auth.updateProfile({ fullName: 'Ann Lee' })
21
- * const result = await auth.updateProfile({ avatar: file })
22
- */
23
- async function updateProfile(changes) {
24
- assert(changes.fullName !== void 0 || changes.avatar !== void 0, "updateProfile needs a name or a photo to change");
25
- const token = readSessionToken();
26
- if (token === null) return authError("notAuthenticated", "Nobody is signed in.");
27
- const form = new FormData();
28
- if (changes.fullName !== void 0) form.set("fullName", changes.fullName);
29
- if (changes.avatar === null) form.set("removeAvatar", "true");
30
- if (changes.avatar) form.set("avatar", changes.avatar);
31
- const result = await makePlatform(sessionConfig().applicationId).updateProfile(token, form);
32
- if (!result.ok) {
33
- switch (result.status) {
34
- case 400: return authError("invalidName", result.message);
35
- case 401: return authError("notAuthenticated", result.message);
36
- case 409: return authError("avatarNotSupported", result.message);
37
- case 413: return authError("fileTooLarge", result.message);
38
- case 415: return authError("notAnImage", result.message);
39
- case 429: return authError("tooManyAttempts", result.message);
40
- }
41
- if (result.status >= 500) panic(`Expected the profile to update, but got ${result.status}: ` + result.message);
42
- throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID.`);
43
- }
44
- clearSessionData();
45
- return {
46
- success: true,
47
- data: result.data
48
- };
49
- }
50
-
51
- //#endregion
52
- export { updateProfile };