@protobase/server 0.6.0 → 0.8.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 (105) hide show
  1. package/dist/assistant/conversations.d.ts +1 -1
  2. package/dist/assistant/tools.d.ts +1 -1
  3. package/dist/assistant/turn.d.ts +2 -2
  4. package/dist/audit/origin.js +12 -0
  5. package/dist/auth/jwt.d.ts +2 -0
  6. package/dist/auth/jwt.js +5 -3
  7. package/dist/batch/reorder.js +1 -1
  8. package/dist/better-auth/auth-schema.d.ts +4 -2
  9. package/dist/better-auth/auth-schema.js +18 -8
  10. package/dist/better-auth/authenticator.d.ts +7 -3
  11. package/dist/better-auth/authenticator.js +24 -3
  12. package/dist/better-auth/create-auth.d.ts +683 -10
  13. package/dist/better-auth/create-auth.js +72 -27
  14. package/dist/better-auth/organizations/accept-invitation.js +80 -0
  15. package/dist/better-auth/organizations/active-organization.js +12 -0
  16. package/dist/better-auth/organizations/admin-routes.js +83 -0
  17. package/dist/better-auth/organizations/app-roles.js +43 -0
  18. package/dist/better-auth/organizations/host.d.ts +53 -0
  19. package/dist/better-auth/organizations/host.js +251 -0
  20. package/dist/better-auth/organizations/invitation-endpoints.js +133 -0
  21. package/dist/better-auth/organizations/invitation-mail.js +17 -0
  22. package/dist/better-auth/organizations/invitation-token.js +27 -0
  23. package/dist/better-auth/organizations/member-endpoints.js +182 -0
  24. package/dist/better-auth/organizations/options.d.ts +48 -0
  25. package/dist/better-auth/organizations/options.js +23 -0
  26. package/dist/better-auth/organizations/organization-plugin.js +128 -0
  27. package/dist/better-auth/organizations/protobase-plugin.js +21 -0
  28. package/dist/better-auth/organizations/role-definitions.d.ts +15 -0
  29. package/dist/better-auth/organizations/role-definitions.js +56 -0
  30. package/dist/better-auth/organizations/session-organization.js +68 -0
  31. package/dist/better-auth/organizations/store.d.ts +19 -0
  32. package/dist/better-auth/organizations/store.js +73 -0
  33. package/dist/better-auth/policy-store.js +44 -35
  34. package/dist/better-auth/snake-case-migration.js +122 -0
  35. package/dist/better-auth/snake-case-names.js +72 -0
  36. package/dist/better-auth/staff-checks.js +5 -1
  37. package/dist/better-auth/staff-log.js +56 -45
  38. package/dist/better-auth/staff-sign-in-endpoints.js +1 -1
  39. package/dist/better-auth/status-route.js +14 -3
  40. package/dist/better-auth/users.d.ts +2 -1
  41. package/dist/better-auth/users.js +4 -1
  42. package/dist/create-admin.d.ts +8 -2
  43. package/dist/create-admin.js +31 -2
  44. package/dist/deps.d.ts +1 -0
  45. package/dist/files/attach.js +80 -0
  46. package/dist/files/cleanup-runner.d.ts +8 -0
  47. package/dist/files/cleanup-runner.js +58 -0
  48. package/dist/files/cleanup-schedule.d.ts +28 -0
  49. package/dist/files/cleanup-schedule.js +48 -0
  50. package/dist/files/detect.js +51 -0
  51. package/dist/files/download-url.js +43 -0
  52. package/dist/files/download.js +90 -0
  53. package/dist/files/duration.js +35 -0
  54. package/dist/files/file-cleanup.d.ts +18 -0
  55. package/dist/files/file-cleanup.js +17 -0
  56. package/dist/files/local-files.d.ts +17 -0
  57. package/dist/files/local-files.js +101 -0
  58. package/dist/files/media-types.js +99 -0
  59. package/dist/files/options.d.ts +15 -0
  60. package/dist/files/present.d.ts +15 -0
  61. package/dist/files/present.js +59 -0
  62. package/dist/files/processors.d.ts +15 -0
  63. package/dist/files/processors.js +39 -0
  64. package/dist/files/providers.js +70 -0
  65. package/dist/files/routes.js +26 -0
  66. package/dist/files/runtime.d.ts +3 -0
  67. package/dist/files/runtime.js +37 -0
  68. package/dist/files/signer.js +32 -0
  69. package/dist/files/store.d.ts +37 -0
  70. package/dist/files/ticket.js +87 -0
  71. package/dist/files/upload-body.js +85 -0
  72. package/dist/files/upload.js +124 -0
  73. package/dist/files/uri.js +47 -0
  74. package/dist/functions/define-function.d.ts +71 -0
  75. package/dist/functions/define-function.js +36 -0
  76. package/dist/functions/function-app.d.ts +17 -0
  77. package/dist/functions/function-app.js +19 -0
  78. package/dist/functions/read-json.d.ts +35 -0
  79. package/dist/functions/read-json.js +24 -0
  80. package/dist/functions/records.d.ts +43 -0
  81. package/dist/functions/records.js +31 -0
  82. package/dist/functions/require-env.d.ts +7 -0
  83. package/dist/functions/require-env.js +12 -0
  84. package/dist/functions/routes.js +60 -0
  85. package/dist/functions/verify-signature.d.ts +32 -0
  86. package/dist/functions/verify-signature.js +49 -0
  87. package/dist/index.d.ts +21 -3
  88. package/dist/index.js +13 -3
  89. package/dist/list-service.js +4 -2
  90. package/dist/openapi/resource-paths.js +78 -1
  91. package/dist/openapi/schemas.js +28 -3
  92. package/dist/project-config.d.ts +7 -1
  93. package/dist/project-config.js +37 -1
  94. package/dist/read-service.js +5 -1
  95. package/dist/request-access.d.ts +5 -0
  96. package/dist/request-access.js +9 -4
  97. package/dist/resource-source.d.ts +4 -1
  98. package/dist/routes/batch.js +1 -1
  99. package/dist/routes/collection.js +3 -1
  100. package/dist/routes/record.js +2 -5
  101. package/dist/types.d.ts +25 -5
  102. package/dist/write-pipeline.js +2 -1
  103. package/dist/write-shared.js +6 -3
  104. package/dist/write-update.js +2 -1
  105. package/package.json +6 -4
