@softr/sdk 0.0.24 → 0.0.26-alpha-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/action.d.ts +195 -0
  2. package/dist/action.js +21 -0
  3. package/dist/assertions.js +1 -7
  4. package/dist/auth/isomorphic.d.ts +1 -2
  5. package/dist/auth/methods/authenticator.d.ts +80 -0
  6. package/dist/auth/methods/authenticator.js +175 -0
  7. package/dist/auth/methods/code.d.ts +40 -0
  8. package/dist/auth/methods/code.js +74 -0
  9. package/dist/auth/methods/magicLink.d.ts +24 -0
  10. package/dist/auth/methods/magicLink.js +53 -0
  11. package/dist/auth/methods/password.d.ts +19 -5
  12. package/dist/auth/methods/password.js +36 -15
  13. package/dist/auth/methods/signup.d.ts +22 -14
  14. package/dist/auth/methods/signup.js +48 -54
  15. package/dist/auth/methods/social.d.ts +35 -0
  16. package/dist/auth/methods/social.js +58 -0
  17. package/dist/auth/methods/utils.d.ts +1 -1
  18. package/dist/auth/permissions.d.ts +7 -2
  19. package/dist/auth/platform.js +185 -7
  20. package/dist/auth/server.d.ts +15 -0
  21. package/dist/auth/server.js +16 -1
  22. package/dist/auth/session.js +35 -2
  23. package/dist/cli.d.ts +1 -0
  24. package/dist/cli.js +23 -0
  25. package/dist/codegen/datasource-types.js +471 -0
  26. package/dist/codegen/fetch-metadata.js +46 -0
  27. package/dist/codegen/softr-env.js +102 -0
  28. package/dist/codegen/user-groups.js +11 -0
  29. package/dist/core/integration.js +1 -1
  30. package/dist/datasource/chart.d.ts +196 -0
  31. package/dist/datasource/chart.js +289 -0
  32. package/dist/datasource/datasource.d.ts +16 -0
  33. package/dist/datasource/datasource.js +17 -0
  34. package/dist/datasource/fields.d.ts +122 -0
  35. package/dist/datasource/fields.js +82 -0
  36. package/dist/datasource/filter.d.ts +30 -0
  37. package/dist/datasource/filter.js +146 -0
  38. package/dist/datasource/record.d.ts +138 -0
  39. package/dist/datasource/record.js +258 -0
  40. package/dist/index.d.ts +3 -5
  41. package/dist/index.js +2 -2
  42. package/dist/package.js +5 -0
  43. package/dist/routes/visibility.d.ts +1 -1
  44. package/dist/server.d.ts +1 -1
  45. package/dist/server.js +1 -1
  46. package/package.json +31 -8
  47. package/skills/softr-sdk/SKILL.md +11 -7
  48. package/skills/softr-sdk/auth/SKILL.md +222 -36
  49. package/skills/softr-sdk/charts-and-metrics/SKILL.md +178 -0
  50. package/skills/softr-sdk/charts-and-metrics/references/aggregations.md +76 -0
  51. package/skills/softr-sdk/current-user/SKILL.md +1 -1
  52. package/skills/softr-sdk/data-mutations/SKILL.md +76 -56
  53. package/skills/softr-sdk/reading-records/SKILL.md +77 -93
  54. package/skills/softr-sdk/reading-records/references/field-types.md +41 -48
  55. package/skills/softr-sdk/reading-records/references/query-builder.md +66 -70
  56. package/skills/softr-sdk/routes-and-visibility/SKILL.md +1 -1
  57. package/dist/core/datasource.d.ts +0 -31
  58. package/dist/core/datasource.js +0 -226
  59. package/dist/core/query.d.ts +0 -354
  60. package/dist/core/query.js +0 -528
  61. package/dist/core/utils.js +0 -14
  62. package/dist/types.d.ts +0 -128
