@better-auth/core 1.7.0-rc.1 → 1.7.0-rc.2

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 (208) hide show
  1. package/dist/api/index.d.mts +28 -28
  2. package/dist/async_hooks/index.d.mts +0 -1
  3. package/dist/async_hooks/pure.index.d.mts +0 -1
  4. package/dist/context/endpoint-context.d.mts +1 -1
  5. package/dist/context/global.d.mts +0 -1
  6. package/dist/context/global.mjs +1 -1
  7. package/dist/context/request-state.d.mts +0 -1
  8. package/dist/context/request-state.mjs +8 -5
  9. package/dist/context/transaction.d.mts +8 -3
  10. package/dist/context/transaction.mjs +27 -10
  11. package/dist/db/adapter/factory.d.mts +3 -5
  12. package/dist/db/adapter/factory.mjs +7 -5
  13. package/dist/db/adapter/get-default-field-name.d.mts +2 -9
  14. package/dist/db/adapter/get-default-model-name.d.mts +1 -5
  15. package/dist/db/adapter/get-default-model-name.mjs +6 -5
  16. package/dist/db/adapter/get-field-attributes.d.mts +4 -11
  17. package/dist/db/adapter/get-field-name.d.mts +2 -9
  18. package/dist/db/adapter/get-id-field.d.mts +4 -12
  19. package/dist/db/adapter/get-model-name.d.mts +1 -5
  20. package/dist/db/adapter/index.d.mts +7 -32
  21. package/dist/db/adapter/types.d.mts +5 -17
  22. package/dist/db/adapter/utils.d.mts +0 -1
  23. package/dist/db/database-index.d.mts +44 -0
  24. package/dist/db/database-index.mjs +168 -0
  25. package/dist/db/get-tables.d.mts +7 -1
  26. package/dist/db/get-tables.mjs +47 -6
  27. package/dist/db/index.d.mts +3 -3
  28. package/dist/db/index.mjs +2 -2
  29. package/dist/db/internal.d.mts +3 -0
  30. package/dist/db/internal.mjs +3 -0
  31. package/dist/db/plugin.d.mts +4 -3
  32. package/dist/db/schema/account.d.mts +16 -3
  33. package/dist/db/schema/account.mjs +35 -2
  34. package/dist/db/schema/rate-limit.d.mts +1 -1
  35. package/dist/db/schema/rate-limit.mjs +9 -0
  36. package/dist/db/schema/session.d.mts +1 -1
  37. package/dist/db/schema/shared.d.mts +0 -1
  38. package/dist/db/schema/user.d.mts +1 -1
  39. package/dist/db/schema/verification.d.mts +1 -1
  40. package/dist/db/type.d.mts +17 -9
  41. package/dist/env/logger.d.mts +25 -25
  42. package/dist/error/codes.d.mts +0 -1
  43. package/dist/error/index.d.mts +0 -1
  44. package/dist/index.d.mts +1 -1
  45. package/dist/instrumentation/attributes.d.mts +0 -1
  46. package/dist/instrumentation/pure.index.d.mts +0 -1
  47. package/dist/instrumentation/tracer.mjs +1 -1
  48. package/dist/oauth2/authorization-params.d.mts +0 -1
  49. package/dist/oauth2/client-assertion.d.mts +3 -13
  50. package/dist/oauth2/client-credentials-token.d.mts +3 -17
  51. package/dist/oauth2/client-credentials-token.mjs +1 -4
  52. package/dist/oauth2/create-authorization-url.d.mts +3 -21
  53. package/dist/oauth2/dpop.d.mts +2 -23
  54. package/dist/oauth2/dpop.mjs +1 -1
  55. package/dist/oauth2/index.d.mts +2 -2
  56. package/dist/oauth2/oauth-provider.d.mts +85 -36
  57. package/dist/oauth2/refresh-access-token.d.mts +3 -19
  58. package/dist/oauth2/refresh-access-token.mjs +3 -9
  59. package/dist/oauth2/reject-redirects.mjs +1 -1
  60. package/dist/oauth2/token-endpoint-auth.d.mts +0 -1
  61. package/dist/oauth2/utils.d.mts +0 -1
  62. package/dist/oauth2/utils.mjs +2 -3
  63. package/dist/oauth2/validate-authorization-code.d.mts +5 -29
  64. package/dist/oauth2/verify-id-token.d.mts +3 -2
  65. package/dist/oauth2/verify-id-token.mjs +3 -3
  66. package/dist/oauth2/verify.d.mts +5 -7
  67. package/dist/oauth2/verify.mjs +2 -2
  68. package/dist/social-providers/apple.d.mts +9 -24
  69. package/dist/social-providers/apple.mjs +2 -1
  70. package/dist/social-providers/atlassian.d.mts +18 -19
  71. package/dist/social-providers/atlassian.mjs +1 -1
  72. package/dist/social-providers/cognito.d.mts +33 -22
  73. package/dist/social-providers/cognito.mjs +2 -2
  74. package/dist/social-providers/discord.d.mts +7 -20
  75. package/dist/social-providers/discord.mjs +1 -1
  76. package/dist/social-providers/dropbox.d.mts +7 -22
  77. package/dist/social-providers/dropbox.mjs +1 -1
  78. package/dist/social-providers/facebook.d.mts +19 -27
  79. package/dist/social-providers/facebook.mjs +6 -17
  80. package/dist/social-providers/figma.d.mts +7 -22
  81. package/dist/social-providers/figma.mjs +1 -1
  82. package/dist/social-providers/github.d.mts +7 -23
  83. package/dist/social-providers/github.mjs +1 -1
  84. package/dist/social-providers/gitlab.d.mts +6 -43
  85. package/dist/social-providers/gitlab.mjs +1 -1
  86. package/dist/social-providers/google.d.mts +10 -32
  87. package/dist/social-providers/google.mjs +2 -1
  88. package/dist/social-providers/huggingface.d.mts +7 -22
  89. package/dist/social-providers/huggingface.mjs +1 -1
  90. package/dist/social-providers/index.d.mts +229 -787
  91. package/dist/social-providers/kakao.d.mts +8 -33
  92. package/dist/social-providers/kakao.mjs +1 -1
  93. package/dist/social-providers/kick.d.mts +7 -22
  94. package/dist/social-providers/kick.mjs +1 -1
  95. package/dist/social-providers/line.d.mts +8 -44
  96. package/dist/social-providers/line.mjs +4 -6
  97. package/dist/social-providers/linear.d.mts +7 -21
  98. package/dist/social-providers/linear.mjs +1 -1
  99. package/dist/social-providers/linkedin.d.mts +7 -21
  100. package/dist/social-providers/linkedin.mjs +1 -1
  101. package/dist/social-providers/microsoft-entra-id.d.mts +8 -18
  102. package/dist/social-providers/microsoft-entra-id.mjs +18 -2
  103. package/dist/social-providers/naver.d.mts +27 -42
  104. package/dist/social-providers/naver.mjs +1 -1
  105. package/dist/social-providers/notion.d.mts +7 -21
  106. package/dist/social-providers/notion.mjs +1 -1
  107. package/dist/social-providers/paybin.d.mts +8 -23
  108. package/dist/social-providers/paybin.mjs +2 -1
  109. package/dist/social-providers/paypal.d.mts +8 -33
  110. package/dist/social-providers/paypal.mjs +1 -1
  111. package/dist/social-providers/polar.d.mts +7 -22
  112. package/dist/social-providers/polar.mjs +1 -1
  113. package/dist/social-providers/railway.d.mts +7 -22
  114. package/dist/social-providers/railway.mjs +1 -1
  115. package/dist/social-providers/reddit.d.mts +7 -20
  116. package/dist/social-providers/reddit.mjs +1 -1
  117. package/dist/social-providers/roblox.d.mts +7 -20
  118. package/dist/social-providers/roblox.mjs +1 -1
  119. package/dist/social-providers/salesforce.d.mts +7 -22
  120. package/dist/social-providers/salesforce.mjs +1 -1
  121. package/dist/social-providers/slack.d.mts +7 -20
  122. package/dist/social-providers/slack.mjs +1 -1
  123. package/dist/social-providers/spotify.d.mts +7 -22
  124. package/dist/social-providers/spotify.mjs +1 -1
  125. package/dist/social-providers/tiktok.d.mts +10 -22
  126. package/dist/social-providers/tiktok.mjs +1 -1
  127. package/dist/social-providers/twitch.d.mts +7 -20
  128. package/dist/social-providers/twitch.mjs +1 -1
  129. package/dist/social-providers/twitter.d.mts +29 -26
  130. package/dist/social-providers/twitter.mjs +1 -1
  131. package/dist/social-providers/vercel.d.mts +7 -22
  132. package/dist/social-providers/vercel.mjs +1 -1
  133. package/dist/social-providers/vk.d.mts +8 -24
  134. package/dist/social-providers/vk.mjs +5 -6
  135. package/dist/social-providers/wechat.d.mts +7 -19
  136. package/dist/social-providers/wechat.mjs +1 -1
  137. package/dist/social-providers/zoom.d.mts +9 -25
  138. package/dist/social-providers/zoom.mjs +1 -1
  139. package/dist/types/context.d.mts +16 -9
  140. package/dist/types/cookie.d.mts +0 -1
  141. package/dist/types/helper.d.mts +1 -1
  142. package/dist/types/index.d.mts +1 -1
  143. package/dist/types/init-options.d.mts +37 -26
  144. package/dist/types/plugin-client.d.mts +1 -3
  145. package/dist/types/plugin.d.mts +2 -2
  146. package/dist/utils/async.d.mts +0 -1
  147. package/dist/utils/db.d.mts +1 -0
  148. package/dist/utils/deprecate.d.mts +0 -1
  149. package/dist/utils/error-codes.d.mts +2 -2
  150. package/dist/utils/host.d.mts +27 -1
  151. package/dist/utils/host.mjs +1 -1
  152. package/dist/utils/ip.d.mts +1 -0
  153. package/dist/utils/ip.mjs +1 -1
  154. package/dist/utils/is-api-error.d.mts +0 -1
  155. package/dist/utils/redirect-uri.d.mts +0 -1
  156. package/package.json +10 -2
  157. package/src/context/request-state.ts +26 -6
  158. package/src/context/transaction.ts +37 -18
  159. package/src/db/adapter/factory.ts +24 -18
  160. package/src/db/adapter/get-default-model-name.ts +23 -14
  161. package/src/db/database-index.ts +351 -0
  162. package/src/db/get-tables.ts +89 -9
  163. package/src/db/index.ts +4 -0
  164. package/src/db/internal.ts +12 -0
  165. package/src/db/plugin.ts +3 -1
  166. package/src/db/schema/account.ts +27 -1
  167. package/src/db/schema/user.ts +1 -1
  168. package/src/db/type.ts +12 -0
  169. package/src/oauth2/index.ts +3 -0
  170. package/src/oauth2/oauth-provider.ts +94 -36
  171. package/src/oauth2/verify-id-token.ts +4 -2
  172. package/src/social-providers/apple.ts +2 -1
  173. package/src/social-providers/atlassian.ts +1 -1
  174. package/src/social-providers/cognito.ts +2 -2
  175. package/src/social-providers/discord.ts +1 -1
  176. package/src/social-providers/dropbox.ts +1 -1
  177. package/src/social-providers/facebook.ts +21 -28
  178. package/src/social-providers/figma.ts +1 -1
  179. package/src/social-providers/github.ts +1 -1
  180. package/src/social-providers/gitlab.ts +1 -1
  181. package/src/social-providers/google.ts +2 -1
  182. package/src/social-providers/huggingface.ts +1 -1
  183. package/src/social-providers/kakao.ts +1 -1
  184. package/src/social-providers/kick.ts +1 -1
  185. package/src/social-providers/line.ts +7 -12
  186. package/src/social-providers/linear.ts +1 -1
  187. package/src/social-providers/linkedin.ts +1 -1
  188. package/src/social-providers/microsoft-entra-id.ts +3 -2
  189. package/src/social-providers/naver.ts +1 -1
  190. package/src/social-providers/notion.ts +1 -1
  191. package/src/social-providers/paybin.ts +2 -1
  192. package/src/social-providers/paypal.ts +1 -1
  193. package/src/social-providers/polar.ts +1 -1
  194. package/src/social-providers/railway.ts +1 -1
  195. package/src/social-providers/reddit.ts +1 -1
  196. package/src/social-providers/roblox.ts +1 -1
  197. package/src/social-providers/salesforce.ts +1 -1
  198. package/src/social-providers/slack.ts +1 -1
  199. package/src/social-providers/spotify.ts +1 -1
  200. package/src/social-providers/tiktok.ts +2 -2
  201. package/src/social-providers/twitch.ts +1 -1
  202. package/src/social-providers/twitter.ts +1 -1
  203. package/src/social-providers/vercel.ts +1 -1
  204. package/src/social-providers/vk.ts +2 -2
  205. package/src/social-providers/wechat.ts +1 -1
  206. package/src/social-providers/zoom.ts +1 -1
  207. package/src/types/context.ts +21 -14
  208. package/src/types/init-options.ts +15 -14
