@softr/sdk 0.0.11 → 0.0.13

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 (47) hide show
  1. package/dist/assertions.js +68 -0
  2. package/dist/auth/config.js +27 -0
  3. package/dist/auth/context.d.ts +18 -0
  4. package/dist/auth/isomorphic.d.ts +73 -0
  5. package/dist/auth/isomorphic.js +113 -0
  6. package/dist/auth/methods/password.d.ts +22 -0
  7. package/dist/auth/methods/password.js +51 -0
  8. package/dist/auth/methods/signup.d.ts +37 -0
  9. package/dist/auth/methods/signup.js +89 -0
  10. package/dist/auth/methods/utils.d.ts +15 -0
  11. package/dist/auth/methods/utils.js +13 -0
  12. package/dist/auth/permissions.d.ts +17 -0
  13. package/dist/auth/permissions.js +13 -0
  14. package/dist/auth/platform.js +138 -0
  15. package/dist/auth/server.d.ts +17 -0
  16. package/dist/auth/server.js +17 -0
  17. package/dist/auth/session.js +167 -0
  18. package/dist/{user-D23hwTsf.d.ts → auth/user.d.ts} +1 -1
  19. package/dist/client/timezone.d.ts +4 -0
  20. package/dist/client/timezone.js +11 -0
  21. package/dist/client.d.ts +1 -4
  22. package/dist/client.js +1 -9
  23. package/dist/cookies.js +47 -0
  24. package/dist/core/datasource.d.ts +21 -0
  25. package/dist/core/datasource.js +196 -0
  26. package/dist/core/integration.d.ts +7 -0
  27. package/dist/core/integration.js +32 -0
  28. package/dist/core/query.d.ts +354 -0
  29. package/dist/core/query.js +528 -0
  30. package/dist/core/workflow.d.ts +7 -0
  31. package/dist/core/workflow.js +19 -0
  32. package/dist/index.d.ts +8 -685
  33. package/dist/index.js +4 -1019
  34. package/dist/routes/discover.d.ts +9 -0
  35. package/dist/routes/discover.js +198 -0
  36. package/dist/routes/manifest.d.ts +17 -0
  37. package/dist/routes/manifest.js +148 -0
  38. package/dist/routes/mem-fs.js +71 -0
  39. package/dist/routes/visibility.d.ts +142 -0
  40. package/dist/routes/visibility.js +125 -0
  41. package/dist/server.d.ts +5 -84
  42. package/dist/server.js +5 -194
  43. package/dist/types.d.ts +105 -0
  44. package/dist/vite.d.ts +1 -21
  45. package/dist/vite.js +2 -405
  46. package/package.json +4 -4
  47. package/dist/session-GUEI96C8.js +0 -360