@@ -0,0 +1,53 @@
1
+ import { assert, panic } from "../../assertions.js";
2
+ import { writeCookies } from "../../cookies.js";
3
+ import { sessionConfig } from "../config.js";
4
+ import { makePlatform } from "../platform.js";
5
+ import { makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
6
+ import { authError } from "./utils.js";
7
+
8
+ //#region src/auth/methods/magicLink.ts
9
+ /**
10
+ * Signs a visitor in with the token from a magic link they followed.
11
+ *
12
+ * The token keeps working after it is used, so anyone holding the link can sign
13
+ * in as that visitor. Never log it, and redirect away from the URL that carried
14
+ * it.
15
+ *
16
+ */
17
+ async function signInWithMagicLink(credentials) {
18
+ const result = await makePlatform(sessionConfig().applicationId).signInWithMagicLink(credentials);
19
+ if (!result.ok) {
20
+ if (result.status === 429) return authError("tooManyAttempts", result.message);
21
+ if (result.status === 400 || result.status === 401) return authError("invalidCredentials", result.message);
22
+ if (result.status >= 500) panic(`Expected magic-link sign-in to succeed, but got ${result.status}: ` + result.message);
23
+ throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app signs people in with a magic link.`);
24
+ }
25
+ if (result.data.status === "MFA_REQUIRED") {
26
+ const { mfa } = result.data;
27
+ assert(mfa, "Expected a challenge from magic-link sign-in, but got none");
28
+ writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
29
+ return {
30
+ success: true,
31
+ data: {
32
+ status: "authenticatorRequired",
33
+ step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
34
+ }
35
+ };
36
+ }
37
+ const { session } = result.data;
38
+ assert(session, "Expected a session from magic-link sign-in, but got none");
39
+ writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
40
+ user: session.user,
41
+ permissions: session.permissions
42
+ }, session.jwt)]);
43
+ return {
44
+ success: true,
45
+ data: {
46
+ status: "signedIn",
47
+ user: session.user
48
+ }
49
+ };
50
+ }
51
+
52
+ //#endregion
53
+ export { signInWithMagicLink };
@@ -5,17 +5,31 @@ import { AuthError, Result } from "./utils.js";
5
5
 
6
6
  /**
7
7
  * Signs a visitor in with an email and password.
8
+ *
9
+ * A `signedIn` result means the visitor is signed in. An app that also asks for
10
+ * an authenticator app answers `authenticatorRequired` instead, naming the step
11
+ * to send them to next.
8
12
  */
9
13
  declare function signIn(credentials: {
10
14
  email: string;
11
15
  password: string;
12
- }): Promise<Result<User, AuthError>>;
16
+ }): Promise<Result<{
17
+ status: "signedIn";
18
+ user: User;
19
+ } | {
20
+ status: "authenticatorRequired";
21
+ /**
22
+ * `verify`: they have an authenticator app and owe a code.
23
+ * `setup`: the app requires one and they have none yet.
24
+ * `setupOptional`: same, except they may skip it instead.
25
+ */
26
+ step: "verify" | "setup" | "setupOptional";
27
+ }, AuthError>>;
13
28
  /**
14
- * Signs the visitor out and clears the session cookies.
29
+ * Signs the visitor out.
15
30
  *
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.
31
+ * This ends **every** session that user has in this app, on every device. There
32
+ * is no per-device sign-out.
19
33
  */
20
34
  declare function signOut(): Promise<void>;
21
35
  //#endregion
@@ -1,44 +1,65 @@
1
- import { panic, softAssert } from "../../assertions.js";
1
+ import { assert, panic, softAssert } from "../../assertions.js";
2
2
  import { writeCookies } from "../../cookies.js";
3
3
  import { sessionConfig } from "../config.js";
4
4
  import { makePlatform } from "../platform.js";
5
- import { clearSession, makeSessionDataCookie, makeSessionTokenCookie, readSessionToken } from "../session.js";
5
+ import { clearPendingAuth, clearSession, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie, readSessionToken } from "../session.js";
6
6
  import { authError } from "./utils.js";
7
7
 
8
8
  //#region src/auth/methods/password.ts
9
9
  /**
10
10
  * Signs a visitor in with an email and password.
11
+ *
12
+ * A `signedIn` result means the visitor is signed in. An app that also asks for
13
+ * an authenticator app answers `authenticatorRequired` instead, naming the step
14
+ * to send them to next.
11
15
  */
12
16
  async function signIn(credentials) {
13
17
  const result = await makePlatform(sessionConfig().applicationId).tokens(credentials);
14
18
  if (!result.ok) {
15
- if (result.status === 401 || result.status === 429) return authError(result.status === 401 ? "invalidCredentials" : "tooManyAttempts", result.message);
16
- if (result.status >= 500) panic(result.message);
19
+ if (result.status === 401) return authError("invalidCredentials", result.message);
20
+ if (result.status === 429) return authError("tooManyAttempts", result.message);
21
+ if (result.status >= 500) panic(`Expected sign-in to succeed, but got ${result.status}: ` + result.message);
17
22
  throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-in.`);
18
23
  }
19
- const { jwt, user, permissions } = result.data;
20
- writeCookies([makeSessionTokenCookie(jwt), makeSessionDataCookie({
21
- user,
22
- permissions
23
- }, jwt)]);
24
+ if (result.data.status === "MFA_REQUIRED") {
25
+ const { mfa } = result.data;
26
+ assert(mfa, "Expected a challenge from sign-in, but got none");
27
+ writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
28
+ return {
29
+ success: true,
30
+ data: {
31
+ status: "authenticatorRequired",
32
+ step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
33
+ }
34
+ };
35
+ }
36
+ const { session } = result.data;
37
+ assert(session, "Expected a session from sign-in, but got none");
38
+ writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
39
+ user: session.user,
40
+ permissions: session.permissions
41
+ }, session.jwt)]);
42
+ clearPendingAuth();
24
43
  return {
25
44
  success: true,
26
- data: user
45
+ data: {
46
+ status: "signedIn",
47
+ user: session.user
48
+ }
27
49
  };