@@ -0,0 +1,44 @@
1
+ import { DBFieldAttribute, DBTableIndex } from "./type.mjs";
2
+ //#region src/db/database-index.d.ts
3
+ declare function getPortableDatabaseIdentifierKey(identifier: string): string;
4
+ /** A table-level index resolved to physical database columns. */
5
+ interface ResolvedDBTableIndex extends Omit<DBTableIndex, "fields"> {
6
+ /** Physical database column names, in index order. */
7
+ columns: readonly [string, ...string[]];
8
+ name: string;
9
+ }
10
+ interface DBTableIndexSource {
11
+ fields: Readonly<Record<string, DBFieldAttribute>>;
12
+ indexes: readonly DBTableIndex[] | undefined;
13
+ tableName: string;
14
+ }
15
+ type BoundedDatabaseIndexDialect = "mssql" | "mysql";
16
+ /** Returns the stable database name for a table-level index. */
17
+ declare function getDatabaseIndexName(tableName: string, index: DBTableIndex): string;
18
+ /** Returns the database name used by legacy field-level index metadata. */
19
+ declare function getDatabaseFieldIndexName(tableName: string, columnName: string, unique: boolean): string;
20
+ /** Resolves logical index fields to their configured database column names. */
21
+ declare function resolveDatabaseTableIndexes({ fields, indexes, tableName }: {
22
+ fields: Readonly<Record<string, DBFieldAttribute>>;
23
+ indexes: readonly DBTableIndex[] | undefined;
24
+ tableName: string;
25
+ }): readonly ResolvedDBTableIndex[];
26
+ /**
27
+ * Returns a safe generated string length for a column across all of its table
28
+ * indexes in byte-limited SQL dialects.
29
+ */
30
+ declare function getDatabaseIndexStringLength({ columnName, dialect, fields, indexes }: {
31
+ columnName: string;
32
+ dialect: BoundedDatabaseIndexDialect;
33
+ fields: Readonly<Record<string, DBFieldAttribute>>;
34
+ indexes: readonly ResolvedDBTableIndex[];
35
+ }): number | undefined;
36
+ /**
37
+ * Resolves and validates every table index as one portable database schema.
38
+ *
39
+ * Index names are schema-wide because SQLite and PostgreSQL do not scope them
40
+ * to an individual table.
41
+ */
42
+ declare function resolveDatabaseSchemaIndexes(sources: readonly DBTableIndexSource[]): ReadonlyMap<string, readonly ResolvedDBTableIndex[]>;
43
+ //#endregion
44
+ export { BoundedDatabaseIndexDialect, DBTableIndexSource, ResolvedDBTableIndex, getDatabaseFieldIndexName, getDatabaseIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes, resolveDatabaseTableIndexes };
@@ -0,0 +1,168 @@
1
+ import { BetterAuthError } from "../error/index.mjs";
2
+ //#region src/db/database-index.ts
3
+ const MAX_DATABASE_INDEX_NAME_BYTES = 63;
4
+ const MAX_DATABASE_INDEX_FIELDS = 16;
5
+ function getPortableDatabaseIdentifierKey(identifier) {
6
+ return identifier.toLowerCase();
7
+ }
8
+ function getUtf8ByteLength(value) {
9
+ return new TextEncoder().encode(value).length;
10
+ }
11
+ function truncateUtf8(value, maxBytes) {
12
+ let result = "";
13
+ let byteLength = 0;
14
+ for (const character of value) {
15
+ const characterByteLength = getUtf8ByteLength(character);
16
+ if (byteLength + characterByteLength > maxBytes) break;
17
+ result += character;
18
+ byteLength += characterByteLength;
19
+ }
20
+ return result;
21
+ }
22
+ function getStableIndexNameHash(value) {
23
+ let hash = 2166136261;
24
+ for (let index = 0; index < value.length; index++) {
25
+ hash ^= value.charCodeAt(index);
26
+ hash = Math.imul(hash, 16777619);
27
+ }
28
+ return (hash >>> 0).toString(16).padStart(8, "0");
29
+ }
30
+ /** Returns the stable database name for a table-level index. */
31
+ function getDatabaseIndexName(tableName, index) {
32
+ if (index.name !== void 0) {
33
+ if (index.name.trim().length === 0) throw new BetterAuthError("Database index names must contain at least one visible character.");
34
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(index.name)) throw new BetterAuthError("Database index names must start with a letter or underscore and contain only letters, numbers, and underscores.");
35
+ if (getUtf8ByteLength(index.name) > MAX_DATABASE_INDEX_NAME_BYTES) throw new BetterAuthError(`Database index names must be at most ${MAX_DATABASE_INDEX_NAME_BYTES} UTF-8 bytes.`);
36
+ return index.name;
37
+ }
38
+ const indexKind = index.unique ? "uidx" : "idx";
39
+ const generatedName = `${tableName}_${index.fields.join("_")}_${indexKind}`;
40
+ if (getUtf8ByteLength(generatedName) <= MAX_DATABASE_INDEX_NAME_BYTES) return generatedName;
41
+ const suffix = `_${getStableIndexNameHash(generatedName)}_${indexKind}`;
42
+ return `${truncateUtf8(generatedName.slice(0, -indexKind.length - 1), MAX_DATABASE_INDEX_NAME_BYTES - getUtf8ByteLength(suffix))}${suffix}`;
43
+ }
44
+ /** Returns the database name used by legacy field-level index metadata. */
45
+ function getDatabaseFieldIndexName(tableName, columnName, unique) {
46
+ return getDatabaseIndexName(tableName, {
47
+ fields: [columnName],
48
+ unique
49
+ });
50
+ }
51
+ /** Resolves logical index fields to their configured database column names. */
52
+ function resolveDatabaseTableIndexes({ fields, indexes, tableName }) {
53
+ const resolvedIndexes = (indexes ?? []).map((index) => {
54
+ if (index.fields.length === 0) throw new BetterAuthError(`Index on table "${tableName}" must include at least one field.`);
55
+ if (index.fields.length > MAX_DATABASE_INDEX_FIELDS) throw new BetterAuthError(`Index on table "${tableName}" can include at most ${MAX_DATABASE_INDEX_FIELDS} fields so it works across supported databases.`);
56
+ if (new Set(index.fields).size !== index.fields.length) throw new BetterAuthError(`Index on table "${tableName}" contains the same field more than once.`);
57
+ if (index.unique && index.fields.some((fieldName) => fields[fieldName]?.required === false)) throw new BetterAuthError(`Unique index on table "${tableName}" can only include required fields so its behavior is consistent across databases.`);
58
+ const unsupportedField = index.fields.find((fieldName) => {
59
+ const fieldType = fields[fieldName]?.type;
60
+ return fieldType === "json" || fieldType === "string[]" || fieldType === "number[]";
61
+ });
62
+ if (unsupportedField) throw new BetterAuthError(`Index on table "${tableName}" references field "${unsupportedField}", whose type is not portably indexable.`);
63
+ const resolveFieldName = (fieldName) => {
64
+ const field = fields[fieldName];
65
+ if (!field) throw new BetterAuthError(`Index on table "${tableName}" references unknown field "${fieldName}".`);
66
+ return field.fieldName || fieldName;
67
+ };
68
+ const [firstField, ...remainingFields] = index.fields;
69
+ const columns = [resolveFieldName(firstField), ...remainingFields.map(resolveFieldName)];
70
+ if (new Set(columns.map(getPortableDatabaseIdentifierKey)).size !== columns.length) throw new BetterAuthError(`Index on table "${tableName}" resolves more than one field to the same database column.`);
71
+ return {
72
+ columns,
73
+ name: getDatabaseIndexName(tableName, {
74
+ ...index,
75
+ fields: columns
76
+ }),
77
+ unique: index.unique
78
+ };
79
+ });
80
+ const definitionsByName = /* @__PURE__ */ new Map();
81
+ const deduplicatedIndexes = [];
82
+ for (const index of resolvedIndexes) {
83
+ const definition = JSON.stringify([index.columns, index.unique ?? false]);
84
+ const identifierKey = getPortableDatabaseIdentifierKey(index.name);
85
+ const existingDefinition = definitionsByName.get(identifierKey);
86
+ if (existingDefinition && existingDefinition !== definition) throw new BetterAuthError(`Database index name "${index.name}" identifies more than one index on table "${tableName}".`);
87
+ if (existingDefinition) continue;
88
+ definitionsByName.set(identifierKey, definition);
89
+ deduplicatedIndexes.push(index);
90
+ }
91
+ return deduplicatedIndexes;
92
+ }
93
+ /**
94
+ * Returns a safe generated string length for a column across all of its table
95
+ * indexes in byte-limited SQL dialects.
96
+ */
97
+ function getDatabaseIndexStringLength({ columnName, dialect, fields, indexes }) {
98
+ const fieldsByColumn = new Map(Object.entries(fields).map(([fieldName, field]) => [field.fieldName || fieldName, field]));
99
+ const containingIndexes = indexes.filter((index) => index.columns.includes(columnName));
100
+ if (containingIndexes.length === 0) return void 0;
101
+ const byteBudget = dialect === "mysql" ? 3072 : 1700;
102
+ const bytesPerCharacter = dialect === "mysql" ? 4 : 1;
103
+ const defaultLength = dialect === "mysql" ? 191 : 255;
104
+ return containingIndexes.reduce((length, index) => {
105
+ const stringColumnCount = index.columns.filter((column) => {
106
+ const type = fieldsByColumn.get(column)?.type;
107
+ return type === "string" || Array.isArray(type);
108
+ }).length;
109
+ if (stringColumnCount === 0) return length;
110
+ const nonStringColumnBytes = (index.columns.length - stringColumnCount) * 16;
111
+ const safeLength = Math.floor(Math.max(1, byteBudget - nonStringColumnBytes) / bytesPerCharacter / stringColumnCount);
112
+ return Math.min(length, safeLength);
113
+ }, defaultLength);
114
+ }
115
+ /**
116
+ * Resolves and validates every table index as one portable database schema.
117
+ *
118
+ * Index names are schema-wide because SQLite and PostgreSQL do not scope them
119
+ * to an individual table.
120
+ */
121
+ function resolveDatabaseSchemaIndexes(sources) {
122
+ const mergedSourcesByTable = /* @__PURE__ */ new Map();
123
+ for (const source of sources) {
124
+ const existingSource = mergedSourcesByTable.get(source.tableName);
125
+ if (existingSource) {
126
+ if (existingSource.indexes.length > 0 || (source.indexes?.length ?? 0) > 0) throw new BetterAuthError(`Database schema resolves more than one indexed logical table to "${source.tableName}". Define table-level indexes through one logical schema key instead of aliasing multiple keys to the same database table.`);
127
+ Object.assign(existingSource.fields, source.fields);
128
+ continue;
129
+ }
130
+ const mergedSource = {
131
+ fields: {},
132
+ indexes: [],
133
+ tableName: source.tableName
134
+ };
135
+ Object.assign(mergedSource.fields, source.fields);
136
+ mergedSource.indexes.push(...source.indexes ?? []);
137
+ mergedSourcesByTable.set(source.tableName, mergedSource);
138
+ }
139
+ const indexesByTable = /* @__PURE__ */ new Map();
140
+ const indexOwnerByName = /* @__PURE__ */ new Map();
141
+ const tableNamesByIdentifier = new Map([...mergedSourcesByTable.keys()].map((tableName) => [getPortableDatabaseIdentifierKey(tableName), tableName]));
142
+ const fieldIndexOwnerByName = /* @__PURE__ */ new Map();
143
+ for (const source of mergedSourcesByTable.values()) for (const [fieldName, field] of Object.entries(source.fields)) {
144
+ if (!field.index && !field.unique) continue;
145
+ const indexName = getDatabaseFieldIndexName(source.tableName, field.fieldName || fieldName, field.unique ?? false);
146
+ const identifierKey = getPortableDatabaseIdentifierKey(indexName);
147
+ if (tableNamesByIdentifier.has(identifierKey)) throw new BetterAuthError(`Database index name "${indexName}" conflicts with a table name. Index and table names must be unique across the schema.`);
148
+ const existingOwner = fieldIndexOwnerByName.get(identifierKey);
149
+ if (existingOwner) throw new BetterAuthError(`Database field-level index name "${indexName}" is used by both table "${existingOwner}" and table "${source.tableName}".`);
150
+ fieldIndexOwnerByName.set(identifierKey, source.tableName);
151
+ }
152
+ for (const source of mergedSourcesByTable.values()) {
153
+ const resolvedIndexes = resolveDatabaseTableIndexes(source);
154
+ indexesByTable.set(source.tableName, [...resolvedIndexes]);
155
+ for (const index of resolvedIndexes) {
156
+ const identifierKey = getPortableDatabaseIdentifierKey(index.name);
157
+ if (tableNamesByIdentifier.has(identifierKey)) throw new BetterAuthError(`Database index name "${index.name}" conflicts with a table name. Index and table names must be unique across the schema.`);
158
+ const fieldIndexOwner = fieldIndexOwnerByName.get(identifierKey);
159
+ if (fieldIndexOwner) throw new BetterAuthError(`Database index name "${index.name}" is already reserved by field-level index metadata on table "${fieldIndexOwner}". Remove the duplicate table-level index or give it a distinct name.`);
160
+ const indexOwner = indexOwnerByName.get(identifierKey);
161
+ if (indexOwner && indexOwner !== source.tableName) throw new BetterAuthError(`Database index name "${index.name}" is used by both table "${indexOwner}" and table "${source.tableName}". Index names must be unique across the schema.`);
162
+ indexOwnerByName.set(identifierKey, source.tableName);
163
+ }
164
+ }
165
+ return indexesByTable;
166
+ }
167
+ //#endregion
168
+ export { getDatabaseFieldIndexName, getDatabaseIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes, resolveDatabaseTableIndexes };
@@ -1,6 +1,12 @@
1
1
  import { BetterAuthDBSchema } from "./type.mjs";
