@better-auth/core 1.7.0-rc.1 → 1.7.0-rc.3
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.
- package/dist/api/index.d.mts +28 -28
- package/dist/async_hooks/index.d.mts +0 -1
- package/dist/async_hooks/pure.index.d.mts +0 -1
- package/dist/context/endpoint-context.d.mts +1 -1
- package/dist/context/global.d.mts +0 -1
- package/dist/context/global.mjs +1 -1
- package/dist/context/request-state.d.mts +0 -1
- package/dist/context/request-state.mjs +8 -5
- package/dist/context/transaction.d.mts +8 -3
- package/dist/context/transaction.mjs +27 -10
- package/dist/db/adapter/factory.d.mts +3 -5
- package/dist/db/adapter/factory.mjs +7 -5
- package/dist/db/adapter/get-default-field-name.d.mts +2 -9
- package/dist/db/adapter/get-default-model-name.d.mts +1 -5
- package/dist/db/adapter/get-default-model-name.mjs +6 -5
- package/dist/db/adapter/get-field-attributes.d.mts +4 -11
- package/dist/db/adapter/get-field-name.d.mts +2 -9
- package/dist/db/adapter/get-id-field.d.mts +4 -12
- package/dist/db/adapter/get-model-name.d.mts +1 -5
- package/dist/db/adapter/index.d.mts +7 -32
- package/dist/db/adapter/types.d.mts +5 -17
- package/dist/db/adapter/utils.d.mts +0 -1
- package/dist/db/database-index.d.mts +44 -0
- package/dist/db/database-index.mjs +168 -0
- package/dist/db/get-tables.d.mts +7 -1
- package/dist/db/get-tables.mjs +47 -6
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/internal.d.mts +3 -0
- package/dist/db/internal.mjs +3 -0
- package/dist/db/plugin.d.mts +4 -3
- package/dist/db/schema/account.d.mts +16 -3
- package/dist/db/schema/account.mjs +35 -2
- package/dist/db/schema/rate-limit.d.mts +1 -1
- package/dist/db/schema/rate-limit.mjs +9 -0
- package/dist/db/schema/session.d.mts +1 -1
- package/dist/db/schema/shared.d.mts +0 -1
- package/dist/db/schema/user.d.mts +1 -1
- package/dist/db/schema/verification.d.mts +1 -1
- package/dist/db/type.d.mts +17 -9
- package/dist/env/logger.d.mts +25 -25
- package/dist/error/codes.d.mts +0 -1
- package/dist/error/index.d.mts +0 -1
- package/dist/index.d.mts +1 -1
- package/dist/instrumentation/attributes.d.mts +0 -1
- package/dist/instrumentation/pure.index.d.mts +0 -1
- package/dist/instrumentation/tracer.mjs +1 -1
- package/dist/oauth2/authorization-params.d.mts +0 -1
- package/dist/oauth2/client-assertion.d.mts +3 -13
- package/dist/oauth2/client-credentials-token.d.mts +3 -17
- package/dist/oauth2/client-credentials-token.mjs +1 -4
- package/dist/oauth2/create-authorization-url.d.mts +3 -21
- package/dist/oauth2/dpop.d.mts +2 -23
- package/dist/oauth2/dpop.mjs +1 -1
- package/dist/oauth2/index.d.mts +3 -3
- package/dist/oauth2/index.mjs +2 -2
- package/dist/oauth2/oauth-provider.d.mts +94 -36
- package/dist/oauth2/refresh-access-token.d.mts +3 -19
- package/dist/oauth2/refresh-access-token.mjs +3 -9
- package/dist/oauth2/reject-redirects.mjs +1 -1
- package/dist/oauth2/token-endpoint-auth.d.mts +0 -1
- package/dist/oauth2/utils.d.mts +0 -1
- package/dist/oauth2/utils.mjs +2 -3
- package/dist/oauth2/validate-authorization-code.d.mts +5 -29
- package/dist/oauth2/verify-id-token.d.mts +3 -2
- package/dist/oauth2/verify-id-token.mjs +3 -3
- package/dist/oauth2/verify.d.mts +18 -9
- package/dist/oauth2/verify.mjs +68 -5
- package/dist/social-providers/apple.d.mts +9 -24
- package/dist/social-providers/apple.mjs +2 -1
- package/dist/social-providers/atlassian.d.mts +18 -19
- package/dist/social-providers/atlassian.mjs +1 -1
- package/dist/social-providers/cognito.d.mts +33 -22
- package/dist/social-providers/cognito.mjs +2 -2
- package/dist/social-providers/discord.d.mts +7 -20
- package/dist/social-providers/discord.mjs +1 -1
- package/dist/social-providers/dropbox.d.mts +7 -22
- package/dist/social-providers/dropbox.mjs +1 -1
- package/dist/social-providers/facebook.d.mts +19 -27
- package/dist/social-providers/facebook.mjs +6 -17
- package/dist/social-providers/figma.d.mts +7 -22
- package/dist/social-providers/figma.mjs +1 -1
- package/dist/social-providers/github.d.mts +7 -23
- package/dist/social-providers/github.mjs +1 -1
- package/dist/social-providers/gitlab.d.mts +6 -43
- package/dist/social-providers/gitlab.mjs +1 -1
- package/dist/social-providers/google.d.mts +10 -32
- package/dist/social-providers/google.mjs +2 -1
- package/dist/social-providers/huggingface.d.mts +7 -22
- package/dist/social-providers/huggingface.mjs +1 -1
- package/dist/social-providers/index.d.mts +229 -787
- package/dist/social-providers/kakao.d.mts +8 -33
- package/dist/social-providers/kakao.mjs +1 -1
- package/dist/social-providers/kick.d.mts +7 -22
- package/dist/social-providers/kick.mjs +1 -1
- package/dist/social-providers/line.d.mts +8 -44
- package/dist/social-providers/line.mjs +4 -6
- package/dist/social-providers/linear.d.mts +7 -21
- package/dist/social-providers/linear.mjs +1 -1
- package/dist/social-providers/linkedin.d.mts +7 -21
- package/dist/social-providers/linkedin.mjs +1 -1
- package/dist/social-providers/microsoft-entra-id.d.mts +8 -18
- package/dist/social-providers/microsoft-entra-id.mjs +24 -4
- package/dist/social-providers/naver.d.mts +27 -42
- package/dist/social-providers/naver.mjs +1 -1
- package/dist/social-providers/notion.d.mts +7 -21
- package/dist/social-providers/notion.mjs +1 -1
- package/dist/social-providers/paybin.d.mts +8 -23
- package/dist/social-providers/paybin.mjs +2 -1
- package/dist/social-providers/paypal.d.mts +8 -33
- package/dist/social-providers/paypal.mjs +1 -1
- package/dist/social-providers/polar.d.mts +7 -22
- package/dist/social-providers/polar.mjs +1 -1
- package/dist/social-providers/railway.d.mts +7 -22
- package/dist/social-providers/railway.mjs +1 -1
- package/dist/social-providers/reddit.d.mts +7 -20
- package/dist/social-providers/reddit.mjs +1 -1
- package/dist/social-providers/roblox.d.mts +7 -20
- package/dist/social-providers/roblox.mjs +1 -1
- package/dist/social-providers/salesforce.d.mts +7 -22
- package/dist/social-providers/salesforce.mjs +1 -1
- package/dist/social-providers/slack.d.mts +7 -20
- package/dist/social-providers/slack.mjs +1 -1
- package/dist/social-providers/spotify.d.mts +7 -22
- package/dist/social-providers/spotify.mjs +1 -1
- package/dist/social-providers/tiktok.d.mts +10 -22
- package/dist/social-providers/tiktok.mjs +1 -1
- package/dist/social-providers/twitch.d.mts +7 -20
- package/dist/social-providers/twitch.mjs +1 -1
- package/dist/social-providers/twitter.d.mts +29 -26
- package/dist/social-providers/twitter.mjs +1 -1
- package/dist/social-providers/vercel.d.mts +7 -22
- package/dist/social-providers/vercel.mjs +1 -1
- package/dist/social-providers/vk.d.mts +8 -24
- package/dist/social-providers/vk.mjs +5 -6
- package/dist/social-providers/wechat.d.mts +7 -19
- package/dist/social-providers/wechat.mjs +1 -1
- package/dist/social-providers/zoom.d.mts +9 -25
- package/dist/social-providers/zoom.mjs +1 -1
- package/dist/types/context.d.mts +16 -9
- package/dist/types/cookie.d.mts +0 -1
- package/dist/types/helper.d.mts +1 -1
- package/dist/types/index.d.mts +1 -1
- package/dist/types/init-options.d.mts +37 -26
- package/dist/types/plugin-client.d.mts +1 -3
- package/dist/types/plugin.d.mts +2 -2
- package/dist/utils/async.d.mts +0 -1
- package/dist/utils/db.d.mts +1 -0
- package/dist/utils/deprecate.d.mts +0 -1
- package/dist/utils/error-codes.d.mts +2 -2
- package/dist/utils/host.d.mts +27 -1
- package/dist/utils/host.mjs +1 -1
- package/dist/utils/ip.d.mts +1 -0
- package/dist/utils/ip.mjs +1 -1
- package/dist/utils/is-api-error.d.mts +0 -1
- package/dist/utils/redirect-uri.d.mts +6 -2
- package/dist/utils/redirect-uri.mjs +11 -1
- package/package.json +10 -2
- package/src/context/request-state.ts +26 -6
- package/src/context/transaction.ts +37 -18
- package/src/db/adapter/factory.ts +34 -28
- package/src/db/adapter/get-default-model-name.ts +23 -14
- package/src/db/database-index.ts +351 -0
- package/src/db/get-tables.ts +89 -9
- package/src/db/index.ts +4 -0
- package/src/db/internal.ts +12 -0
- package/src/db/plugin.ts +3 -1
- package/src/db/schema/account.ts +27 -1
- package/src/db/schema/user.ts +1 -1
- package/src/db/type.ts +12 -0
- package/src/error/codes.ts +1 -0
- package/src/oauth2/index.ts +5 -0
- package/src/oauth2/oauth-provider.ts +105 -36
- package/src/oauth2/verify-id-token.ts +4 -2
- package/src/oauth2/verify.ts +121 -12
- package/src/social-providers/apple.ts +2 -1
- package/src/social-providers/atlassian.ts +1 -1
- package/src/social-providers/cognito.ts +2 -2
- package/src/social-providers/discord.ts +1 -1
- package/src/social-providers/dropbox.ts +1 -1
- package/src/social-providers/facebook.ts +21 -28
- package/src/social-providers/figma.ts +1 -1
- package/src/social-providers/github.ts +1 -1
- package/src/social-providers/gitlab.ts +1 -1
- package/src/social-providers/google.ts +2 -1
- package/src/social-providers/huggingface.ts +1 -1
- package/src/social-providers/kakao.ts +1 -1
- package/src/social-providers/kick.ts +1 -1
- package/src/social-providers/line.ts +7 -12
- package/src/social-providers/linear.ts +1 -1
- package/src/social-providers/linkedin.ts +1 -1
- package/src/social-providers/microsoft-entra-id.ts +36 -27
- package/src/social-providers/naver.ts +1 -1
- package/src/social-providers/notion.ts +1 -1
- package/src/social-providers/paybin.ts +2 -1
- package/src/social-providers/paypal.ts +1 -1
- package/src/social-providers/polar.ts +1 -1
- package/src/social-providers/railway.ts +1 -1
- package/src/social-providers/reddit.ts +1 -1
- package/src/social-providers/roblox.ts +1 -1
- package/src/social-providers/salesforce.ts +1 -1
- package/src/social-providers/slack.ts +1 -1
- package/src/social-providers/spotify.ts +1 -1
- package/src/social-providers/tiktok.ts +2 -2
- package/src/social-providers/twitch.ts +1 -1
- package/src/social-providers/twitter.ts +1 -1
- package/src/social-providers/vercel.ts +1 -1
- package/src/social-providers/vk.ts +2 -2
- package/src/social-providers/wechat.ts +1 -1
- package/src/social-providers/zoom.ts +1 -1
- package/src/types/context.ts +21 -14
- package/src/types/init-options.ts +15 -14
- package/src/utils/redirect-uri.ts +20 -0
|
@@ -425,19 +425,19 @@ export const createAdapterFactory =
|
|
|
425
425
|
joinConfig,
|
|
426
426
|
} of requiredModels) {
|
|
427
427
|
let joinedData = await (async () => {
|
|
428
|
-
if (options.
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
baseModel: unsafe_model,
|
|
435
|
-
baseData: transformedData,
|
|
436
|
-
joinModel: modelName,
|
|
437
|
-
specificJoinConfig: joinConfig,
|
|
438
|
-
});
|
|
439
|
-
return result;
|
|
428
|
+
if (options.advanced?.database?.joins) {
|
|
429
|
+
// Use native joined data when the adapter included the key;
|
|
430
|
+
// otherwise fall back to separate queries.
|
|
431
|
+
if (modelName in data) {
|
|
432
|
+
return data[modelName];
|
|
433
|
+
}
|
|
440
434
|
}
|
|
435
|
+
return await handleFallbackJoin({
|
|
436
|
+
baseModel: unsafe_model,
|
|
437
|
+
baseData: transformedData,
|
|
438
|
+
joinModel: modelName,
|
|
439
|
+
specificJoinConfig: joinConfig,
|
|
440
|
+
});
|
|
441
441
|
})();
|
|
442
442
|
|
|
443
443
|
// If joinedData is undefined, initialize it based on relationship type
|
|
@@ -863,8 +863,8 @@ export const createAdapterFactory =
|
|
|
863
863
|
}: {
|
|
864
864
|
model: string;
|
|
865
865
|
data: T;
|
|
866
|
-
select?: string[];
|
|
867
|
-
forceAllowId?: boolean;
|
|
866
|
+
select?: string[] | undefined;
|
|
867
|
+
forceAllowId?: boolean | undefined;
|
|
868
868
|
}): Promise<R> => {
|
|
869
869
|
transactionId++;
|
|
870
870
|
const thisTransactionId = transactionId;
|
|
@@ -1098,8 +1098,8 @@ export const createAdapterFactory =
|
|
|
1098
1098
|
}: {
|
|
1099
1099
|
model: string;
|
|
1100
1100
|
where: Where[];
|
|
1101
|
-
select?: string[];
|
|
1102
|
-
join?: JoinOption;
|
|
1101
|
+
select?: string[] | undefined;
|
|
1102
|
+
join?: JoinOption | undefined;
|
|
1103
1103
|
}) => {
|
|
1104
1104
|
transactionId++;
|
|
1105
1105
|
const thisTransactionId = transactionId;
|
|
@@ -1118,9 +1118,12 @@ export const createAdapterFactory =
|
|
|
1118
1118
|
join = result.join;
|
|
1119
1119
|
select = result.select;
|
|
1120
1120
|
}
|
|
1121
|
-
// If
|
|
1122
|
-
|
|
1123
|
-
|
|
1121
|
+
// If joins are disabled and we have joins, don't pass them to the adapter
|
|
1122
|
+
if (
|
|
1123
|
+
!options.advanced?.database?.joins &&
|
|
1124
|
+
join &&
|
|
1125
|
+
Object.keys(join).length > 0
|
|
1126
|
+
) {
|
|
1124
1127
|
passJoinToAdapter = false;
|
|
1125
1128
|
}
|
|
1126
1129
|
} else {
|
|
@@ -1178,12 +1181,12 @@ export const createAdapterFactory =
|
|
|
1178
1181
|
join: unsafeJoin,
|
|
1179
1182
|
}: {
|
|
1180
1183
|
model: string;
|
|
1181
|
-
where?: Where[];
|
|
1182
|
-
limit?: number;
|
|
1184
|
+
where?: Where[] | undefined;
|
|
1185
|
+
limit?: number | undefined;
|
|
1183
1186
|
select?: string[] | undefined;
|
|
1184
|
-
sortBy?: { field: string; direction: "asc" | "desc" };
|
|
1185
|
-
offset?: number;
|
|
1186
|
-
join?: JoinOption;
|
|
1187
|
+
sortBy?: { field: string; direction: "asc" | "desc" } | undefined;
|
|
1188
|
+
offset?: number | undefined;
|
|
1189
|
+
join?: JoinOption | undefined;
|
|
1187
1190
|
}) => {
|
|
1188
1191
|
transactionId++;
|
|
1189
1192
|
const thisTransactionId = transactionId;
|
|
@@ -1206,9 +1209,12 @@ export const createAdapterFactory =
|
|
|
1206
1209
|
join = result.join;
|
|
1207
1210
|
select = result.select;
|
|
1208
1211
|
}
|
|
1209
|
-
// If
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
+
// If joins are disabled and we have joins, don't pass them to the adapter
|
|
1213
|
+
if (
|
|
1214
|
+
!options.advanced?.database?.joins &&
|
|
1215
|
+
join &&
|
|
1216
|
+
Object.keys(join).length > 0
|
|
1217
|
+
) {
|
|
1212
1218
|
passJoinToAdapter = false;
|
|
1213
1219
|
}
|
|
1214
1220
|
} else {
|
|
@@ -1503,7 +1509,7 @@ export const createAdapterFactory =
|
|
|
1503
1509
|
where: unsafeWhere,
|
|
1504
1510
|
}: {
|
|
1505
1511
|
model: string;
|
|
1506
|
-
where?: Where[];
|
|
1512
|
+
where?: Where[] | undefined;
|
|
1507
1513
|
}) => {
|
|
1508
1514
|
transactionId++;
|
|
1509
1515
|
const thisTransactionId = transactionId;
|
|
@@ -20,26 +20,35 @@ export const initGetDefaultModelName = ({
|
|
|
20
20
|
* 3. Using this function helps us get the actual model name based on the user's defined custom modelName.
|
|
21
21
|
*/
|
|
22
22
|
const getDefaultModelName = (model: string) => {
|
|
23
|
+
// Resolve a model string (either a schema key or a user-defined
|
|
24
|
+
// `modelName`) back to its canonical schema key.
|
|
25
|
+
//
|
|
26
|
+
// An exact schema-key match must win over a `modelName` match.
|
|
27
|
+
// Better-auth internals (and `references.model`, see get-tables.ts)
|
|
28
|
+
// always pass canonical schema keys, so when a user remaps a
|
|
29
|
+
// built-in table onto another table's schema key (e.g.
|
|
30
|
+
// `user.modelName = "account"`), preferring the modelName alias
|
|
31
|
+
// would silently reroute every internal "account" query to the
|
|
32
|
+
// user table. The modelName lookup is only a fallback for
|
|
33
|
+
// externally supplied physical table names.
|
|
34
|
+
// @see https://github.com/better-auth/better-auth/issues/8111
|
|
35
|
+
// @see https://github.com/better-auth/better-auth/issues/10136
|
|
36
|
+
const resolve = (candidate: string): string | undefined => {
|
|
37
|
+
if (schema[candidate]) return candidate;
|
|
38
|
+
return Object.entries(schema).find(
|
|
39
|
+
([_, f]) => f.modelName === candidate,
|
|
40
|
+
)?.[0];
|
|
41
|
+
};
|
|
42
|
+
|
|
23
43
|
// It's possible this `model` could had applied `usePlural`.
|
|
24
44
|
// Thus we'll try the search but without the trailing `s`.
|
|
25
45
|
if (usePlural && model.charAt(model.length - 1) === "s") {
|
|
26
46
|
const pluralessModel = model.slice(0, -1);
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
m = Object.entries(schema).find(
|
|
30
|
-
([_, f]) => f.modelName === pluralessModel,
|
|
31
|
-
)?.[0];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (m) {
|
|
35
|
-
return m;
|
|
36
|
-
}
|
|
47
|
+
const m = resolve(pluralessModel);
|
|
48
|
+
if (m) return m;
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
if (!m) {
|
|
41
|
-
m = Object.entries(schema).find(([_, f]) => f.modelName === model)?.[0];
|
|
42
|
-
}
|
|
51
|
+
const m = resolve(model);
|
|
43
52
|
|
|
44
53
|
if (!m) {
|
|
45
54
|
throw new BetterAuthError(`Model "${model}" not found in schema`);
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { BetterAuthError } from "../error";
|
|
2
|
+
import type { DBFieldAttribute, DBTableIndex } from "./type";
|
|
3
|
+
|
|
4
|
+
const MAX_DATABASE_INDEX_NAME_BYTES = 63;
|
|
5
|
+
const MAX_DATABASE_INDEX_FIELDS = 16;
|
|
6
|
+
|
|
7
|
+
export function getPortableDatabaseIdentifierKey(identifier: string) {
|
|
8
|
+
return identifier.toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getUtf8ByteLength(value: string) {
|
|
12
|
+
return new TextEncoder().encode(value).length;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function truncateUtf8(value: string, maxBytes: number) {
|
|
16
|
+
let result = "";
|
|
17
|
+
let byteLength = 0;
|
|
18
|
+
for (const character of value) {
|
|
19
|
+
const characterByteLength = getUtf8ByteLength(character);
|
|
20
|
+
if (byteLength + characterByteLength > maxBytes) break;
|
|
21
|
+
result += character;
|
|
22
|
+
byteLength += characterByteLength;
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getStableIndexNameHash(value: string) {
|
|
28
|
+
let hash = 0x811c9dc5;
|
|
29
|
+
for (let index = 0; index < value.length; index++) {
|
|
30
|
+
hash ^= value.charCodeAt(index);
|
|
31
|
+
hash = Math.imul(hash, 0x01000193);
|
|
32
|
+
}
|
|
33
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A table-level index resolved to physical database columns. */
|
|
37
|
+
export interface ResolvedDBTableIndex extends Omit<DBTableIndex, "fields"> {
|
|
38
|
+
/** Physical database column names, in index order. */
|
|
39
|
+
columns: readonly [string, ...string[]];
|
|
40
|
+
name: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface DBTableIndexSource {
|
|
44
|
+
fields: Readonly<Record<string, DBFieldAttribute>>;
|
|
45
|
+
indexes: readonly DBTableIndex[] | undefined;
|
|
46
|
+
tableName: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type BoundedDatabaseIndexDialect = "mssql" | "mysql";
|
|
50
|
+
|
|
51
|
+
/** Returns the stable database name for a table-level index. */
|
|
52
|
+
export function getDatabaseIndexName(
|
|
53
|
+
tableName: string,
|
|
54
|
+
index: DBTableIndex,
|
|
55
|
+
): string {
|
|
56
|
+
if (index.name !== undefined) {
|
|
57
|
+
if (index.name.trim().length === 0) {
|
|
58
|
+
throw new BetterAuthError(
|
|
59
|
+
"Database index names must contain at least one visible character.",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(index.name)) {
|
|
63
|
+
throw new BetterAuthError(
|
|
64
|
+
"Database index names must start with a letter or underscore and contain only letters, numbers, and underscores.",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (getUtf8ByteLength(index.name) > MAX_DATABASE_INDEX_NAME_BYTES) {
|
|
68
|
+
throw new BetterAuthError(
|
|
69
|
+
`Database index names must be at most ${MAX_DATABASE_INDEX_NAME_BYTES} UTF-8 bytes.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return index.name;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const indexKind = index.unique ? "uidx" : "idx";
|
|
76
|
+
const generatedName = `${tableName}_${index.fields.join("_")}_${indexKind}`;
|
|
77
|
+
if (getUtf8ByteLength(generatedName) <= MAX_DATABASE_INDEX_NAME_BYTES) {
|
|
78
|
+
return generatedName;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const suffix = `_${getStableIndexNameHash(generatedName)}_${indexKind}`;
|
|
82
|
+
return `${truncateUtf8(
|
|
83
|
+
generatedName.slice(0, -indexKind.length - 1),
|
|
84
|
+
MAX_DATABASE_INDEX_NAME_BYTES - getUtf8ByteLength(suffix),
|
|
85
|
+
)}${suffix}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Returns the database name used by legacy field-level index metadata. */
|
|
89
|
+
export function getDatabaseFieldIndexName(
|
|
90
|
+
tableName: string,
|
|
91
|
+
columnName: string,
|
|
92
|
+
unique: boolean,
|
|
93
|
+
) {
|
|
94
|
+
return getDatabaseIndexName(tableName, {
|
|
95
|
+
fields: [columnName],
|
|
96
|
+
unique,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Resolves logical index fields to their configured database column names. */
|
|
101
|
+
export function resolveDatabaseTableIndexes({
|
|
102
|
+
fields,
|
|
103
|
+
indexes,
|
|
104
|
+
tableName,
|
|
105
|
+
}: {
|
|
106
|
+
fields: Readonly<Record<string, DBFieldAttribute>>;
|
|
107
|
+
indexes: readonly DBTableIndex[] | undefined;
|
|
108
|
+
tableName: string;
|
|
109
|
+
}): readonly ResolvedDBTableIndex[] {
|
|
110
|
+
const resolvedIndexes = (indexes ?? []).map((index) => {
|
|
111
|
+
if (index.fields.length === 0) {
|
|
112
|
+
throw new BetterAuthError(
|
|
113
|
+
`Index on table "${tableName}" must include at least one field.`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (index.fields.length > MAX_DATABASE_INDEX_FIELDS) {
|
|
117
|
+
throw new BetterAuthError(
|
|
118
|
+
`Index on table "${tableName}" can include at most ${MAX_DATABASE_INDEX_FIELDS} fields so it works across supported databases.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (new Set(index.fields).size !== index.fields.length) {
|
|
122
|
+
throw new BetterAuthError(
|
|
123
|
+
`Index on table "${tableName}" contains the same field more than once.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (
|
|
127
|
+
index.unique &&
|
|
128
|
+
index.fields.some((fieldName) => fields[fieldName]?.required === false)
|
|
129
|
+
) {
|
|
130
|
+
throw new BetterAuthError(
|
|
131
|
+
`Unique index on table "${tableName}" can only include required fields so its behavior is consistent across databases.`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const unsupportedField = index.fields.find((fieldName) => {
|
|
135
|
+
const fieldType = fields[fieldName]?.type;
|
|
136
|
+
return (
|
|
137
|
+
fieldType === "json" ||
|
|
138
|
+
fieldType === "string[]" ||
|
|
139
|
+
fieldType === "number[]"
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
if (unsupportedField) {
|
|
143
|
+
throw new BetterAuthError(
|
|
144
|
+
`Index on table "${tableName}" references field "${unsupportedField}", whose type is not portably indexable.`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const resolveFieldName = (fieldName: string) => {
|
|
148
|
+
const field = fields[fieldName];
|
|
149
|
+
if (!field) {
|
|
150
|
+
throw new BetterAuthError(
|
|
151
|
+
`Index on table "${tableName}" references unknown field "${fieldName}".`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return field.fieldName || fieldName;
|
|
155
|
+
};
|
|
156
|
+
const [firstField, ...remainingFields] = index.fields;
|
|
157
|
+
const columns = [
|
|
158
|
+
resolveFieldName(firstField),
|
|
159
|
+
...remainingFields.map(resolveFieldName),
|
|
160
|
+
] as ResolvedDBTableIndex["columns"];
|
|
161
|
+
if (
|
|
162
|
+
new Set(columns.map(getPortableDatabaseIdentifierKey)).size !==
|
|
163
|
+
columns.length
|
|
164
|
+
) {
|
|
165
|
+
throw new BetterAuthError(
|
|
166
|
+
`Index on table "${tableName}" resolves more than one field to the same database column.`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
columns,
|
|
172
|
+
name: getDatabaseIndexName(tableName, {
|
|
173
|
+
...index,
|
|
174
|
+
fields: columns,
|
|
175
|
+
}),
|
|
176
|
+
unique: index.unique,
|
|
177
|
+
} satisfies ResolvedDBTableIndex;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const definitionsByName = new Map<string, string>();
|
|
181
|
+
const deduplicatedIndexes: ResolvedDBTableIndex[] = [];
|
|
182
|
+
for (const index of resolvedIndexes) {
|
|
183
|
+
const definition = JSON.stringify([index.columns, index.unique ?? false]);
|
|
184
|
+
const identifierKey = getPortableDatabaseIdentifierKey(index.name);
|
|
185
|
+
const existingDefinition = definitionsByName.get(identifierKey);
|
|
186
|
+
if (existingDefinition && existingDefinition !== definition) {
|
|
187
|
+
throw new BetterAuthError(
|
|
188
|
+
`Database index name "${index.name}" identifies more than one index on table "${tableName}".`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (existingDefinition) continue;
|
|
192
|
+
definitionsByName.set(identifierKey, definition);
|
|
193
|
+
deduplicatedIndexes.push(index);
|
|
194
|
+
}
|
|
195
|
+
return deduplicatedIndexes;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Returns a safe generated string length for a column across all of its table
|
|
200
|
+
* indexes in byte-limited SQL dialects.
|
|
201
|
+
*/
|
|
202
|
+
export function getDatabaseIndexStringLength({
|
|
203
|
+
columnName,
|
|
204
|
+
dialect,
|
|
205
|
+
fields,
|
|
206
|
+
indexes,
|
|
207
|
+
}: {
|
|
208
|
+
columnName: string;
|
|
209
|
+
dialect: BoundedDatabaseIndexDialect;
|
|
210
|
+
fields: Readonly<Record<string, DBFieldAttribute>>;
|
|
211
|
+
indexes: readonly ResolvedDBTableIndex[];
|
|
212
|
+
}): number | undefined {
|
|
213
|
+
const fieldsByColumn = new Map(
|
|
214
|
+
Object.entries(fields).map(([fieldName, field]) => [
|
|
215
|
+
field.fieldName || fieldName,
|
|
216
|
+
field,
|
|
217
|
+
]),
|
|
218
|
+
);
|
|
219
|
+
const containingIndexes = indexes.filter((index) =>
|
|
220
|
+
index.columns.includes(columnName),
|
|
221
|
+
);
|
|
222
|
+
if (containingIndexes.length === 0) return undefined;
|
|
223
|
+
|
|
224
|
+
const byteBudget = dialect === "mysql" ? 3072 : 1700;
|
|
225
|
+
const bytesPerCharacter = dialect === "mysql" ? 4 : 1;
|
|
226
|
+
const defaultLength = dialect === "mysql" ? 191 : 255;
|
|
227
|
+
return containingIndexes.reduce((length, index) => {
|
|
228
|
+
const stringColumnCount = index.columns.filter((column) => {
|
|
229
|
+
const type = fieldsByColumn.get(column)?.type;
|
|
230
|
+
return type === "string" || Array.isArray(type);
|
|
231
|
+
}).length;
|
|
232
|
+
if (stringColumnCount === 0) return length;
|
|
233
|
+
const nonStringColumnBytes =
|
|
234
|
+
(index.columns.length - stringColumnCount) * 16;
|
|
235
|
+
const safeLength = Math.floor(
|
|
236
|
+
Math.max(1, byteBudget - nonStringColumnBytes) /
|
|
237
|
+
bytesPerCharacter /
|
|
238
|
+
stringColumnCount,
|
|
239
|
+
);
|
|
240
|
+
return Math.min(length, safeLength);
|
|
241
|
+
}, defaultLength);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Resolves and validates every table index as one portable database schema.
|
|
246
|
+
*
|
|
247
|
+
* Index names are schema-wide because SQLite and PostgreSQL do not scope them
|
|
248
|
+
* to an individual table.
|
|
249
|
+
*/
|
|
250
|
+
export function resolveDatabaseSchemaIndexes(
|
|
251
|
+
sources: readonly DBTableIndexSource[],
|
|
252
|
+
): ReadonlyMap<string, readonly ResolvedDBTableIndex[]> {
|
|
253
|
+
const mergedSourcesByTable = new Map<
|
|
254
|
+
string,
|
|
255
|
+
{
|
|
256
|
+
fields: Record<string, DBFieldAttribute>;
|
|
257
|
+
indexes: DBTableIndex[];
|
|
258
|
+
tableName: string;
|
|
259
|
+
}
|
|
260
|
+
>();
|
|
261
|
+
for (const source of sources) {
|
|
262
|
+
const existingSource = mergedSourcesByTable.get(source.tableName);
|
|
263
|
+
if (existingSource) {
|
|
264
|
+
if (
|
|
265
|
+
existingSource.indexes.length > 0 ||
|
|
266
|
+
(source.indexes?.length ?? 0) > 0
|
|
267
|
+
) {
|
|
268
|
+
throw new BetterAuthError(
|
|
269
|
+
`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.`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
Object.assign(existingSource.fields, source.fields);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const mergedSource: {
|
|
276
|
+
fields: Record<string, DBFieldAttribute>;
|
|
277
|
+
indexes: DBTableIndex[];
|
|
278
|
+
tableName: string;
|
|
279
|
+
} = {
|
|
280
|
+
fields: {},
|
|
281
|
+
indexes: [],
|
|
282
|
+
tableName: source.tableName,
|
|
283
|
+
};
|
|
284
|
+
Object.assign(mergedSource.fields, source.fields);
|
|
285
|
+
mergedSource.indexes.push(...(source.indexes ?? []));
|
|
286
|
+
mergedSourcesByTable.set(source.tableName, mergedSource);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const indexesByTable = new Map<string, ResolvedDBTableIndex[]>();
|
|
290
|
+
const indexOwnerByName = new Map<string, string>();
|
|
291
|
+
const tableNamesByIdentifier = new Map(
|
|
292
|
+
[...mergedSourcesByTable.keys()].map((tableName) => [
|
|
293
|
+
getPortableDatabaseIdentifierKey(tableName),
|
|
294
|
+
tableName,
|
|
295
|
+
]),
|
|
296
|
+
);
|
|
297
|
+
const fieldIndexOwnerByName = new Map<string, string>();
|
|
298
|
+
|
|
299
|
+
for (const source of mergedSourcesByTable.values()) {
|
|
300
|
+
for (const [fieldName, field] of Object.entries(source.fields)) {
|
|
301
|
+
if (!field.index && !field.unique) continue;
|
|
302
|
+
const indexName = getDatabaseFieldIndexName(
|
|
303
|
+
source.tableName,
|
|
304
|
+
field.fieldName || fieldName,
|
|
305
|
+
field.unique ?? false,
|
|
306
|
+
);
|
|
307
|
+
const identifierKey = getPortableDatabaseIdentifierKey(indexName);
|
|
308
|
+
if (tableNamesByIdentifier.has(identifierKey)) {
|
|
309
|
+
throw new BetterAuthError(
|
|
310
|
+
`Database index name "${indexName}" conflicts with a table name. Index and table names must be unique across the schema.`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
const existingOwner = fieldIndexOwnerByName.get(identifierKey);
|
|
314
|
+
if (existingOwner) {
|
|
315
|
+
throw new BetterAuthError(
|
|
316
|
+
`Database field-level index name "${indexName}" is used by both table "${existingOwner}" and table "${source.tableName}".`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
fieldIndexOwnerByName.set(identifierKey, source.tableName);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
for (const source of mergedSourcesByTable.values()) {
|
|
324
|
+
const resolvedIndexes = resolveDatabaseTableIndexes(source);
|
|
325
|
+
indexesByTable.set(source.tableName, [...resolvedIndexes]);
|
|
326
|
+
|
|
327
|
+
for (const index of resolvedIndexes) {
|
|
328
|
+
const identifierKey = getPortableDatabaseIdentifierKey(index.name);
|
|
329
|
+
if (tableNamesByIdentifier.has(identifierKey)) {
|
|
330
|
+
throw new BetterAuthError(
|
|
331
|
+
`Database index name "${index.name}" conflicts with a table name. Index and table names must be unique across the schema.`,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
const fieldIndexOwner = fieldIndexOwnerByName.get(identifierKey);
|
|
335
|
+
if (fieldIndexOwner) {
|
|
336
|
+
throw new BetterAuthError(
|
|
337
|
+
`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.`,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
const indexOwner = indexOwnerByName.get(identifierKey);
|
|
341
|
+
if (indexOwner && indexOwner !== source.tableName) {
|
|
342
|
+
throw new BetterAuthError(
|
|
343
|
+
`Database index name "${index.name}" is used by both table "${indexOwner}" and table "${source.tableName}". Index names must be unique across the schema.`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
indexOwnerByName.set(identifierKey, source.tableName);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return indexesByTable;
|
|
351
|
+
}
|
package/src/db/get-tables.ts
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
import type { BetterAuthOptions } from "../types";
|
|
2
|
-
import
|
|
2
|
+
import { resolveDatabaseSchemaIndexes } from "./database-index";
|
|
3
|
+
import type {
|
|
4
|
+
BetterAuthDBSchema,
|
|
5
|
+
DBFieldAttribute,
|
|
6
|
+
DBTableIndex,
|
|
7
|
+
} from "./type";
|
|
3
8
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
)
|
|
9
|
+
function mergeTableIndexes(
|
|
10
|
+
...indexCollections: ReadonlyArray<readonly DBTableIndex[] | undefined>
|
|
11
|
+
) {
|
|
12
|
+
const indexes: DBTableIndex[] = [];
|
|
13
|
+
const seenIndexDefinitions = new Set<string>();
|
|
14
|
+
for (const index of indexCollections.flatMap(
|
|
15
|
+
(collection) => collection ?? [],
|
|
16
|
+
)) {
|
|
17
|
+
const definition = JSON.stringify([
|
|
18
|
+
index.name ?? null,
|
|
19
|
+
index.fields,
|
|
20
|
+
index.unique ?? false,
|
|
21
|
+
]);
|
|
22
|
+
if (seenIndexDefinitions.has(definition)) continue;
|
|
23
|
+
seenIndexDefinitions.add(definition);
|
|
24
|
+
indexes.push(index);
|
|
25
|
+
}
|
|
26
|
+
return indexes;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const buildAuthTables = (options: BetterAuthOptions): BetterAuthDBSchema => {
|
|
7
30
|
const pluginSchema = (options.plugins ?? []).reduce(
|
|
8
31
|
(acc, plugin) => {
|
|
9
32
|
const schema = plugin.schema;
|
|
@@ -14,6 +37,7 @@ export const getAuthTables = (
|
|
|
14
37
|
...acc[key]?.fields,
|
|
15
38
|
...value.fields,
|
|
16
39
|
},
|
|
40
|
+
indexes: mergeTableIndexes(acc[key]?.indexes, value.indexes),
|
|
17
41
|
modelName: value.modelName || key,
|
|
18
42
|
disableMigrations:
|
|
19
43
|
value.disableMigration ?? acc[key]?.disableMigrations,
|
|
@@ -25,6 +49,7 @@ export const getAuthTables = (
|
|
|
25
49
|
string,
|
|
26
50
|
{
|
|
27
51
|
fields: Record<string, DBFieldAttribute>;
|
|
52
|
+
indexes?: readonly DBTableIndex[] | undefined;
|
|
28
53
|
modelName: string;
|
|
29
54
|
disableMigrations?: boolean | undefined;
|
|
30
55
|
}
|
|
@@ -64,6 +89,7 @@ export const getAuthTables = (
|
|
|
64
89
|
const verificationTable = {
|
|
65
90
|
verification: {
|
|
66
91
|
modelName: options.verification?.modelName || "verification",
|
|
92
|
+
indexes: verification?.indexes,
|
|
67
93
|
fields: {
|
|
68
94
|
identifier: {
|
|
69
95
|
type: "string",
|
|
@@ -104,6 +130,7 @@ export const getAuthTables = (
|
|
|
104
130
|
const sessionTable = {
|
|
105
131
|
session: {
|
|
106
132
|
modelName: options.session?.modelName || "session",
|
|
133
|
+
indexes: session?.indexes,
|
|
107
134
|
fields: {
|
|
108
135
|
expiresAt: {
|
|
109
136
|
type: "date",
|
|
@@ -142,7 +169,18 @@ export const getAuthTables = (
|
|
|
142
169
|
type: "string",
|
|
143
170
|
fieldName: options.session?.fields?.userId || "userId",
|
|
144
171
|
references: {
|
|
145
|
-
|
|
172
|
+
// Use the canonical user schema key here rather than
|
|
173
|
+
// `options.user.modelName`. Downstream consumers (e.g.
|
|
174
|
+
// `getSchema`, `getMigrations`, and the runtime adapter
|
|
175
|
+
// resolvers) treat `references.model` as a schema key
|
|
176
|
+
// and look it up via `tables[references.model]` /
|
|
177
|
+
// `getDefaultModelName`. Writing the modelName alias
|
|
178
|
+
// here would collide when a user picks a modelName that
|
|
179
|
+
// matches another schema key (for example
|
|
180
|
+
// `user.modelName = "account"`), causing the FK to
|
|
181
|
+
// resolve to the wrong table.
|
|
182
|
+
// @see https://github.com/better-auth/better-auth/issues/8111
|
|
183
|
+
model: "user",
|
|
146
184
|
field: "id",
|
|
147
185
|
onDelete: "cascade",
|
|
148
186
|
},
|
|
@@ -156,9 +194,10 @@ export const getAuthTables = (
|
|
|
156
194
|
},
|
|
157
195
|
} satisfies BetterAuthDBSchema;
|
|
158
196
|
|
|
159
|
-
|
|
197
|
+
const authTables = {
|
|
160
198
|
user: {
|
|
161
199
|
modelName: options.user?.modelName || "user",
|
|
200
|
+
indexes: user?.indexes,
|
|
162
201
|
fields: {
|
|
163
202
|
name: {
|
|
164
203
|
type: "string",
|
|
@@ -211,11 +250,26 @@ export const getAuthTables = (
|
|
|
211
250
|
: {}),
|
|
212
251
|
account: {
|
|
213
252
|
modelName: options.account?.modelName || "account",
|
|
253
|
+
indexes: mergeTableIndexes(
|
|
254
|
+
[
|
|
255
|
+
{
|
|
256
|
+
fields: ["issuer", "providerAccountId"],
|
|
257
|
+
unique: true,
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
account?.indexes,
|
|
261
|
+
),
|
|
214
262
|
fields: {
|
|
215
|
-
|
|
263
|
+
issuer: {
|
|
264
|
+
type: "string",
|
|
265
|
+
required: true,
|
|
266
|
+
fieldName: options.account?.fields?.issuer || "issuer",
|
|
267
|
+
},
|
|
268
|
+
providerAccountId: {
|
|
216
269
|
type: "string",
|
|
217
270
|
required: true,
|
|
218
|
-
fieldName:
|
|
271
|
+
fieldName:
|
|
272
|
+
options.account?.fields?.providerAccountId || "providerAccountId",
|
|
219
273
|
},
|
|
220
274
|
providerId: {
|
|
221
275
|
type: "string",
|
|
@@ -225,7 +279,11 @@ export const getAuthTables = (
|
|
|
225
279
|
userId: {
|
|
226
280
|
type: "string",
|
|
227
281
|
references: {
|
|
228
|
-
|
|
282
|
+
// See note on `session.userId.references.model` above:
|
|
283
|
+
// always use the canonical user schema key so the FK
|
|
284
|
+
// target survives `user.modelName` aliasing.
|
|
285
|
+
// @see https://github.com/better-auth/better-auth/issues/8111
|
|
286
|
+
model: "user",
|
|
229
287
|
field: "id",
|
|
230
288
|
onDelete: "cascade",
|
|
231
289
|
},
|
|
@@ -301,4 +359,26 @@ export const getAuthTables = (
|
|
|
301
359
|
...pluginTables,
|
|
302
360
|
...(shouldAddRateLimitTable ? rateLimitTable : {}),
|
|
303
361
|
} satisfies BetterAuthDBSchema;
|
|
362
|
+
|
|
363
|
+
return authTables;
|
|
304
364
|
};
|
|
365
|
+
|
|
366
|
+
export function getAuthTablesWithResolvedIndexes(options: BetterAuthOptions) {
|
|
367
|
+
const tables = buildAuthTables(options);
|
|
368
|
+
const indexesByTable = resolveDatabaseSchemaIndexes(
|
|
369
|
+
Object.values(tables)
|
|
370
|
+
.filter(
|
|
371
|
+
(table) => !("disableMigrations" in table) || !table.disableMigrations,
|
|
372
|
+
)
|
|
373
|
+
.map((table) => ({
|
|
374
|
+
fields: table.fields,
|
|
375
|
+
indexes: "indexes" in table ? table.indexes : undefined,
|
|
376
|
+
tableName: table.modelName,
|
|
377
|
+
})),
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
return { indexesByTable, tables };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export const getAuthTables = (options: BetterAuthOptions): BetterAuthDBSchema =>
|
|
384
|
+
getAuthTablesWithResolvedIndexes(options).tables;
|
package/src/db/index.ts
CHANGED
|
@@ -2,8 +2,11 @@ export { getAuthTables } from "./get-tables";
|
|
|
2
2
|
export type { BetterAuthPluginDBSchema } from "./plugin";
|
|
3
3
|
export {
|
|
4
4
|
type Account,
|
|
5
|
+
type AccountKey,
|
|
5
6
|
accountSchema,
|
|
6
7
|
type BaseAccount,
|
|
8
|
+
createLocalAccountIssuer,
|
|
9
|
+
createOAuthAccountIssuer,
|
|
7
10
|
} from "./schema/account";
|
|
8
11
|
export {
|
|
9
12
|
type BaseRateLimit,
|
|
@@ -29,6 +32,7 @@ export type {
|
|
|
29
32
|
DBFieldAttributeConfig,
|
|
30
33
|
DBFieldType,
|
|
31
34
|
DBPrimitive,
|
|
35
|
+
DBTableIndex,
|
|
32
36
|
InferDBFieldInput,
|
|
33
37
|
InferDBFieldOutput,
|
|
34
38
|
InferDBFieldsFromOptions,
|