28
50
  }
29
51
  /**
30
- * Signs the visitor out and clears the session cookies.
52
+ * Signs the visitor out.
31
53
  *
32
- * This revokes **every** outstanding session for that user in this app, on every
33
- * device: the platform stamps `tokensInvalidatedAt` and rejects anything issued
34
- * before it. There is no per-device sign-out.
54
+ * This ends **every** session that user has in this app, on every device. There
55
+ * is no per-device sign-out.
35
56
  */
36
57
  async function signOut() {
37
58
  const token = readSessionToken();
38
59
  clearSession();
39
60
  if (!token) return;
40
61
  const result = await makePlatform(sessionConfig().applicationId).signOut(token);
41
- softAssert(result.ok || result.status < 500, () => `Could not revoke the session (${result.status}). This browser is signed out, but other devices may still be.`);
62
+ softAssert(result.ok || result.status < 500, () => `Expected sign-out to succeed, but got ${result.status}. This browser is signed out, other devices may not be.`);
42
63
  }
43
64
 
44
65
  //#endregion
@@ -13,25 +13,33 @@ declare function signUp(details: {
13
13
  name?: string;
14
14
  }): Promise<Result<null, AuthError>>;
15
15
  /**
16
- * Finishes sign-up flow with the emailed code and signs the visitor in.
16
+ * Finishes a sign-up with the emailed code, creating the account and signing
17
+ * the visitor in.
17
18
  *
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`.
19
+ * `password` is what the account is created with. Leave it out only for an app
20
+ * that signs people in with an emailed code. An app expecting a password
21
+ * rejects a sign-up without one, and the other way round.
22
+ *
23
+ * A `signedIn` result means the visitor is signed in. An app that also asks for
24
+ * an authenticator app answers `authenticatorRequired` instead, naming the step
25
+ * to send them to next.
22
26
  */
23
27
  declare function confirmSignUp(details: {
24
28
  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
29
  code: string;
34
30
  name?: string;
35
- }): Promise<Result<User, AuthError>>;
31
+ password?: string;
32
+ }): Promise<Result<{
33
+ status: "signedIn";
34
+ user: User;
35
+ } | {
36
+ status: "authenticatorRequired";
37
+ /**
38
+ * `verify`: they have an authenticator app and owe a code.
39
+ * `setup`: the app requires one and they have none yet.
40
+ * `setupOptional`: same, except they may skip it instead.
41
+ */
42
+ step: "verify" | "setup" | "setupOptional";
43
+ }, AuthError>>;
36
44
  //#endregion
37
45
  export { confirmSignUp, signUp };
@@ -1,10 +1,9 @@
1
- import { panic, todo } from "../../assertions.js";
1
+ import { assert, panic } from "../../assertions.js";
2
2
  import { writeCookies } from "../../cookies.js";
3
3
  import { sessionConfig } from "../config.js";
4
4
  import { makePlatform } from "../platform.js";