2
+ import { ResolvedDBTableIndex } from "./database-index.mjs";
2
3
  import { BetterAuthOptions } from "../types/init-options.mjs";
4
+ import "../types/index.mjs";
3
5
  //#region src/db/get-tables.d.ts
6
+ declare function getAuthTablesWithResolvedIndexes(options: BetterAuthOptions): {
7
+ indexesByTable: ReadonlyMap<string, readonly ResolvedDBTableIndex[]>;
8
+ tables: BetterAuthDBSchema;
9
+ };
4
10
  declare const getAuthTables: (options: BetterAuthOptions) => BetterAuthDBSchema;
5
11
  //#endregion
6
- export { getAuthTables };
12
+ export { getAuthTables, getAuthTablesWithResolvedIndexes };
@@ -1,5 +1,21 @@
1
+ import { resolveDatabaseSchemaIndexes } from "./database-index.mjs";
1
2
  //#region src/db/get-tables.ts
2
- const getAuthTables = (options) => {
3
+ function mergeTableIndexes(...indexCollections) {
4
+ const indexes = [];
5
+ const seenIndexDefinitions = /* @__PURE__ */ new Set();
6
+ for (const index of indexCollections.flatMap((collection) => collection ?? [])) {
7
+ const definition = JSON.stringify([
8
+ index.name ?? null,
9
+ index.fields,
10
+ index.unique ?? false
11
+ ]);
12
+ if (seenIndexDefinitions.has(definition)) continue;
13
+ seenIndexDefinitions.add(definition);
14
+ indexes.push(index);
15
+ }
16
+ return indexes;
17
+ }
18
+ const buildAuthTables = (options) => {
3
19
  const pluginSchema = (options.plugins ?? []).reduce((acc, plugin) => {
4
20
  const schema = plugin.schema;
5
21
  if (!schema) return acc;
@@ -8,6 +24,7 @@ const getAuthTables = (options) => {
8
24
  ...acc[key]?.fields,
9
25
  ...value.fields
10
26
  },
27
+ indexes: mergeTableIndexes(acc[key]?.indexes, value.indexes),
11
28
  modelName: value.modelName || key,
12
29
  disableMigrations: value.disableMigration ?? acc[key]?.disableMigrations
13
30
  };
@@ -40,6 +57,7 @@ const getAuthTables = (options) => {
40
57
  const { user, session, account, verification, ...pluginTables } = pluginSchema;
41
58
  const verificationTable = { verification: {
42
59
  modelName: options.verification?.modelName || "verification",
60
+ indexes: verification?.indexes,
43
61
  fields: {
44
62
  identifier: {
45
63
  type: "string",
@@ -77,6 +95,7 @@ const getAuthTables = (options) => {
77
95
  } };
78
96
  const sessionTable = { session: {
79
97
  modelName: options.session?.modelName || "session",
98
+ indexes: session?.indexes,
80
99
  fields: {
81
100
  expiresAt: {
82
101
  type: "date",
@@ -115,7 +134,7 @@ const getAuthTables = (options) => {
115
134
  type: "string",
116
135
  fieldName: options.session?.fields?.userId || "userId",
117
136
  references: {
118
- model: options.user?.modelName || "user",
137
+ model: "user",
119
138
  field: "id",
120
139
  onDelete: "cascade"
121
140
  },
@@ -130,6 +149,7 @@ const getAuthTables = (options) => {
130
149
  return {
131
150
  user: {
132
151
  modelName: options.user?.modelName || "user",
152
+ indexes: user?.indexes,
133
153
  fields: {
134
154
  name: {
135
155
  type: "string",
@@ -177,11 +197,20 @@ const getAuthTables = (options) => {
177
197
  ...!options.secondaryStorage || options.session?.storeSessionInDatabase ? sessionTable : {},
178
198
  account: {
179
199
  modelName: options.account?.modelName || "account",
200
+ indexes: mergeTableIndexes([{
201
+ fields: ["issuer", "providerAccountId"],
202
+ unique: true
203
+ }], account?.indexes),
180
204
  fields: {
181
- accountId: {
205
+ issuer: {
206
+ type: "string",
207
+ required: true,
208
+ fieldName: options.account?.fields?.issuer || "issuer"
209
+ },
210
+ providerAccountId: {
182
211
  type: "string",
183
212
  required: true,
184
- fieldName: options.account?.fields?.accountId || "accountId"
213
+ fieldName: options.account?.fields?.providerAccountId || "providerAccountId"
185
214
  },
186
215
  providerId: {
187
216
  type: "string",
@@ -191,7 +220,7 @@ const getAuthTables = (options) => {
191
220
  userId: {
192
221
  type: "string",
193
222
  references: {
194
- model: options.user?.modelName || "user",
223
+ model: "user",
195
224
  field: "id",
196
225
  onDelete: "cascade"
197
226
  },
@@ -262,5 +291,17 @@ const getAuthTables = (options) => {
262
291
  ...shouldAddRateLimitTable ? rateLimitTable : {}
263
292
  };
264
293
  };
294
+ function getAuthTablesWithResolvedIndexes(options) {
295
+ const tables = buildAuthTables(options);
296
+ return {
297
+ indexesByTable: resolveDatabaseSchemaIndexes(Object.values(tables).filter((table) => !("disableMigrations" in table) || !table.disableMigrations).map((table) => ({
298
+ fields: table.fields,
299
+ indexes: "indexes" in table ? table.indexes : void 0,
300
+ tableName: table.modelName
301
+ }))),
302
+ tables
303
+ };
304
+ }
305
+ const getAuthTables = (options) => getAuthTablesWithResolvedIndexes(options).tables;
265
306
  //#endregion
266
- export { getAuthTables };
307
+ export { getAuthTables, getAuthTablesWithResolvedIndexes };
@@ -1,10 +1,10 @@
1
- import { BaseModelNames, BetterAuthDBSchema, DBFieldAttribute, DBFieldAttributeConfig, DBFieldType, DBPrimitive, InferDBFieldInput, InferDBFieldOutput, InferDBFieldsFromOptions, InferDBFieldsFromOptionsInput, InferDBFieldsFromPlugins, InferDBFieldsFromPluginsInput, InferDBFieldsInput, InferDBFieldsOutput, InferDBValueType, ModelNames, SecondaryStorage } from "./type.mjs";
1
+ import { BaseModelNames, BetterAuthDBSchema, DBFieldAttribute, DBFieldAttributeConfig, DBFieldType, DBPrimitive, DBTableIndex, InferDBFieldInput, InferDBFieldOutput, InferDBFieldsFromOptions, InferDBFieldsFromOptionsInput, InferDBFieldsFromPlugins, InferDBFieldsFromPluginsInput, InferDBFieldsInput, InferDBFieldsOutput, InferDBValueType, ModelNames, SecondaryStorage } from "./type.mjs";
2
2
  import { getAuthTables } from "./get-tables.mjs";
3
3
  import { BetterAuthPluginDBSchema } from "./plugin.mjs";
4
4
  import { BaseRateLimit, RateLimit, rateLimitSchema } from "./schema/rate-limit.mjs";
5
5
  import { BaseSession, Session, sessionSchema } from "./schema/session.mjs";
6
6
  import { BaseUser, User, userSchema } from "./schema/user.mjs";
7
7
  import { BaseVerification, Verification, verificationSchema } from "./schema/verification.mjs";
8
- import { Account, BaseAccount, accountSchema } from "./schema/account.mjs";
8
+ import { Account, AccountKey, BaseAccount, accountSchema, createLocalAccountIssuer, createOAuthAccountIssuer } from "./schema/account.mjs";
9
9
  import { coreSchema } from "./schema/shared.mjs";
10
- export { type Account, type BaseAccount, type BaseModelNames, type BaseRateLimit, type BaseSession, type BaseUser, type BaseVerification, type BetterAuthDBSchema, type BetterAuthPluginDBSchema, type DBFieldAttribute, type DBFieldAttributeConfig, type DBFieldType, type DBPrimitive, type InferDBFieldInput, type InferDBFieldOutput, type InferDBFieldsFromOptions, type InferDBFieldsFromOptionsInput, type InferDBFieldsFromPlugins, type InferDBFieldsFromPluginsInput, type InferDBFieldsInput, type InferDBFieldsOutput, type InferDBValueType, type ModelNames, type RateLimit, type SecondaryStorage, type Session, type User, type Verification, accountSchema, coreSchema, getAuthTables, rateLimitSchema, sessionSchema, userSchema, verificationSchema };
10
+ export { type Account, type AccountKey, type BaseAccount, type BaseModelNames, type BaseRateLimit, type BaseSession, type BaseUser, type BaseVerification, type BetterAuthDBSchema, type BetterAuthPluginDBSchema, type DBFieldAttribute, type DBFieldAttributeConfig, type DBFieldType, type DBPrimitive, type DBTableIndex, type InferDBFieldInput, type InferDBFieldOutput, type InferDBFieldsFromOptions, type InferDBFieldsFromOptionsInput, type InferDBFieldsFromPlugins, type InferDBFieldsFromPluginsInput, type InferDBFieldsInput, type InferDBFieldsOutput, type InferDBValueType, type ModelNames, type RateLimit, type SecondaryStorage, type Session, type User, type Verification, accountSchema, coreSchema, createLocalAccountIssuer, createOAuthAccountIssuer, getAuthTables, rateLimitSchema, sessionSchema, userSchema, verificationSchema };
package/dist/db/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { getAuthTables } from "./get-tables.mjs";
2
2
  import { coreSchema } from "./schema/shared.mjs";
3
- import { accountSchema } from "./schema/account.mjs";
3
+ import { accountSchema, createLocalAccountIssuer, createOAuthAccountIssuer } from "./schema/account.mjs";
4
4
  import { rateLimitSchema } from "./schema/rate-limit.mjs";
5
5
  import { sessionSchema } from "./schema/session.mjs";
6
6
  import { userSchema } from "./schema/user.mjs";
7
7
  import { verificationSchema } from "./schema/verification.mjs";
8
- export { accountSchema, coreSchema, getAuthTables, rateLimitSchema, sessionSchema, userSchema, verificationSchema };
8
+ export { accountSchema, coreSchema, createLocalAccountIssuer, createOAuthAccountIssuer, getAuthTables, rateLimitSchema, sessionSchema, userSchema, verificationSchema };
@@ -0,0 +1,3 @@
1
+ import { BoundedDatabaseIndexDialect, DBTableIndexSource, ResolvedDBTableIndex, getDatabaseFieldIndexName, getDatabaseIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes, resolveDatabaseTableIndexes } from "./database-index.mjs";
2
+ import { getAuthTablesWithResolvedIndexes } from "./get-tables.mjs";
3
+ export { type BoundedDatabaseIndexDialect, type DBTableIndexSource, type ResolvedDBTableIndex, getAuthTablesWithResolvedIndexes, getDatabaseFieldIndexName, getDatabaseIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes, resolveDatabaseTableIndexes };
@@ -0,0 +1,3 @@
1
+ import { getDatabaseFieldIndexName, getDatabaseIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes, resolveDatabaseTableIndexes } from "./database-index.mjs";
2
+ import { getAuthTablesWithResolvedIndexes } from "./get-tables.mjs";
3
+ export { getAuthTablesWithResolvedIndexes, getDatabaseFieldIndexName, getDatabaseIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes, resolveDatabaseTableIndexes };
@@ -1,12 +1,13 @@
1
- import { DBFieldAttribute } from "./type.mjs";
2
-
1
+ import { DBFieldAttribute, DBTableIndex } from "./type.mjs";
3
2
  //#region src/db/plugin.d.ts
4
3
  type BetterAuthPluginDBSchema = { [table in string]: {
5
4
  fields: {
6
5
  [field: string]: DBFieldAttribute;
7
6
  };
7
+ /** Table-level indexes, including compound indexes. */
8
+ indexes?: readonly DBTableIndex[] | undefined;
8
9
  disableMigration?: boolean | undefined;
9
10
  modelName?: string | undefined;
10
- } };
11
+ }; };
11
12
  //#endregion
12
13
  export { BetterAuthPluginDBSchema };
@@ -1,15 +1,16 @@
1
1
  import { InferDBFieldsFromOptions, InferDBFieldsFromPlugins } from "../type.mjs";
2
2
  import { Prettify } from "../../types/helper.mjs";
3
3
  import { BetterAuthOptions } from "../../types/init-options.mjs";
4
+ import "../../types/index.mjs";
4
5
  import * as z from "zod";
5
-
6
6
  //#region src/db/schema/account.d.ts
7
7
  declare const accountSchema: z.ZodObject<{
8
8
  id: z.ZodString;
9
9
  createdAt: z.ZodDefault<z.ZodDate>;
10
10
  updatedAt: z.ZodDefault<z.ZodDate>;
11
11
  providerId: z.ZodString;
12
- accountId: z.ZodString;
12
+ issuer: z.ZodString;
13
+ providerAccountId: z.ZodString;
13
14
  userId: z.ZodCoercedString<unknown>;
14
15
  accessToken: z.ZodOptional<z.ZodNullable<z.ZodString>>;
15
16
  refreshToken: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -20,9 +21,21 @@ declare const accountSchema: z.ZodObject<{
20
21
  password: z.ZodOptional<z.ZodNullable<z.ZodString>>;
21
22
  }, z.core.$strip>;
22
23
  type BaseAccount = z.infer<typeof accountSchema>;
24
+ /** The stable provider-side key used to recognize an account. */
25
+ type AccountKey = Readonly<Pick<BaseAccount, "issuer" | "providerAccountId">>;
26
+ /**
27
+ * Creates the synthetic issuer used by providers without an issuer of their own.
28
+ */
29
+ declare function createLocalAccountIssuer(providerId: string): string;
30
+ /**
31
+ * Creates the synthetic issuer used by OAuth providers without an issuer of
32
+ * their own. OAuth identities use a distinct namespace so a provider ID
33
+ * cannot collide with an internal local authentication method.
34
+ */
35
+ declare function createOAuthAccountIssuer(providerId: string): string;
23
36
  /**
24
37
  * Account schema type used by better-auth, note that it's possible that account could have additional fields
25
38
  */
26
39
  type Account<DBOptions extends BetterAuthOptions["account"] = BetterAuthOptions["account"], Plugins extends BetterAuthOptions["plugins"] = BetterAuthOptions["plugins"]> = Prettify<BaseAccount & InferDBFieldsFromOptions<DBOptions> & InferDBFieldsFromPlugins<"account", Plugins>>;
27
40
  //#endregion
28
- export { Account, BaseAccount, accountSchema };
41
+ export { Account, AccountKey, BaseAccount, accountSchema, createLocalAccountIssuer, createOAuthAccountIssuer };
@@ -3,15 +3,48 @@ import * as z from "zod";
3
3
  //#region src/db/schema/account.ts
4
4
  const accountSchema = coreSchema.extend({
5
5
  providerId: z.string(),
6
- accountId: z.string(),
6
+ issuer: z.string(),
7
+ providerAccountId: z.string(),
7
8
  userId: z.coerce.string(),
8
9
  accessToken: z.string().nullish(),
9
10
  refreshToken: z.string().nullish(),
10
11
  idToken: z.string().nullish(),
12
+ /**
13
+ * Access token expires at
14
+ */
11
15
  accessTokenExpiresAt: z.date().nullish(),
16
+ /**
17
+ * Refresh token expires at
18
+ */
12
19
  refreshTokenExpiresAt: z.date().nullish(),
20
+ /**
21
+ * The set of OAuth scopes the user has granted to this account, stored
22
+ * as a comma-separated list. Represents the accumulated grant rather
23
+ * than the latest token's `scope` claim, since per RFC 6749 Section 1.5 a
24
+ * token's scope may be narrower than the user's grant.
25
+ */
13
26
  scope: z.string().nullish(),
27
+ /**
28
+ * Password is only stored in the credential provider
29
+ */
14
30
  password: z.string().nullish()
15
31
  });
32
+ function encodeAccountIssuerProviderId(providerId) {
33
+ return encodeURIComponent(providerId);
34
+ }
35
+ /**
36
+ * Creates the synthetic issuer used by providers without an issuer of their own.
37
+ */
38
+ function createLocalAccountIssuer(providerId) {
39
+ return `local:${encodeAccountIssuerProviderId(providerId)}`;
40
+ }
41
+ /**
42
+ * Creates the synthetic issuer used by OAuth providers without an issuer of
43
+ * their own. OAuth identities use a distinct namespace so a provider ID
44
+ * cannot collide with an internal local authentication method.
45
+ */
46
+ function createOAuthAccountIssuer(providerId) {
47
+ return `local:oauth:${encodeAccountIssuerProviderId(providerId)}`;
48
+ }
16
49
  //#endregion
17
- export { accountSchema };
50
+ export { accountSchema, createLocalAccountIssuer, createOAuthAccountIssuer };
@@ -1,8 +1,8 @@
1
1
  import { InferDBFieldsFromOptions, InferDBFieldsFromPlugins } from "../type.mjs";
2
2
  import { Prettify } from "../../types/helper.mjs";
3
3
  import { BetterAuthOptions } from "../../types/init-options.mjs";
4
+ import "../../types/index.mjs";
4
5
  import * as z from "zod";
5
-
6
6
  //#region src/db/schema/rate-limit.d.ts
7
7
  declare const rateLimitSchema: z.ZodObject<{
8
8
  key: z.ZodString;
@@ -1,8 +1,17 @@
1
1
  import * as z from "zod";
2
2
  //#region src/db/schema/rate-limit.ts
3
3
  const rateLimitSchema = z.object({
4
+ /**
5
+ * The key to use for rate limiting
6
+ */
4
7
  key: z.string(),
8
+ /**
9
+ * The number of requests made
10
+ */
5
11
  count: z.number(),
12
+ /**
13
+ * The last request time in milliseconds
14
+ */
6
15
  lastRequest: z.number()
7
16
  });
8
17
  //#endregion
@@ -1,8 +1,8 @@
1
1
  import { InferDBFieldsFromOptions, InferDBFieldsFromPlugins } from "../type.mjs";
2
2
  import { Prettify } from "../../types/helper.mjs";
3
3
  import { BetterAuthOptions } from "../../types/init-options.mjs";
4
+ import "../../types/index.mjs";
4
5
  import * as z from "zod";
5
-
6
6
  //#region src/db/schema/session.d.ts
7
7
  declare const sessionSchema: z.ZodObject<{
8
8
  id: z.ZodString;
@@ -1,5 +1,4 @@
1
1
  import * as z from "zod";
2
-
3
2
  //#region src/db/schema/shared.d.ts
4
3
  declare const coreSchema: z.ZodObject<{
5
4
  id: z.ZodString;
@@ -1,8 +1,8 @@
1
1
  import { InferDBFieldsFromOptions, InferDBFieldsFromPlugins } from "../type.mjs";
2
2
  import { Prettify } from "../../types/helper.mjs";
3
3
  import { BetterAuthOptions } from "../../types/init-options.mjs";
4
+ import "../../types/index.mjs";
4
5
  import * as z from "zod";
5
-
6
6
  //#region src/db/schema/user.d.ts
7
7
  declare const userSchema: z.ZodObject<{
8
8
  id: z.ZodString;
@@ -1,8 +1,8 @@
1
1
  import { InferDBFieldsFromOptions, InferDBFieldsFromPlugins } from "../type.mjs";
2
2
  import { Prettify } from "../../types/helper.mjs";
3
3
  import { BetterAuthOptions } from "../../types/init-options.mjs";
4
+ import "../../types/index.mjs";
4
5
  import * as z from "zod";
5
-
6
6
  //#region src/db/schema/verification.d.ts
7
7
  declare const verificationSchema: z.ZodObject<{
8
8
  id: z.ZodString;
@@ -1,15 +1,15 @@
1
1
  import { Awaitable, LiteralString, UnionToIntersection } from "../types/helper.mjs";
2
2
  import { BetterAuthOptions } from "../types/init-options.mjs";
3
+ import "../types/index.mjs";
3
4
  import { StandardSchemaV1 } from "@standard-schema/spec";
4
-
5
5
  //#region src/db/type.d.ts
6
6
  type BaseModelNames = "user" | "account" | "session" | "verification";
7
7
  type ModelNames<T extends string = LiteralString> = BaseModelNames | T | "rate-limit";
8
8
  type InferDBValueType<T extends DBFieldType> = T extends "string" ? string : T extends "number" ? number : T extends "boolean" ? boolean : T extends "date" ? Date : T extends "json" ? Record<string, any> : T extends `${infer U}[]` ? U extends "string" ? string[] : number[] : T extends Array<any> ? T[number] : never;
9
9
  type InferDBFieldOutput<T extends DBFieldAttribute> = T["returned"] extends false ? never : T["required"] extends false ? InferDBValueType<T["type"]> | undefined | null : InferDBValueType<T["type"]>;
10
10
  type InferDBFieldInput<T extends DBFieldAttribute> = InferDBValueType<T["type"]>;
11
- type InferDBFieldsInput<Field> = Field extends Record<infer Key, DBFieldAttribute> ? { [key in Key as Field[key]["required"] extends false ? never : Field[key]["defaultValue"] extends string | number | boolean | Date ? never : Field[key]["input"] extends false ? never : key]: InferDBFieldInput<Field[key]> } & { [key in Key as Field[key]["input"] extends false ? never : key]?: InferDBFieldInput<Field[key]> | undefined | null } : {};
12
- type InferDBFieldsOutput<Fields extends Record<string, DBFieldAttribute>> = Fields extends Record<infer Key, DBFieldAttribute> ? { [key in Key as Fields[key]["returned"] extends false ? never : Fields[key]["required"] extends false ? Fields[key]["defaultValue"] extends boolean | string | number | Date ? key : never : key]: InferDBFieldOutput<Fields[key]> } & { [key in Key as Fields[key]["returned"] extends false ? never : Fields[key]["required"] extends false ? Fields[key]["defaultValue"] extends boolean | string | number | Date ? never : key : never]?: InferDBFieldOutput<Fields[key]> | null } : never;
11
+ type InferDBFieldsInput<Field> = Field extends Record<infer Key, DBFieldAttribute> ? { [key in Key as Field[key]["required"] extends false ? never : Field[key]["defaultValue"] extends string | number | boolean | Date ? never : Field[key]["input"] extends false ? never : key]: InferDBFieldInput<Field[key]>; } & { [key in Key as Field[key]["input"] extends false ? never : key]?: InferDBFieldInput<Field[key]> | undefined | null; } : {};
12
+ type InferDBFieldsOutput<Fields extends Record<string, DBFieldAttribute>> = Fields extends Record<infer Key, DBFieldAttribute> ? { [key in Key as Fields[key]["returned"] extends false ? never : Fields[key]["required"] extends false ? Fields[key]["defaultValue"] extends boolean | string | number | Date ? key : never : key]: InferDBFieldOutput<Fields[key]>; } & { [key in Key as Fields[key]["returned"] extends false ? never : Fields[key]["required"] extends false ? Fields[key]["defaultValue"] extends boolean | string | number | Date ? never : key : never]?: InferDBFieldOutput<Fields[key]> | null; } : never;
13
13
  type InferDBFieldsFromOptionsInput<DBOptions extends BetterAuthOptions["session"] | BetterAuthOptions["user"] | BetterAuthOptions["verification"] | BetterAuthOptions["account"] | BetterAuthOptions["rateLimit"]> = DBOptions extends {
14
14
  additionalFields: Record<string, DBFieldAttribute>;
15
15
  } ? InferDBFieldsInput<DBOptions["additionalFields"]> : {};
@@ -19,12 +19,12 @@ type InferDBFieldsFromOptions<DBOptions extends BetterAuthOptions["session"] | B
19
19
  type InferDBFieldsFromPluginsInput<ModelName extends string, Plugins extends unknown[] | undefined> = Plugins extends [] ? {} : Plugins extends [infer P, ...infer Rest] ? P extends {
20
20
  schema: { [key in ModelName]: {
21
21
  fields: infer Fields;
22
- } };
22
+ }; };
23
23
  } ? Fields extends Record<string, DBFieldAttribute> ? UnionToIntersection<InferDBFieldsInput<Fields> & InferDBFieldsFromPluginsInput<ModelName, Rest>> : InferDBFieldsFromPluginsInput<ModelName, Rest> : InferDBFieldsFromPluginsInput<ModelName, Rest> : {};
24
24
  type InferDBFieldsFromPlugins<ModelName extends string, Plugins extends unknown[] | undefined> = Plugins extends [] ? {} : Plugins extends [infer P, ...infer Rest] ? P extends {
25
25
  schema: { [key in ModelName]: {
26
26
  fields: infer Fields;
27
- } };
27
+ }; };
28
28
  } ? Fields extends Record<string, DBFieldAttribute> ? UnionToIntersection<InferDBFieldsOutput<Fields> & InferDBFieldsFromPlugins<ModelName, Rest>> : InferDBFieldsFromPlugins<ModelName, Rest> : InferDBFieldsFromPlugins<ModelName, Rest> : {};
29
29
  type DBFieldType = "string" | "number" | "boolean" | "date" | "json" | `${"string" | "number"}[]` | Array<LiteralString>;
30
30
  type DBPrimitive = string | number | boolean | Date | null | undefined | string[] | number[] | (Record<string, unknown> | unknown[]);
@@ -115,6 +115,15 @@ type DBFieldAttributeConfig = {
115
115
  type DBFieldAttribute<T extends DBFieldType = DBFieldType> = {
116
116
  type: T;
117
117
  } & DBFieldAttributeConfig;
118
+ /** A database index spanning one or more logical schema fields. */
119
+ interface DBTableIndex {
120
+ /** One to sixteen logical field names included in the index, in index order. */
121
+ fields: readonly [string, ...string[]];
122
+ /** Portable database index name of at most 63 UTF-8 bytes. */
123
+ name?: string | undefined;
124
+ /** Whether the indexed field tuple must be unique. */
125
+ unique?: boolean | undefined;
126
+ }
118
127
  type BetterAuthDBSchema = Record<string, {
119
128
  /**
120
129
  * The name of the table in the database
@@ -124,6 +133,8 @@ type BetterAuthDBSchema = Record<string, {
124
133
  * The fields of the table
125
134
  */
126
135
  fields: Record<string, DBFieldAttribute>;
136
+ /** Table-level indexes, including compound indexes. */
137
+ indexes?: readonly DBTableIndex[] | undefined;
127
138
  /**
128
139
  * Whether to disable migrations for this table
129
140
  * @default false
@@ -162,17 +173,14 @@ interface SecondaryStorage {
162
173
  /**
163
174
  * Key to store
164
175
  */
165
-
166
176
  key: string,
167
177
  /**
168
178
  * Value to store
169
179
  */
170
-
171
180
  value: string,
172
181
  /**
173
182
  * Time to live in seconds
174
183
  */
175
-
176
184
  ttl?: number | undefined) => Awaitable<void | null | unknown>;
177
185
  /**
178
186
  *
@@ -181,4 +189,4 @@ interface SecondaryStorage {
181
189
  delete: (key: string) => Awaitable<void | null | string>;
182
190
  }
183
191
  //#endregion
184
- export { BaseModelNames, BetterAuthDBSchema, DBFieldAttribute, DBFieldAttributeConfig, DBFieldType, DBPrimitive, InferDBFieldInput, InferDBFieldOutput, InferDBFieldsFromOptions, InferDBFieldsFromOptionsInput, InferDBFieldsFromPlugins, InferDBFieldsFromPluginsInput, InferDBFieldsInput, InferDBFieldsOutput, InferDBValueType, ModelNames, SecondaryStorage };
192
+ export { BaseModelNames, BetterAuthDBSchema, DBFieldAttribute, DBFieldAttributeConfig, DBFieldType, DBPrimitive, DBTableIndex, InferDBFieldInput, InferDBFieldOutput, InferDBFieldsFromOptions, InferDBFieldsFromOptionsInput, InferDBFieldsFromPlugins, InferDBFieldsFromPluginsInput, InferDBFieldsInput, InferDBFieldsOutput, InferDBValueType, ModelNames, SecondaryStorage };