@@ -0,0 +1,128 @@
1
+ import "./role-definitions.js";
2
+ import { checkAppRoleChange, effectiveRoles, globalRoles, parseAppRoles } from "./app-roles.js";
3
+ import { findMember } from "./store.js";
4
+ import { invitationEmail } from "./invitation-mail.js";
5
+ import { invitationLink, invitationToken } from "./invitation-token.js";
6
+ import { APIError } from "better-auth/api";
7
+ import { organization } from "better-auth/plugins/organization";
8
+ //#region src/better-auth/organizations/organization-plugin.ts
9
+ /** The snake_case names of the organization plugin's tables and columns, with the app roles of members and invitations. */
10
+ const organizationNames = {
11
+ organization: { fields: { createdAt: "created_at" } },
12
+ member: {
13
+ fields: {
14
+ organizationId: "organization_id",
15
+ userId: "user_id",
16
+ createdAt: "created_at"
17
+ },
18
+ additionalFields: { appRoles: {
19
+ type: "string[]",
20
+ required: false,
21
+ defaultValue: [],
22
+ input: false,
23
+ fieldName: "app_roles"
24
+ } }
25
+ },
26
+ invitation: {
27
+ fields: {
28
+ organizationId: "organization_id",
29
+ inviterId: "inviter_id",
30
+ expiresAt: "expires_at",
31
+ createdAt: "created_at"
32
+ },
33
+ additionalFields: { appRoles: {
34
+ type: "string[]",
35
+ required: false,
36
+ defaultValue: [],
37
+ input: true,
38
+ fieldName: "app_roles"
39
+ } }
40
+ },
41
+ session: { fields: { activeOrganizationId: "active_organization_id" } }
42
+ };
43
+ /** Runs Protobase's hook, then the project's, with the same data; data either returns is merged, the project's last. */
44
+ const mergeHooks = (own, project = {}) => {
45
+ const keys = /* @__PURE__ */ new Set([...Object.keys(own), ...Object.keys(project)]);
46
+ return Object.fromEntries([...keys].map((key) => {
47
+ const first = own[key];
48
+ const second = project[key];
49
+ if (!first || !second) return [key, first ?? second];
50
+ const both = async (...args) => {
51
+ const data = {
52
+ ...(await first(...args))?.data,
53
+ ...(await second(...args))?.data
54
+ };
55
+ return Object.keys(data).length > 0 ? { data } : void 0;
56
+ };
57
+ return [key, both];
58
+ }));
59
+ };
60
+ /**
61
+ * Better Auth's organization plugin as Protobase uses it: organization roles `owner`, `admin` and `member` with Better
62
+ * Auth's own access control, app roles on members and invitations, ids from `generateId`, creators holding
63
+ * `creatorAppRoles`, invitations checked against what the inviter may give and mailed with a signed link, and the
64
+ * project's hooks after Protobase's.
65
+ */
66
+ const organizationPlugin = ({ resolved, secret, mailer, context }) => {
67
+ const { definitions } = resolved;
68
+ const own = {
69
+ beforeCreateOrganization: async () => ({ data: { id: await resolved.generateId() } }),
70
+ beforeAddMember: async ({ member }) => member.role === "owner" && member.appRoles === void 0 ? { data: { appRoles: resolved.creatorAppRoles } } : void 0,
71
+ afterCreateOrganization: async ({ organization, member }) => {
72
+ await resolved.options.onCreated?.({
73
+ organization,
74
+ member: {
75
+ ...member,
76
+ appRoles: parseAppRoles(member.appRoles)
77
+ }
78
+ });
79
+ },
80
+ beforeCreateInvitation: async ({ invitation, inviter }) => {
81
+ const membership = await findMember((await context()).adapter, {
82
+ organizationId: invitation.organizationId,
83
+ userId: inviter.id
84
+ });
85
+ const held = effectiveRoles(globalRoles(inviter.role), membership?.appRoles);
86
+ const refusal = checkAppRoleChange({
87
+ held,
88
+ before: [],
89
+ after: parseAppRoles(invitation.appRoles),
90
+ definitions
91
+ });
92
+ if (refusal) throw new APIError(refusal.status, {
93
+ code: refusal.code,
94
+ message: refusal.message
95
+ });
96
+ },
97
+ afterRemoveMember: async ({ member, user }) => {
98
+ if (globalRoles(user.role).length > 0) return;
99
+ const { internalAdapter } = await context();
100
+ for (const session of await internalAdapter.listSessions(member.userId)) if (session.activeOrganizationId === member.organizationId) await internalAdapter.updateSession(session.token, { activeOrganizationId: null });
101
+ },
102
+ beforeDeleteOrganization: async ({ organization }) => {
103
+ await resolved.options.beforeDelete?.(organization);
104
+ }
105
+ };
106
+ return organization({
107
+ creatorRole: "owner",
108
+ allowUserToCreateOrganization: (user) => resolved.create === "everyone" || globalRoles(user.role).includes("admin"),
109
+ invitationExpiresIn: resolved.invitationExpiresIn,
110
+ membershipLimit: resolved.membershipLimit,
111
+ requireEmailVerificationOnInvitation: true,
112
+ cancelPendingInvitationsOnReInvite: true,
113
+ schema: organizationNames,
114
+ ...mailer && { sendInvitationEmail: async ({ invitation, organization, inviter }) => {
115
+ const token = await invitationToken(secret, invitation);
116
+ await mailer.send(invitationEmail({
117
+ to: invitation.email,
118
+ inviter: inviter.user.name || inviter.user.email,
119
+ organization: organization.name,
120
+ url: invitationLink(resolved.invitationUrl, token),
121
+ expiresAt: invitation.expiresAt
122
+ }));
123
+ } },
124
+ organizationHooks: mergeHooks(own, resolved.options.hooks)
125
+ });
126
+ };
127
+ //#endregion
128
+ export { organizationNames, organizationPlugin };
@@ -0,0 +1,21 @@
1
+ import { invitationEndpoints, invitationLinkHook } from "./invitation-endpoints.js";
2
+ import { memberEndpoints } from "./member-endpoints.js";
3
+ //#region src/better-auth/organizations/protobase-plugin.ts
4
+ /**
5
+ * Protobase's endpoints beside Better Auth's organization plugin: invitation links that also make accounts, app roles,
6
+ * handing ownership over, and finding organizations for people with a global role. Without a mailer, whoever invites
7
+ * gets the link in the answer.
8
+ */
9
+ const protobaseOrganizationPlugin = ({ resolved, mail }) => ({
10
+ id: "protobase-organizations",
11
+ endpoints: {
12
+ ...invitationEndpoints({
13
+ resolved,
14
+ mail
15
+ }),
16
+ ...memberEndpoints({ resolved })
17
+ },
18
+ hooks: { after: mail ? [] : [invitationLinkHook(resolved)] }
19
+ });
20
+ //#endregion
21
+ export { protobaseOrganizationPlugin };
@@ -0,0 +1,15 @@
1
+ //#region src/better-auth/organizations/role-definitions.d.ts
2
+ /** A role: its label, or its label and the other roles its holders may also give (see `grantableRoles`). */
3
+ export type RoleDefinition = string | {
4
+ label: string;
5
+ grants?: string[];
6
+ };
7
+ /** The roles of an app: names only, or each with its definition. */
8
+ export type RolesInput = string[] | Record<string, RoleDefinition>;
9
+ export type RoleDefinitions = {
10
+ /** `admin` first, then the others in the order given. */
11
+ names: string[];
12
+ labels: Record<string, string>;
13
+ grants: Record<string, string[]>;
14
+ };
15
+ //#endregion
@@ -0,0 +1,56 @@
1
+ //#region src/better-auth/organizations/role-definitions.ts
2
+ const roleName = /^[a-z][a-z0-9_-]*$/;
3
+ /** The superuser: holds every role in every organization. */
4
+ const superuserRole = "admin";
5
+ /** The admin plugin's role for an account without a global role; in organization mode it grants nothing. */
6
+ const noGlobalRole = "user";
7
+ const organizationRoleNames = ["owner", "member"];
8
+ const builtInLabels = {
9
+ [superuserRole]: "Superuser",
10
+ [noGlobalRole]: "No global role"
11
+ };
12
+ const entriesOf = (input) => Array.isArray(input) ? input.map((name) => [name, void 0]) : Object.entries(input);
13
+ /**
14
+ * The roles with `admin` first (added when missing), their labels and grants. With `organizations`, every role needs a
15
+ * label (`admin` and `user` have one built in), `user` is always there, as the role of an account without a global
16
+ * role, and `owner` and `member` are refused, since they name organization roles.
17
+ */
18
+ const roleDefinitions = (input = ["admin", "user"], { organizations = false } = {}) => {
19
+ const entries = entriesOf(input);
20
+ const bad = entries.find(([name]) => !roleName.test(name));
21
+ if (bad) throw new Error(`Invalid role "${bad[0]}": use lowercase letters, digits, "_" or "-"`);
22
+ const given = new Map(entries);
23
+ const names = [
24
+ superuserRole,
25
+ ...new Set(entries.map(([name]) => name).filter((name) => name !== superuserRole)),
26
+ ...organizations && !given.has("user") ? [noGlobalRole] : []
27
+ ];
28
+ const labels = {};
29
+ const grants = {};
30
+ for (const name of names) {
31
+ const definition = given.get(name);
32
+ const label = typeof definition === "string" ? definition : definition?.label ?? builtInLabels[name];
33
+ if (label !== void 0) labels[name] = label;
34
+ grants[name] = typeof definition === "object" ? definition.grants ?? [] : [];
35
+ const unknown = grants[name].find((granted) => !names.includes(granted));
36
+ if (unknown !== void 0) throw new Error(`Role "${name}" grants "${unknown}", which is not one of the roles: ${names.join(", ")}`);
37
+ }
38
+ if (!organizations) return {
39
+ names,
40
+ labels,
41
+ grants
42
+ };
43
+ const reserved = names.find((name) => organizationRoleNames.includes(name));
44
+ if (reserved) throw new Error(`Role "${reserved}" names an organization role; choose another name`);
45
+ const unlabelled = names.find((name) => labels[name] === void 0);
46
+ if (unlabelled) throw new Error(`Role "${unlabelled}" needs a label: with organizations, roles are given as { name: 'Label' }`);
47
+ return {
48
+ names,
49
+ labels,
50
+ grants
51
+ };
52
+ };
53
+ /** The roles a member of an organization can hold there: every role but the superuser's and the placeholder. */
54
+ const membershipRoles = ({ names }) => names.filter((name) => name !== "admin" && name !== "user");
55
+ //#endregion
56
+ export { membershipRoles, noGlobalRole, roleDefinitions, superuserRole };
@@ -0,0 +1,68 @@
1
+ import { globalRoles } from "./app-roles.js";
2
+ import { initialOrganization } from "./active-organization.js";
3
+ import { findMember, findOrganization, membershipsOf, organizationRoleOf, summaries } from "./store.js";
4
+ //#region src/better-auth/organizations/session-organization.ts
5
+ /** The user field that keeps the organization someone last worked in, for their next sign-in. */
6
+ const lastOrganizationField = { lastOrganizationId: {
7
+ type: "string",
8
+ required: false,
9
+ input: false,
10
+ fieldName: "last_organization_id"
11
+ } };
12
+ /** Whether someone may work in an organization: a member, or holding a global role, which enters every one. */
13
+ const mayEnter = async (adapter, user, organizationId) => {
14
+ if (await findMember(adapter, {
15
+ organizationId,
16
+ userId: user.id
17
+ })) return true;
18
+ return globalRoles(user.role).length > 0 && await findOrganization(adapter, organizationId) !== null;
19
+ };
20
+ /**
21
+ * The token's organization claims for a session: `org`, the active organization, while the person may still work in
22
+ * it; for a member also `org_role`, their organization role, and `app_roles`, their app roles there. Read when the
23
+ * token is issued, so a removed member or a changed role shows within the token's lifetime.
24
+ */
25
+ const organizationClaims = async (adapter, user, activeOrganizationId) => {
26
+ if (!activeOrganizationId) return {};
27
+ const member = await findMember(adapter, {
28
+ organizationId: activeOrganizationId,
29
+ userId: user.id
30
+ });
31
+ if (member) return {
32
+ org: activeOrganizationId,
33
+ org_role: organizationRoleOf(member),
34
+ app_roles: member.appRoles
35
+ };
36
+ if (await mayEnter(adapter, user, activeOrganizationId)) return { org: activeOrganizationId };
37
+ return {};
38
+ };
39
+ /**
40
+ * Better Auth's session hooks for organizations: a new session starts in the organization the person last worked in
41
+ * (`initialOrganization`), and a session's change of organization is kept on the user as the next one's start.
42
+ */
43
+ const sessionOrganizationHooks = (context) => ({
44
+ create: { before: async (session) => {
45
+ const { adapter, internalAdapter } = await context();
46
+ const user = await internalAdapter.findUserById(session.userId);
47
+ if (!user) return;
48
+ const last = user.lastOrganizationId;
49
+ const activeOrganizationId = initialOrganization({
50
+ memberships: summaries(await membershipsOf(adapter, user.id)),
51
+ last,
52
+ entersAny: globalRoles(user.role).length > 0,
53
+ lastExists: Boolean(last) && await findOrganization(adapter, last) !== null
54
+ });
55
+ return activeOrganizationId ? { data: {
56
+ ...session,
57
+ activeOrganizationId
58
+ } } : void 0;
59
+ } },
60
+ update: { after: async (session) => {
61
+ if (!session.activeOrganizationId) return;
62
+ const { internalAdapter } = await context();
63
+ const user = await internalAdapter.findUserById(session.userId);
64
+ if (user && user.lastOrganizationId !== session.activeOrganizationId) await internalAdapter.updateUser(session.userId, { lastOrganizationId: session.activeOrganizationId });
65
+ } }
66
+ });
67
+ //#endregion
68
+ export { lastOrganizationField, mayEnter, organizationClaims, sessionOrganizationHooks };
@@ -0,0 +1,19 @@
1
+ import { createAuthMiddleware } from "better-auth/api";
2
+ //#region src/better-auth/organizations/store.d.ts
3
+ export type OrganizationRole = 'owner' | 'admin' | 'member';
4
+ export type StoredMember = {
5
+ id: string;
6
+ organizationId: string;
7
+ userId: string;
8
+ role: string;
9
+ appRoles: string[];
10
+ createdAt: Date;
11
+ };
12
+ export type StoredOrganization = {
13
+ id: string;
14
+ name: string;
15
+ slug: string;
16
+ logo?: string | null;
17
+ createdAt: Date;
18
+ };
19
+ //#endregion
@@ -0,0 +1,73 @@
1
+ import { parseAppRoles } from "./app-roles.js";
2
+ //#region src/better-auth/organizations/store.ts
3
+ /** Better Auth's `findMany` returns 100 rows unless told otherwise; these lists are read whole. */
4
+ const everyRow = 1e5;
5
+ const memberOf = (row) => ({
6
+ ...row,
7
+ appRoles: parseAppRoles(row.appRoles)
8
+ });
9
+ /** Better Auth stores several organization roles comma separated; Protobase gives each member one. */
10
+ const organizationRoleOf = (member) => {
11
+ const roles = member.role.split(",").map((name) => name.trim());
12
+ return roles.includes("owner") ? "owner" : roles.includes("admin") ? "admin" : "member";
13
+ };
14
+ const findMember = async (adapter, where) => {
15
+ const row = await adapter.findOne({
16
+ model: "member",
17
+ where: [{
18
+ field: "organizationId",
19
+ value: where.organizationId
20
+ }, {
21
+ field: "userId",
22
+ value: where.userId
23
+ }]
24
+ });
25
+ return row ? memberOf(row) : void 0;
26
+ };
27
+ const findMemberById = async (adapter, id) => {
28
+ const row = await adapter.findOne({
29
+ model: "member",
30
+ where: [{
31
+ field: "id",
32
+ value: id
33
+ }]
34
+ });
35
+ return row ? memberOf(row) : void 0;
36
+ };
37
+ const membershipsOf = async (adapter, userId) => (await adapter.findMany({
38
+ model: "member",
39
+ where: [{
40
+ field: "userId",
41
+ value: userId
42
+ }],
43
+ limit: everyRow
44
+ })).map(memberOf);
45
+ const membersOf = async (adapter, organizationId) => (await adapter.findMany({
46
+ model: "member",
47
+ where: [{
48
+ field: "organizationId",
49
+ value: organizationId
50
+ }],
51
+ limit: everyRow
52
+ })).map(memberOf);
53
+ const findOrganization = (adapter, id) => adapter.findOne({
54
+ model: "organization",
55
+ where: [{
56
+ field: "id",
57
+ value: id
58
+ }]
59
+ });
60
+ const summaries = (members) => members.map(({ organizationId, createdAt }) => ({
61
+ organizationId,
62
+ createdAt
63
+ }));
64
+ const setAppRoles = (adapter, memberId, appRoles) => adapter.update({
65
+ model: "member",
66
+ where: [{
67
+ field: "id",
68
+ value: memberId
69
+ }],
70
+ update: { appRoles: [...appRoles] }
71
+ });
72
+ //#endregion
73
+ export { everyRow, findMember, findMemberById, findOrganization, membersOf, membershipsOf, organizationRoleOf, setAppRoles, summaries };
@@ -2,42 +2,51 @@ import { defaultSignInPolicy, parseSignInPolicy } from "./sign-in-policy.js";
2
2
  //#region src/better-auth/policy-store.ts