@@ -0,0 +1,68 @@
1
+ //#region src/assertions.ts
2
+ function requireDefined(value, message) {
3
+ if (value === null || value === void 0) {
4
+ let error = "Unexpected null or undefined value";
5
+ if (message) error += ": " + message;
6
+ throwAtCaller(requireDefined, new Error(error));
7
+ }
8
+ return value;
9
+ }
10
+ function assert(condition, message) {
11
+ if (!condition) throwAtCaller(assert, new AssertionError(unwrapErrorMessage(message)));
12
+ }
13
+ /**
14
+ * Shorthand for throw new AssertionError("Error message")
15
+ * Useful because it is an expression instead of a statement.
16
+ * Can be assigned to any value, because it never returns.
17
+ */
18
+ function panic(message) {
19
+ throwAtCaller(panic, new AssertionError(unwrapErrorMessage(message)));
20
+ }
21
+ function todo(message) {
22
+ throwAtCaller(todo, new UnimplementedError(unwrapErrorMessage(message)));
23
+ }
24
+ /**
25
+ * A soft assertion that throws an error in non prod environments, including
26
+ * staging, but logs an error in production mode.
27
+ * This is useful for cases where something unexpected happens, but you can still
28
+ * continue in a slightly broken state.
29
+ * For example, if a block has defined a style control with a hardcoded id and
30
+ * that style control is not found, this is definitely a bug, but it's still
31
+ * possible to go on rendering with a fallback value for this style.
32
+ * It is recommended to liberally use soft assertions so that we fail loudly in
33
+ * staging and local environments.
34
+ *
35
+ * A hard assertion would be more appropriate if we wanted to display some data
36
+ * from an object but that object is null. It's better to throw an assertion
37
+ * error at the place we detect this, instead of continuing and potentially
38
+ * causing an exception in an unrelated part of the code.
39
+ */
40
+ function softAssert(condition, message) {
41
+ if (!condition) if (process.env.NODE_ENV !== "production") throwAtCaller(softAssert, new SoftAssertionError(unwrapErrorMessage(message)));
42
+ else console.error(`Assertion failed: ${unwrapErrorMessage(message)}`);
43
+ }
44
+ var AssertionError = class extends Error {
45
+ name = "AssertionError";
46
+ };
47
+ var SoftAssertionError = class extends Error {
48
+ name = "SoftAssertionError";
49
+ };
50
+ var UnimplementedError = class extends Error {
51
+ name = "UnimplementedError";
52
+ };
53
+ function unwrapErrorMessage(message) {
54
+ return typeof message === "function" ? message() : message ?? "";
55
+ }
56
+ /**
57
+ * Uses chrome's non-standard Error.captureStackTrace to set the top of the stack
58
+ * trace to start from the caller of the supplied function {@param f}.
59
+ * Useful in assertion functions to make sure that the stack trace shows the spot
60
+ * where the assertion failed, not the place where the assertion was thrown.
61
+ */
62
+ function throwAtCaller(f, ex) {
63
+ if (Error.captureStackTrace) Error.captureStackTrace(ex, f);
64
+ throw ex;
65
+ }
66
+
67
+ //#endregion
68
+ export { assert, panic, requireDefined, softAssert, todo };
@@ -0,0 +1,27 @@
1
+ import { assert } from "../assertions.js";
2
+
3
+ //#region src/auth/config.ts
4
+ const DEFAULT_SESSION_MAX_AGE = 2160 * 60 * 60;
5
+ function sessionConfig() {
6
+ const missing = [];
7
+ const applicationId = process.env.SOFTR_APPLICATION_ID;
8
+ if (!applicationId) missing.push("SOFTR_APPLICATION_ID");
9
+ const sessionSecret = process.env.SOFTR_SESSION_SECRET;
10
+ if (!sessionSecret) missing.push("SOFTR_SESSION_SECRET");
11
+ if (missing.length > 0) throw new Error(`Missing ${missing.join(", ")}.`);
12
+ assert(applicationId && sessionSecret);
13
+ const rawMaxAge = process.env.SOFTR_SESSION_MAX_AGE;
14
+ let sessionMaxAge = DEFAULT_SESSION_MAX_AGE;
15
+ if (rawMaxAge) {
16
+ sessionMaxAge = Number(rawMaxAge);
17
+ if (!Number.isInteger(sessionMaxAge) || sessionMaxAge <= 0) throw new Error(`SOFTR_SESSION_MAX_AGE must be a positive whole number of seconds, got "${rawMaxAge}".`);
18
+ }
19
+ return {
20
+ applicationId,
21
+ sessionSecret,
22
+ sessionMaxAge
23
+ };
24
+ }
25
+
26
+ //#endregion
27
+ export { sessionConfig };
@@ -0,0 +1,18 @@
1
+ import { User } from "./user.js";
2
+ import { Permissions } from "./permissions.js";
3
+
4
+ //#region src/auth/context.d.ts
5
+ /** Who the visitor is and what they may do, for one request. */
6
+ type Session = {
7
+ /** The visitor, or `null` when signed out. */
8
+ user: User | null;
9
+ /** What the visitor may do, signed in or not. */
10
+ permissions: Permissions;
11
+ };
12
+ /**
13
+ * The route context the root route publishes — see `getSoftrContext`. The same
14
+ * session, under the name it carries in the public API.
15
+ */
16
+ type SoftrContext = Session;
17
+ //#endregion
18
+ export { Session, SoftrContext };
@@ -0,0 +1,73 @@
1
+ import { User } from "./user.js";
2
+ import { DatasourcePermissions } from "../types.js";
3
+ import { Permissions } from "./permissions.js";
4
+ import { Session, SoftrContext } from "./context.js";
5
+ import * as _tanstack_react_start0 from "@tanstack/react-start";
6
+
7
+ //#region src/auth/isomorphic.d.ts
8
+
9
+ /**
10
+ * Resolves the Softr visitor and their permissions for every request, so that
11
+ * `getSoftrContext` and the route guards have something to read.
12
+ *
13
+ * Register it once, as a global request middleware in the app's `src/start.ts`.
14
+ * It touches nothing but the session.
15
+ *
16
+ * @example
17
+ * export const startInstance = createStart(() => ({
18
+ * requestMiddleware: [softrAuthMiddleware()],
19
+ * }))
20
+ */
21
+ declare function softrAuthMiddleware(): _tanstack_react_start0.RequestMiddlewareAfterServer<{}, undefined, {
22
+ user: User | null;
23
+ permissions: Permissions;
24
+ }>;
25
+ /**
26
+ * Resolves the route context of the root route: the visitor and what they may
27
+ * do. It requires {@link softrAuthMiddleware} to be registered.
28
+ *
29
+ * @example
30
+ * export const Route = createRootRouteWithContext<SoftrContext>()({
31
+ * beforeLoad: async () => await getSoftrContext(),
32
+ * })
33
+ */
34
+ declare const getSoftrContext: _tanstack_react_start0.OptionalFetcher<undefined, undefined, Promise<Session>>;
35
+ /**
36
+ * The context to hand `createRouter`, before any request has resolved one.
37
+ *
38
+ * @example
39
+ * createRouter({ routeTree, context: initialSoftrContext() })
40
+ *
41
+ * @example
42
+ * createRouter({ routeTree, context: { ...initialSoftrContext(), something: "else" } })
43
+ */
44
+ declare function initialSoftrContext(): SoftrContext;
45
+ /**
46
+ * Resolves the current user, returning `null` for anonymous visitors.
47
+ *
48
+ * @example
49
+ * function Greeting() {
50
+ * const user = useCurrentUser()
51
+ * return <p>{user ? `Hello, ${user.name}` : "Please sign in"}</p>
52
+ * }
53
+ */
54
+ declare function useCurrentUser(): User | null;
55
+ /**
56
+ * Resolves what the current user may do with one datasource.
57
+ *
58
+ * A rendering hint: every request is authorized again on the server, so never
59
+ * let this stand in for a permission check.
60
+ *
61
+ * Requires the root route to resolve {@link getSoftrContext}.
62
+ *
63
+ * @example
64
+ * function NewRecordButton() {
65
+ * const { create } = usePermissions(datasourceId)
66
+ * return create ? <button>New record</button> : null
67
+ * }
68
+ *
69
+ * @throws if the application has no such datasource.
70
+ */
71
+ declare function usePermissions(datasourceId: string): DatasourcePermissions;
72
+ //#endregion
73
+ export { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser, usePermissions };
@@ -0,0 +1,113 @@
1
+ import { assert, panic, requireDefined } from "../assertions.js";
2
+ import { writeCookies } from "../cookies.js";
3
+ import { sessionConfig } from "./config.js";
4
+ import { makePlatform } from "./platform.js";
5
+ import { clearSession, makeSessionDataCookie, readSessionData, readSessionToken } from "./session.js";
6
+ import { rootRouteId, useRouteContext } from "@tanstack/react-router";
7
+ import { createMiddleware, createServerFn, getGlobalStartContext } from "@tanstack/react-start";
8
+
9
+ //#region src/auth/isomorphic.ts
10
+ /**
11
+ * Resolves the Softr visitor and their permissions for every request, so that
12
+ * `getSoftrContext` and the route guards have something to read.
13
+ *
14
+ * Register it once, as a global request middleware in the app's `src/start.ts`.
15
+ * It touches nothing but the session.
16
+ *
17
+ * @example
18
+ * export const startInstance = createStart(() => ({
19
+ * requestMiddleware: [softrAuthMiddleware()],
20
+ * }))
21
+ */
22
+ function softrAuthMiddleware() {
23
+ return createMiddleware({ type: "request" }).server(async ({ next }) => {
24
+ async function getSession() {
25
+ const token = readSessionToken();
26
+ const cached = readSessionData(token);
27
+ if (cached !== null) return cached;
28
+ const result = await makePlatform(sessionConfig().applicationId).session(token);
29
+ if (!result.ok) panic(result.message);
30
+ const session = result.data;
31
+ if (token !== null && session.user === null) {
32
+ clearSession();
33
+ return session;
34
+ }
35
+ writeCookies([makeSessionDataCookie(session, token)]);
36
+ return session;
37
+ }
38
+ const { user, permissions } = await getSession();
39
+ return next({ context: {
40
+ user,
41
+ permissions
42
+ } });
43
+ });
44
+ }
45
+ /**
46
+ * Resolves the route context of the root route: the visitor and what they may
47
+ * do. It requires {@link softrAuthMiddleware} to be registered.
48
+ *
49
+ * @example
50
+ * export const Route = createRootRouteWithContext<SoftrContext>()({
51
+ * beforeLoad: async () => await getSoftrContext(),
52
+ * })
53
+ */
54
+ const getSoftrContext = createServerFn({ method: "GET" }).handler(async () => {
55
+ const context = getGlobalStartContext();
56
+ assert(typeof context === "object" && context !== null && "user" in context && "permissions" in context, "softrAuthMiddleware has not run: the Start global context carries no session. Register it as a global request middleware, e.g. createStart(() => ({ requestMiddleware: [softrAuthMiddleware()] })).");
57
+ return context;
58
+ });
59
+ /**
60
+ * The context to hand `createRouter`, before any request has resolved one.
61
+ *
62
+ * @example
63
+ * createRouter({ routeTree, context: initialSoftrContext() })
64
+ *
65
+ * @example
66
+ * createRouter({ routeTree, context: { ...initialSoftrContext(), something: "else" } })
67
+ */
68
+ function initialSoftrContext() {
69
+ return {
70
+ user: null,
71
+ permissions: { datasource: {} }
72
+ };
73
+ }
74
+ /**
75
+ * Resolves the current user, returning `null` for anonymous visitors.
76
+ *
77
+ * @example
78
+ * function Greeting() {
79
+ * const user = useCurrentUser()
80
+ * return <p>{user ? `Hello, ${user.name}` : "Please sign in"}</p>
81
+ * }
82
+ */
83
+ function useCurrentUser() {
84
+ return useRouteContext({
85
+ from: rootRouteId,
86
+ select: (context) => context.user
87
+ });
88
+ }
89
+ /**
90
+ * Resolves what the current user may do with one datasource.
91
+ *
92
+ * A rendering hint: every request is authorized again on the server, so never
93
+ * let this stand in for a permission check.
94
+ *
95
+ * Requires the root route to resolve {@link getSoftrContext}.
96
+ *
97
+ * @example
98
+ * function NewRecordButton() {
99
+ * const { create } = usePermissions(datasourceId)
100
+ * return create ? <button>New record</button> : null
101
+ * }
102
+ *
103
+ * @throws if the application has no such datasource.
104
+ */
105
+ function usePermissions(datasourceId) {
106
+ return useRouteContext({
107
+ from: rootRouteId,
108
+ select: (context) => requireDefined(context.permissions.datasource[datasourceId], `the application has no datasource "${datasourceId}"`)
109
+ });
110
+ }
111
+
112
+ //#endregion
113
+ export { getSoftrContext, initialSoftrContext, softrAuthMiddleware, useCurrentUser, usePermissions };
@@ -0,0 +1,22 @@
1
+ import { User } from "../user.js";
2
+ import { AuthError, Result } from "./utils.js";
3
+
4
+ //#region src/auth/methods/password.d.ts
5
+
6
+ /**
7
+ * Signs a visitor in with an email and password.
8
+ */
9
+ declare function signIn(credentials: {
10
+ email: string;
11
+ password: string;
12
+ }): Promise<Result<User, AuthError>>;
13
+ /**
14
+ * Signs the visitor out and clears the session cookies.
15
+ *
16
+ * This revokes **every** outstanding session for that user in this app, on every
17
+ * device: the platform stamps `tokensInvalidatedAt` and rejects anything issued
18
+ * before it. There is no per-device sign-out.
19
+ */
20
+ declare function signOut(): Promise<void>;
21
+ //#endregion
22
+ export { signIn, signOut };
@@ -0,0 +1,51 @@
1
+ import { panic, softAssert, todo } from "../../assertions.js";
2
+ import { writeCookies } from "../../cookies.js";
3
+ import { sessionConfig } from "../config.js";
4
+ import { makePlatform } from "../platform.js";
5
+ import { clearSession, makeSessionDataCookie, makeSessionTokenCookie, readSessionToken } from "../session.js";
6
+ import { authError } from "./utils.js";
7
+
8
+ //#region src/auth/methods/password.ts
9
+ /**
10
+ * Signs a visitor in with an email and password.
11
+ */
12
+ async function signIn(credentials) {
13
+ const platform = makePlatform(sessionConfig().applicationId);
14
+ const result = await platform.signIn(credentials);
15
+ if (!result.ok) {
16
+ if (result.status === 401 || result.status === 429) return authError(result.status === 401 ? "invalidCredentials" : "tooManyAttempts", result.message);
17
+ if (result.status >= 500) panic(result.message);
18
+ if (result.status === 400) todo(result.message);
19
+ throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-in.`);
20
+ }
21
+ const body = result.data;
22
+ if (body?.authenticatorAppAction) todo(`Sign-in needs an authenticator step (${body.authenticatorAppAction}), which the SDK cannot finish yet.`);
23
+ const token = body?.jwt;
24
+ if (!token) todo("This app is configured for passwordless sign-in, which the SDK cannot finish yet.");
25
+ const sessionResult = await platform.session(token);
26
+ if (!sessionResult.ok) panic(sessionResult.message);
27
+ const session = sessionResult.data;
28
+ if (session.user === null) panic("Session token issued, but no user came back with it.");
29
+ writeCookies([makeSessionTokenCookie(token), makeSessionDataCookie(session, token)]);
30
+ return {
31
+ success: true,
32
+ data: session.user
33
+ };
34
+ }
35
+ /**
36
+ * Signs the visitor out and clears the session cookies.
37
+ *
38
+ * This revokes **every** outstanding session for that user in this app, on every
39
+ * device: the platform stamps `tokensInvalidatedAt` and rejects anything issued
40
+ * before it. There is no per-device sign-out.
41
+ */
42
+ async function signOut() {
43
+ const token = readSessionToken();
44
+ clearSession();
45
+ if (!token) return;
46
+ const result = await makePlatform(sessionConfig().applicationId).signOut(token);
47
+ softAssert(result.ok || result.status < 500, () => `Could not revoke the session (${result.status}). This browser is signed out, but other devices may still be.`);
48
+ }
49
+
50
+ //#endregion
51
+ export { signIn, signOut };
@@ -0,0 +1,37 @@
1
+ import { User } from "../user.js";
2
+ import { AuthError, Result } from "./utils.js";
3
+
4
+ //#region src/auth/methods/signup.d.ts
5
+
6
+ /**
7
+ * Starts a sign-up flow by emailing a verification code.
8
+ *
9
+ * User only exists once `confirmSignUp` returns the code.
10
+ */
11
+ declare function signUp(details: {
12
+ email: string;
13
+ name?: string;
14
+ }): Promise<Result<null, AuthError>>;
15
+ /**
16
+ * Finishes sign-up flow with the emailed code and signs the visitor in.
17
+ *
18
+ * The platform splits on how the app is configured: a code-only app creates the
19
+ * user on the second `sign-up` request and answers with a token, while a
20
+ * password app answers it with an empty 200 and expects the password on
21
+ * `users/new`.
22
+ */
23
+ declare function confirmSignUp(details: {
24
+ email: string;
25
+ /**
26
+ * What `users/new` creates the account with, and unused on a code-only app,
27
+ * which never reaches that request.
28
+ *
29
+ * TODO: temporary in one respect — it is spent a second time on the
30
+ * 400-after-create workaround below, which goes away with the platform fix.
31
+ */
32
+ password: string;
33
+ code: string;
34
+ name?: string;
35
+ }): Promise<Result<User, AuthError>>;
36
+ //#endregion
37
+ export { confirmSignUp, signUp };
@@ -0,0 +1,89 @@
1
+ import { panic, todo } from "../../assertions.js";
2
+ import { writeCookies } from "../../cookies.js";
3
+ import { sessionConfig } from "../config.js";
4
+ import { makePlatform } from "../platform.js";
5
+ import { makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
6
+ import { authError } from "./utils.js";
7
+ import { signIn } from "./password.js";
8
+
9
+ //#region src/auth/methods/signup.ts
10
+ /**
11
+ * Starts a sign-up flow by emailing a verification code.
12
+ *
13
+ * User only exists once `confirmSignUp` returns the code.
14
+ */
15
+ async function signUp(details) {
16
+ const result = await makePlatform(sessionConfig().applicationId).signUp(signUpBody(details));
17
+ if (!result.ok) {
18
+ if (result.status >= 500) panic(result.message);
19
+ if (result.status === 409) return authError("emailTaken", result.message);
20
+ return authError("invalidDetails", result.message);
21
+ }
22
+ return {
23
+ success: true,
24
+ data: null
25
+ };
26
+ }
27
+ /**
28
+ * Finishes sign-up flow with the emailed code and signs the visitor in.
29
+ *
30
+ * The platform splits on how the app is configured: a code-only app creates the
31
+ * user on the second `sign-up` request and answers with a token, while a
32
+ * password app answers it with an empty 200 and expects the password on
33
+ * `users/new`.
34
+ */
35
+ async function confirmSignUp(details) {
36
+ const platform = makePlatform(sessionConfig().applicationId);
37
+ const verified = await platform.signUp({
38
+ ...signUpBody(details),
39
+ token: details.code
40
+ });
41
+ if (!verified.ok) {
42
+ if (verified.status >= 500) panic(verified.message);
43
+ if (verified.status === 409) return authError("emailTaken", verified.message);
44
+ return authError("invalidCode", verified.message);
45
+ }
46
+ let token = verified.data?.jwt;
47
+ if (!token) {
48
+ const created = await platform.new({
49
+ ...signUpBody(details),
50
+ password: details.password,
51
+ token: details.code
52
+ });
53
+ if (created.ok) token = created.data?.jwt;
54
+ else {
55
+ if (created.status >= 500) panic(created.message);
56
+ if (created.status === 409) return authError("emailTaken", created.message);
57
+ const signedIn = await signIn({
58
+ email: details.email,
59
+ password: details.password
60
+ });
61
+ if (!signedIn.success) return authError("invalidCode", created.message);
62
+ return signedIn;
63
+ }
64
+ }
65
+ if (!token) todo("The sign-up returned no session token. This app is configured for a sign-up flow the SDK cannot finish yet.");
66
+ const result = await platform.session(token);
67
+ if (!result.ok) panic(result.message);
68
+ const session = result.data;
69
+ if (session.user === null) panic("Session token issued, but no user could be resolved for it.");
70
+ writeCookies([makeSessionTokenCookie(token), makeSessionDataCookie(session, token)]);
71
+ return {
72
+ success: true,
73
+ data: session.user
74
+ };
75
+ }
76
+ /**
77
+ * `name` is dropped when absent rather than sent as `undefined`: the platform's
78
+ * validator runs on whatever key is present, and an explicit null is not the
79
+ * same request as no name at all.
80
+ */
81
+ function signUpBody(details) {
82
+ return details.name === void 0 ? { email: details.email } : {
83
+ email: details.email,
84
+ name: details.name
85
+ };
86
+ }
87
+
88
+ //#endregion
89
+ export { confirmSignUp, signUp };
@@ -0,0 +1,15 @@
1
+ //#region src/auth/methods/utils.d.ts
2
+ type Result<T, E = Error> = {
3
+ success: false;
4
+ error: E;
5
+ } | {
6
+ success: true;
7
+ data: T;
8
+ };
9
+ type AuthError = {
10
+ code: AuthErrorCode;
11
+ message: string;
12
+ };
13
+ type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode";
14
+ //#endregion
15
+ export { AuthError, Result };
@@ -0,0 +1,13 @@
1
+ //#region src/auth/methods/utils.ts
2
+ function authError(code, message) {
3
+ return {
4
+ success: false,
5
+ error: {
6
+ code,
7
+ message
8
+ }
9
+ };
10
+ }
11
+
12
+ //#endregion
13
+ export { authError };
@@ -0,0 +1,17 @@
1
+ import { DatasourcePermissions } from "../types.js";
2
+ import { z } from "zod";
3
+
4
+ //#region src/auth/permissions.d.ts
5
+
6
+ /**
7
+ * What the visitor may do in this app, as the platform sees it.
8
+ *
9
+ * Rendering hints, not enforcement: every data request is authorized again
10
+ * server-side, and these may go stale between the two.
11
+ */
12
+ type Permissions = {
13
+ /** Keyed by datasource id. */
14
+ datasource: Record<string, DatasourcePermissions>;
15
+ };
16
+ //#endregion
17
+ export { Permissions };
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/auth/permissions.ts
4
+ const datasourcePermissionsSchema = z.object({
5
+ read: z.boolean(),
6
+ create: z.boolean(),
7
+ update: z.boolean(),
8
+ delete: z.boolean()
9
+ });
10
+ const permissionsSchema = z.object({ datasource: z.record(z.string(), datasourcePermissionsSchema) });
11
+
12
+ //#endregion
13
+ export { permissionsSchema };