5
- import { makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
5
+ import { clearPendingAuth, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
6
6
  import { authError } from "./utils.js";
7
- import { signIn } from "./password.js";
8
7
 
9
8
  //#region src/auth/methods/signup.ts
10
9
  /**
@@ -13,11 +12,13 @@ import { signIn } from "./password.js";
13
12
  * User only exists once `confirmSignUp` returns the code.
14
13
  */
15
14
  async function signUp(details) {
16
- const result = await makePlatform(sessionConfig().applicationId).signUp(signUpBody(details));
15
+ const result = await makePlatform(sessionConfig().applicationId).sendSignUpCode(details);
17
16
  if (!result.ok) {
18
- if (result.status >= 500) panic(result.message);
19
17
  if (result.status === 409) return authError("emailTaken", result.message);
20
- return authError("invalidDetails", result.message);
18
+ if (result.status === 429) return authError("tooManyAttempts", result.message);
19
+ if (result.status === 400) return authError("invalidDetails", result.message);
20
+ if (result.status >= 500) panic(`Expected the sign-up code to be emailed, but got ${result.status}: ` + result.message);
21
+ throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-up.`);
21
22
  }
22
23
  return {
23
24
  success: true,
@@ -25,63 +26,56 @@ async function signUp(details) {
25
26
  };
26
27
  }
27
28
  /**
28
- * Finishes sign-up flow with the emailed code and signs the visitor in.
29
+ * Finishes a sign-up with the emailed code, creating the account and signing
30
+ * the visitor in.
29
31
  *
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`.
32
+ * `password` is what the account is created with. Leave it out only for an app
33
+ * that signs people in with an emailed code. An app expecting a password
34
+ * rejects a sign-up without one, and the other way round.
35
+ *
36
+ * A `signedIn` result means the visitor is signed in. An app that also asks for
37
+ * an authenticator app answers `authenticatorRequired` instead, naming the step
38
+ * to send them to next.
34
39
  */
35
40
  async function confirmSignUp(details) {
36
41
  const platform = makePlatform(sessionConfig().applicationId);
37
- const verified = await platform.signUp({
38
- ...signUpBody(details),
39
- token: details.code
42
+ const { password,...rest } = details;
43
+ const result = password === void 0 ? await platform.signUpWithCode(rest) : await platform.signUpWithPassword({
44
+ ...rest,
45
+ password
40
46
  });
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);
47
+ if (!result.ok) {
48
+ if (result.status === 409) return authError("emailTaken", result.message);
49
+ if (result.status === 429) return authError("tooManyAttempts", result.message);
50
+ if (result.status === 400 || result.status === 401) return authError("invalidCode", result.message);
51
+ if (result.status >= 500) panic(`Expected sign-up to succeed, but got ${result.status}: ` + result.message);
52
+ throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows sign-up.`);
45
53
  }
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
- }
54
+ if (result.data.status === "MFA_REQUIRED") {
55
+ const { mfa } = result.data;
56
+ assert(mfa, "Expected a challenge from sign-up, but got none");
57
+ writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
58
+ return {
59
+ success: true,
60
+ data: {
61
+ status: "authenticatorRequired",
62
+ step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
63
+ }
64
+ };
64
65
  }
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)]);
66
+ const { session } = result.data;
67
+ assert(session, "Expected a session from sign-up, but got none");
68
+ writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
69
+ user: session.user,
70
+ permissions: session.permissions
71
+ }, session.jwt)]);
72
+ clearPendingAuth();
71
73
  return {
72
74
  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
75
+ data: {
76
+ status: "signedIn",
77
+ user: session.user
78
+ }
85
79
  };
86
80
  }
87
81
 
@@ -0,0 +1,35 @@
1
+ import { User } from "../user.js";
2
+ import { AuthError, Result } from "./utils.js";
3
+
4
+ //#region src/auth/methods/social.d.ts
5
+
6
+ /**
7
+ * Signs a visitor in with a Google token the browser obtained, creating their
8
+ * account the first time.
9
+ *
10
+ * Pass the id token Google Identity Services handed the page. An access token
11
+ * is rejected, and so is a token minted for another app. `fullName` names a
12
+ * first-time visitor and does nothing for one who already has an account.
13
+ *
14
+ * A `signedIn` result means the visitor is signed in. An app that also asks for
15
+ * an authenticator app answers `authenticatorRequired` instead, naming the step
16
+ * to send them to next.
17
+ */
18
+ declare function signInWithGoogle(credentials: {
19
+ email: string;
20
+ token: string;
21
+ fullName?: string;
22
+ }): Promise<Result<{
23
+ status: "signedIn";
24
+ user: User;
25
+ } | {
26
+ status: "authenticatorRequired";
27
+ /**
28
+ * `verify`: they have an authenticator app and owe a code.
29
+ * `setup`: the app requires one and they have none yet.
30
+ * `setupOptional`: same, except they may skip it instead.
31
+ */
32
+ step: "verify" | "setup" | "setupOptional";
33
+ }, AuthError>>;
34
+ //#endregion
35
+ export { signInWithGoogle };
@@ -0,0 +1,58 @@
1
+ import { assert, panic } from "../../assertions.js";
2
+ import { writeCookies } from "../../cookies.js";
3
+ import { sessionConfig } from "../config.js";
4
+ import { makePlatform } from "../platform.js";
5
+ import { clearPendingAuth, makePendingAuthCookie, makeSessionDataCookie, makeSessionTokenCookie } from "../session.js";
6
+ import { authError } from "./utils.js";
7
+
8
+ //#region src/auth/methods/social.ts
9
+ /**
10
+ * Signs a visitor in with a Google token the browser obtained, creating their
11
+ * account the first time.
12
+ *
13
+ * Pass the id token Google Identity Services handed the page. An access token
14
+ * is rejected, and so is a token minted for another app. `fullName` names a
15
+ * first-time visitor and does nothing for one who already has an account.
16
+ *
17
+ * A `signedIn` result means the visitor is signed in. An app that also asks for
18
+ * an authenticator app answers `authenticatorRequired` instead, naming the step
19
+ * to send them to next.
20
+ */
21
+ async function signInWithGoogle(credentials) {
22
+ const result = await makePlatform(sessionConfig().applicationId).signInWithGoogle(credentials);
23
+ if (!result.ok) {
24
+ if (result.status === 429) return authError("tooManyAttempts", result.message);
25
+ if (result.status === 400 || result.status === 401) return authError("invalidCredentials", result.message);
26
+ if (result.status >= 500) panic(`Expected Google sign-in to succeed, but got ${result.status}: ` + result.message);
27
+ throw new Error(`${result.message}. Check SOFTR_APPLICATION_ID and that the app allows Google sign-in.`);
28
+ }
29
+ if (result.data.status === "MFA_REQUIRED") {
30
+ const { mfa } = result.data;
31
+ assert(mfa, "Expected a challenge from Google sign-in, but got none");
32
+ writeCookies([makePendingAuthCookie(mfa.challengeToken)]);
33
+ return {
34
+ success: true,
35
+ data: {
36
+ status: "authenticatorRequired",
37
+ step: mfa.action === "VERIFICATION_REQUIRED" ? "verify" : mfa.action === "SETUP_REQUIRED" ? "setup" : "setupOptional"
38
+ }
39
+ };
40
+ }
41
+ const { session } = result.data;
42
+ assert(session, "Expected a session from Google sign-in, but got none");
43
+ writeCookies([makeSessionTokenCookie(session.jwt), makeSessionDataCookie({
44
+ user: session.user,
45
+ permissions: session.permissions
46
+ }, session.jwt)]);
47
+ clearPendingAuth();
48
+ return {
49
+ success: true,
50
+ data: {
51
+ status: "signedIn",
52
+ user: session.user
53
+ }
54
+ };
55
+ }
56
+
57
+ //#endregion
58
+ export { signInWithGoogle };
@@ -10,6 +10,6 @@ type AuthError = {
10
10
  code: AuthErrorCode;
11
11
  message: string;
12
12
  };
13
- type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode";
13
+ type AuthErrorCode = "invalidCredentials" | "tooManyAttempts" | "emailTaken" | "invalidDetails" | "invalidCode" | "notAuthenticated";
14
14
  //#endregion
15
15
  export { AuthError, Result };
@@ -1,4 +1,3 @@
1
- import { DatasourcePermissions } from "../types.js";
2
1
  import { z } from "zod";
3
2
 
4
3
  //#region src/auth/permissions.d.ts
@@ -9,9 +8,15 @@ import { z } from "zod";
9
8
  * Rendering hints, not enforcement: every data request is authorized again
10
9
  * server-side, and these may go stale between the two.
11
10
  */
11
+ type DatasourcePermissions = {
12
+ read: boolean;
13
+ create: boolean;
14
+ update: boolean;
15
+ delete: boolean;
16
+ };
12
17
  type Permissions = {
13
18
  /** Keyed by datasource id. */
14
19
  datasources: Record<string, DatasourcePermissions>;
15
20
  };
16
21
  //#endregion
17
- export { Permissions };
22
+ export { DatasourcePermissions, Permissions };