3
3
  /** Better Auth's model for the sign-in policy: one row per save, the newest applies, the older ones are its history. */
4
4
  const signInPolicyModel = "signInPolicy";
5
- const signInPolicySchema = { [signInPolicyModel]: { fields: {
6
- password: {
7
- type: "string",
8
- required: true
9
- },
10
- emailCode: {
11
- type: "string",
12
- required: true
13
- },
14
- passkey: {
15
- type: "string",
16
- required: true
17
- },
18
- twoFactor: {
19
- type: "string",
20
- required: true
21
- },
22
- staffAccess: {
23
- type: "string",
24
- required: true,
25
- defaultValue: "allowed"
26
- },
27
- platformSignIn: {
28
- type: "string",
29
- required: true,
30
- defaultValue: "allowed"
31
- },
32
- createdAt: {
33
- type: "date",
34
- required: true
35
- },
36
- createdBy: {
37
- type: "string",
38
- required: false
5
+ const signInPolicySchema = { [signInPolicyModel]: {
6
+ modelName: "sign_in_policy",
7
+ fields: {
8
+ password: {
9
+ type: "string",
10
+ required: true
11
+ },
12
+ emailCode: {
13
+ type: "string",
14
+ required: true,
15
+ fieldName: "email_code"
16
+ },
17
+ passkey: {
18
+ type: "string",
19
+ required: true
20
+ },
21
+ twoFactor: {
22
+ type: "string",
23
+ required: true,
24
+ fieldName: "two_factor"
25
+ },
26
+ staffAccess: {
27
+ type: "string",
28
+ required: true,
29
+ defaultValue: "allowed",
30
+ fieldName: "staff_access"
31
+ },
32
+ platformSignIn: {
33
+ type: "string",
34
+ required: true,
35
+ defaultValue: "allowed",
36
+ fieldName: "platform_sign_in"
37
+ },
38
+ createdAt: {
39
+ type: "date",
40
+ required: true,
41
+ fieldName: "created_at"
42
+ },
43
+ createdBy: {
44
+ type: "string",
45
+ required: false,
46
+ fieldName: "created_by"
47
+ }
39
48
  }
40
- } } };
49
+ } };
41
50
  /** The newest saved policy, or the default when none was saved. A row that no longer parses counts as none. */
42
51
  const readSignInPolicy = async (adapter) => {
43
52
  const [row] = await adapter.findMany({
@@ -0,0 +1,122 @@
1
+ //#region src/better-auth/snake-case-migration.ts
2
+ const schemaName = /^[a-z_][a-z0-9_]*$/;
3
+ /**
4
+ * The migration that renames the auth store of Protobase 0.6 in place, from Better Auth's camelCase names to the
5
+ * snake_case ones of `snake-case-names.ts`: tables, columns, and the indexes and constraints named after them. Data,
6
+ * defaults, keys and indexes stay as they are. On a store that has the snake_case names already it does nothing, so a
7
+ * project's migrations can carry it after an auth migration written with them. `schema` is the store's schema, the
8
+ * connection's current one when left out.
9
+ */
10
+ const snakeCaseMigration = (schema) => {
11
+ if (schema !== void 0 && !schemaName.test(schema)) throw new Error(`Invalid auth schema name "${schema}"`);
12
+ const s = schema === void 0 ? "" : `"${schema}".`;
13
+ const current = schema === void 0 ? "current_schema()" : `'${schema}'`;
14
+ return `-- Renames the auth store of Protobase 0.6 to snake_case. A store set up with the snake_case names has no "emailVerified"
15
+ -- and is left as it is.
16
+ do $$
17
+ declare
18
+ constraint_row record;
19
+ begin
20
+ if not exists (select from information_schema.columns where table_schema = ${current} and table_name = 'user' and column_name = 'emailVerified') then
21
+ return;
22
+ end if;
23
+
24
+ alter table ${s}"twoFactor" rename to two_factor;
25
+ alter table ${s}"signInPolicy" rename to sign_in_policy;
26
+ alter table ${s}"staffSignIn" rename to staff_sign_in;
27
+
28
+ alter table ${s}"user" rename column "emailVerified" to email_verified;
29
+ alter table ${s}"user" rename column "createdAt" to created_at;
30
+ alter table ${s}"user" rename column "updatedAt" to updated_at;
31
+ alter table ${s}"user" rename column "banReason" to ban_reason;
32
+ alter table ${s}"user" rename column "banExpires" to ban_expires;
33
+ alter table ${s}"user" rename column "twoFactorEnabled" to two_factor_enabled;
34
+
35
+ alter table ${s}session rename column "expiresAt" to expires_at;
36
+ alter table ${s}session rename column "createdAt" to created_at;
37
+ alter table ${s}session rename column "updatedAt" to updated_at;
38
+ alter table ${s}session rename column "ipAddress" to ip_address;
39
+ alter table ${s}session rename column "userAgent" to user_agent;
40
+ alter table ${s}session rename column "userId" to user_id;
41
+ alter table ${s}session rename column "impersonatedBy" to impersonated_by;
42
+
43
+ alter table ${s}account rename column "accountId" to account_id;
44
+ alter table ${s}account rename column "providerId" to provider_id;
45
+ alter table ${s}account rename column "userId" to user_id;
46
+ alter table ${s}account rename column "accessToken" to access_token;
47
+ alter table ${s}account rename column "refreshToken" to refresh_token;
48
+ alter table ${s}account rename column "idToken" to id_token;
49
+ alter table ${s}account rename column "accessTokenExpiresAt" to access_token_expires_at;
50
+ alter table ${s}account rename column "refreshTokenExpiresAt" to refresh_token_expires_at;
51
+ alter table ${s}account rename column "createdAt" to created_at;
52
+ alter table ${s}account rename column "updatedAt" to updated_at;
53
+
54
+ alter table ${s}verification rename column "expiresAt" to expires_at;
55
+ alter table ${s}verification rename column "createdAt" to created_at;
56
+ alter table ${s}verification rename column "updatedAt" to updated_at;
57
+
58
+ alter table ${s}jwks rename column "publicKey" to public_key;
59
+ alter table ${s}jwks rename column "privateKey" to private_key;
60
+ alter table ${s}jwks rename column "createdAt" to created_at;
61
+ alter table ${s}jwks rename column "expiresAt" to expires_at;
62
+
63
+ alter table ${s}two_factor rename column "backupCodes" to backup_codes;
64
+ alter table ${s}two_factor rename column "userId" to user_id;
65
+ alter table ${s}two_factor rename column "failedVerificationCount" to failed_verification_count;
66
+ alter table ${s}two_factor rename column "lockedUntil" to locked_until;
67
+
68
+ alter table ${s}passkey rename column "publicKey" to public_key;
69
+ alter table ${s}passkey rename column "userId" to user_id;
70
+ alter table ${s}passkey rename column "credentialID" to credential_id;
71
+ alter table ${s}passkey rename column "deviceType" to device_type;
72
+ alter table ${s}passkey rename column "backedUp" to backed_up;
73
+ alter table ${s}passkey rename column "createdAt" to created_at;
74
+
75
+ alter table ${s}sign_in_policy rename column "emailCode" to email_code;
76
+ alter table ${s}sign_in_policy rename column "twoFactor" to two_factor;
77
+ alter table ${s}sign_in_policy rename column "staffAccess" to staff_access;
78
+ alter table ${s}sign_in_policy rename column "platformSignIn" to platform_sign_in;
79
+ alter table ${s}sign_in_policy rename column "createdAt" to created_at;
80
+ alter table ${s}sign_in_policy rename column "createdBy" to created_by;
81
+
82
+ alter table ${s}staff_sign_in rename column "userId" to user_id;
83
+ alter table ${s}staff_sign_in rename column "userEmail" to user_email;
84
+ alter table ${s}staff_sign_in rename column "staffName" to staff_name;
85
+ alter table ${s}staff_sign_in rename column "staffSubject" to staff_subject;
86
+ alter table ${s}staff_sign_in rename column "sessionId" to session_id;
87
+ alter table ${s}staff_sign_in rename column "startedAt" to started_at;
88
+ alter table ${s}staff_sign_in rename column "expiresAt" to expires_at;
89
+ alter table ${s}staff_sign_in rename column "endedAt" to ended_at;
90
+
91
+ -- A primary key's index takes the new name with it.
92
+ alter table ${s}two_factor rename constraint "twoFactor_pkey" to two_factor_pkey;
93
+ alter table ${s}sign_in_policy rename constraint "signInPolicy_pkey" to sign_in_policy_pkey;
94
+ alter table ${s}staff_sign_in rename constraint "staffSignIn_pkey" to staff_sign_in_pkey;
95
+ alter table ${s}session rename constraint "session_userId_fkey" to session_user_id_fkey;
96
+ alter table ${s}account rename constraint "account_userId_fkey" to account_user_id_fkey;
97
+ alter table ${s}two_factor rename constraint "twoFactor_userId_fkey" to two_factor_user_id_fkey;
98
+ alter table ${s}passkey rename constraint "passkey_userId_fkey" to passkey_user_id_fkey;
99
+
100
+ alter index ${s}"session_userId_idx" rename to session_user_id_idx;
101
+ alter index ${s}"account_userId_idx" rename to account_user_id_idx;
102
+ alter index ${s}"twoFactor_secret_idx" rename to two_factor_secret_idx;
103
+ alter index ${s}"twoFactor_userId_idx" rename to two_factor_user_id_idx;
104
+ alter index ${s}"passkey_userId_idx" rename to passkey_user_id_idx;
105
+ alter index ${s}"passkey_credentialID_idx" rename to passkey_credential_id_idx;
106
+
107
+ -- Postgres 18 names each not-null constraint after its table and column, as "user_emailVerified_not_null"; earlier
108
+ -- versions keep no such constraints, so there is nothing to rename there.
109
+ for constraint_row in
110
+ select c.conrelid::regclass as table_name, c.conname as old_name, t.relname || '_' || a.attname || '_not_null' as new_name
111
+ from pg_constraint c
112
+ join pg_class t on t.oid = c.conrelid
113
+ join pg_attribute a on a.attrelid = c.conrelid and a.attnum = c.conkey[1]
114
+ where c.contype = 'n' and t.relnamespace = (select oid from pg_namespace where nspname = ${current}) and c.conname ~ '[A-Z]'
115
+ loop
116
+ execute format('alter table %s rename constraint %I to %I', constraint_row.table_name, constraint_row.old_name, constraint_row.new_name);
117
+ end loop;
118
+ end
119
+ $$;`;
120
+ };
121
+ //#endregion
122
+ export { snakeCaseMigration };
@@ -0,0 +1,72 @@
1
+ //#region src/better-auth/snake-case-names.ts
2
+ /** Better Auth's own tables, as its `user`, `session`, `account` and `verification` options name them. */
3
+ const coreNames = {
4
+ user: { fields: {
5
+ emailVerified: "email_verified",
6
+ createdAt: "created_at",
7
+ updatedAt: "updated_at"
8
+ } },
9
+ session: { fields: {
10
+ expiresAt: "expires_at",
11
+ createdAt: "created_at",
12
+ updatedAt: "updated_at",
13
+ ipAddress: "ip_address",
14
+ userAgent: "user_agent",
15
+ userId: "user_id"
16
+ } },
17
+ account: { fields: {
18
+ accountId: "account_id",
19
+ providerId: "provider_id",
20
+ userId: "user_id",
21
+ accessToken: "access_token",
22
+ refreshToken: "refresh_token",
23
+ idToken: "id_token",
24
+ accessTokenExpiresAt: "access_token_expires_at",
25
+ refreshTokenExpiresAt: "refresh_token_expires_at",
26
+ createdAt: "created_at",
27
+ updatedAt: "updated_at"
28
+ } },
29
+ verification: { fields: {
30
+ expiresAt: "expires_at",
31
+ createdAt: "created_at",
32
+ updatedAt: "updated_at"
33
+ } }
34
+ };
35
+ /** The `schema` option of each plugin with tables or columns of its own. */
36
+ const pluginNames = {
37
+ admin: {
38
+ user: { fields: {
39
+ banReason: "ban_reason",
40
+ banExpires: "ban_expires"
41
+ } },
42
+ session: { fields: { impersonatedBy: "impersonated_by" } }
43
+ },
44
+ jwt: { jwks: { fields: {
45
+ publicKey: "public_key",
46
+ privateKey: "private_key",
47
+ createdAt: "created_at",
48
+ expiresAt: "expires_at"
49
+ } } },
50
+ twoFactor: {
51
+ user: { fields: { twoFactorEnabled: "two_factor_enabled" } },
52
+ twoFactor: {
53
+ modelName: "two_factor",
54
+ fields: {
55
+ backupCodes: "backup_codes",
56
+ userId: "user_id",
57
+ failedVerificationCount: "failed_verification_count",
58
+ lockedUntil: "locked_until"
59
+ }
60
+ }
61
+ },
62
+ passkey: { passkey: { fields: {
63
+ publicKey: "public_key",
64
+ userId: "user_id",
65
+ credentialID: "credential_id",
66
+ deviceType: "device_type",
67
+ backedUp: "backed_up",
68
+ createdAt: "created_at"
69
+ } } }
70
+ };
71
+ //#endregion
72
+ export { coreNames, pluginNames };