@better-auth/core 1.7.1 → 1.7.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 +3 -0
- package/dist/context/endpoint-context.d.mts +19 -5
- package/dist/context/endpoint-context.mjs +35 -16
- package/dist/context/global.mjs +5 -2
- package/dist/context/index.d.mts +2 -2
- package/dist/context/index.mjs +2 -2
- package/dist/context/transaction.mjs +3 -0
- package/dist/db/adapter/atomic-fallback.mjs +134 -0
- package/dist/db/adapter/factory.mjs +22 -4
- package/dist/db/adapter/index.d.mts +15 -11
- package/dist/db/get-tables.mjs +1 -9
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +2 -2
- package/dist/db/internal.d.mts +3 -1
- package/dist/db/internal.mjs +3 -1
- package/dist/db/schema/account.d.mts +2 -13
- package/dist/db/schema/account.mjs +1 -19
- package/dist/db/schema-check.d.mts +48 -0
- package/dist/db/schema-check.mjs +80 -0
- package/dist/db/schema-diff.d.mts +104 -0
- package/dist/db/schema-diff.mjs +154 -0
- package/dist/env/logger.mjs +16 -1
- package/dist/instrumentation/tracer.mjs +1 -1
- package/dist/oauth2/index.d.mts +2 -2
- package/dist/oauth2/oauth-provider.d.mts +0 -10
- package/dist/oauth2/token-endpoint-auth.d.mts +26 -2
- package/dist/oauth2/token-endpoint-auth.mjs +11 -0
- package/dist/social-providers/apple.d.mts +0 -1
- package/dist/social-providers/apple.mjs +0 -1
- package/dist/social-providers/cloudflare.d.mts +132 -0
- package/dist/social-providers/cloudflare.mjs +85 -0
- package/dist/social-providers/cognito.d.mts +0 -1
- package/dist/social-providers/cognito.mjs +0 -1
- package/dist/social-providers/facebook.d.mts +0 -1
- package/dist/social-providers/facebook.mjs +0 -1
- package/dist/social-providers/google.d.mts +0 -1
- package/dist/social-providers/google.mjs +0 -1
- package/dist/social-providers/index.d.mts +53 -21
- package/dist/social-providers/index.mjs +3 -1
- package/dist/social-providers/line.d.mts +0 -1
- package/dist/social-providers/line.mjs +0 -1
- package/dist/social-providers/microsoft-entra-id.d.mts +0 -3
- package/dist/social-providers/microsoft-entra-id.mjs +0 -1
- package/dist/social-providers/paybin.d.mts +0 -1
- package/dist/social-providers/paybin.mjs +0 -1
- package/dist/social-providers/paypal.d.mts +3 -11
- package/dist/social-providers/paypal.mjs +20 -47
- package/dist/social-providers/reddit.mjs +22 -23
- package/dist/social-providers/roblox.mjs +5 -1
- package/dist/social-providers/tiktok.d.mts +1 -0
- package/dist/social-providers/tiktok.mjs +19 -10
- package/dist/social-providers/twitter.mjs +5 -1
- package/dist/social-providers/wechat.mjs +6 -1
- package/dist/types/context.d.mts +11 -0
- package/dist/types/init-options.d.mts +11 -0
- package/dist/utils/ip.mjs +11 -9
- package/dist/utils/url.d.mts +10 -1
- package/dist/utils/url.mjs +21 -1
- package/package.json +3 -3
- package/src/context/endpoint-context.ts +46 -21
- package/src/context/global.ts +7 -0
- package/src/context/index.ts +2 -0
- package/src/context/transaction.ts +5 -0
- package/src/db/adapter/atomic-fallback.ts +237 -0
- package/src/db/adapter/factory.ts +33 -17
- package/src/db/adapter/index.ts +15 -11
- package/src/db/get-tables.ts +1 -14
- package/src/db/index.ts +0 -2
- package/src/db/internal.ts +19 -0
- package/src/db/schema/account.ts +3 -22
- package/src/db/schema/user.ts +1 -1
- package/src/db/schema-check.ts +107 -0
- package/src/db/schema-diff.ts +270 -0
- package/src/env/logger.ts +22 -1
- package/src/oauth2/index.ts +2 -0
- package/src/oauth2/oauth-provider.ts +0 -10
- package/src/oauth2/token-endpoint-auth.ts +39 -6
- package/src/social-providers/apple.ts +0 -1
- package/src/social-providers/cloudflare.ts +221 -0
- package/src/social-providers/cognito.ts +0 -1
- package/src/social-providers/facebook.ts +0 -1
- package/src/social-providers/google.ts +0 -1
- package/src/social-providers/index.ts +3 -0
- package/src/social-providers/line.ts +0 -1
- package/src/social-providers/microsoft-entra-id.ts +0 -1
- package/src/social-providers/paybin.ts +0 -1
- package/src/social-providers/paypal.ts +30 -71
- package/src/social-providers/reddit.ts +34 -37
- package/src/social-providers/roblox.ts +5 -3
- package/src/social-providers/tiktok.ts +25 -14
- package/src/social-providers/twitter.ts +8 -2
- package/src/social-providers/wechat.ts +6 -6
- package/src/types/context.ts +11 -0
- package/src/types/init-options.ts +11 -0
- package/src/utils/ip.ts +13 -9
- package/src/utils/url.ts +43 -0
package/dist/api/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BetterAuthDBSchema, ModelNames, SecondaryStorage } from "../db/type.mjs";
|
|
2
2
|
import { DBAdapter } from "../db/adapter/index.mjs";
|
|
3
3
|
import { createLogger } from "../env/logger.mjs";
|
|
4
|
+
import { SchemaCheck } from "../db/schema-check.mjs";
|
|
4
5
|
import { AuthContext } from "../types/context.mjs";
|
|
5
6
|
import { OAuthProvider } from "../oauth2/oauth-provider.mjs";
|
|
6
7
|
import * as _$better_call0 from "better-call";
|
|
@@ -112,6 +113,7 @@ declare const createAuthMiddleware: {
|
|
|
112
113
|
storage: "memory" | "database" | "secondary-storage";
|
|
113
114
|
} & Omit<_$_better_auth_core0.BetterAuthRateLimitOptions, "enabled" | "window" | "max" | "storage">;
|
|
114
115
|
adapter: DBAdapter<_$_better_auth_core0.BetterAuthOptions>;
|
|
116
|
+
checkSchema?: SchemaCheck | undefined;
|
|
115
117
|
internalAdapter: _$_better_auth_core0.InternalAdapter<_$_better_auth_core0.BetterAuthOptions>;
|
|
116
118
|
createAuthCookie: (cookieName: string, overrideAttributes?: Partial<_$better_call0.CookieOptions> | undefined) => _$_better_auth_core0.BetterAuthCookie;
|
|
117
119
|
secret: string;
|
|
@@ -242,6 +244,7 @@ declare const createAuthMiddleware: {
|
|
|
242
244
|
storage: "memory" | "database" | "secondary-storage";
|
|
243
245
|
} & Omit<_$_better_auth_core0.BetterAuthRateLimitOptions, "enabled" | "window" | "max" | "storage">;
|
|
244
246
|
adapter: DBAdapter<_$_better_auth_core0.BetterAuthOptions>;
|
|
247
|
+
checkSchema?: SchemaCheck | undefined;
|
|
245
248
|
internalAdapter: _$_better_auth_core0.InternalAdapter<_$_better_auth_core0.BetterAuthOptions>;
|
|
246
249
|
createAuthCookie: (cookieName: string, overrideAttributes?: Partial<_$better_call0.CookieOptions> | undefined) => _$_better_auth_core0.BetterAuthCookie;
|
|
247
250
|
secret: string;
|
|
@@ -6,13 +6,27 @@ import { AsyncLocalStorage } from "@better-auth/core/async_hooks";
|
|
|
6
6
|
type AuthEndpointContext = Partial<InputContext<string, any> & EndpointContext<string, any>> & {
|
|
7
7
|
context: AuthContext;
|
|
8
8
|
};
|
|
9
|
+
type AuthEndpointContextStorage = AsyncLocalStorage<AuthEndpointContext>;
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* @deprecated Use `getCurrentAuthEndpointContext`,
|
|
12
|
+
* `tryGetCurrentAuthEndpointContext`, or `runWithEndpointContext` instead.
|
|
13
|
+
*/
|
|
14
|
+
declare function getCurrentAuthContextAsyncLocalStorage(): Promise<AuthEndpointContextStorage>;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the current auth endpoint context, or `undefined` when called outside
|
|
17
|
+
* of `runWithEndpointContext`.
|
|
18
|
+
*/
|
|
19
|
+
declare function tryGetCurrentAuthEndpointContext(): AuthEndpointContext | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the current auth endpoint context.
|
|
11
22
|
*
|
|
12
|
-
*
|
|
23
|
+
* @throws When called outside of `runWithEndpointContext`.
|
|
24
|
+
*/
|
|
25
|
+
declare function getCurrentAuthEndpointContext(): AuthEndpointContext;
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Use `getCurrentAuthEndpointContext` instead.
|
|
13
28
|
*/
|
|
14
|
-
declare function getCurrentAuthContextAsyncLocalStorage(): Promise<AsyncLocalStorage<AuthEndpointContext>>;
|
|
15
29
|
declare function getCurrentAuthContext(): Promise<AuthEndpointContext>;
|
|
16
|
-
declare function runWithEndpointContext<T>(
|
|
30
|
+
declare function runWithEndpointContext<T>(authEndpointContext: AuthEndpointContext, fn: () => T): Promise<T>;
|
|
17
31
|
//#endregion
|
|
18
|
-
export { AuthEndpointContext, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, runWithEndpointContext };
|
|
32
|
+
export { AuthEndpointContext, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, runWithEndpointContext, tryGetCurrentAuthEndpointContext };
|
|
@@ -1,29 +1,48 @@
|
|
|
1
|
-
import { __getBetterAuthGlobal } from "./global.mjs";
|
|
1
|
+
import { __getBetterAuthGlobal, __getCurrentEndpointContext } from "./global.mjs";
|
|
2
2
|
import { getAsyncLocalStorage } from "@better-auth/core/async_hooks";
|
|
3
3
|
//#region src/context/endpoint-context.ts
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const getExistingEndpointContextStorage = () => {
|
|
5
|
+
return __getBetterAuthGlobal().context.endpointContextAsyncStorage;
|
|
6
|
+
};
|
|
7
|
+
const getOrCreateEndpointContextStorage = async () => {
|
|
8
|
+
const existing = getExistingEndpointContextStorage();
|
|
7
9
|
if (existing) return existing;
|
|
8
10
|
const AsyncLocalStorage = await getAsyncLocalStorage();
|
|
9
|
-
|
|
10
|
-
return
|
|
11
|
+
const globalContext = __getBetterAuthGlobal().context;
|
|
12
|
+
return globalContext.endpointContextAsyncStorage ??= new AsyncLocalStorage();
|
|
11
13
|
};
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* It is exposed for advanced use cases where you need direct access to the AsyncLocalStorage instance.
|
|
15
|
+
* @deprecated Use `getCurrentAuthEndpointContext`,
|
|
16
|
+
* `tryGetCurrentAuthEndpointContext`, or `runWithEndpointContext` instead.
|
|
16
17
|
*/
|
|
17
18
|
async function getCurrentAuthContextAsyncLocalStorage() {
|
|
18
|
-
return
|
|
19
|
+
return getOrCreateEndpointContextStorage();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Returns the current auth endpoint context, or `undefined` when called outside
|
|
23
|
+
* of `runWithEndpointContext`.
|
|
24
|
+
*/
|
|
25
|
+
function tryGetCurrentAuthEndpointContext() {
|
|
26
|
+
return __getCurrentEndpointContext();
|
|
19
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns the current auth endpoint context.
|
|
30
|
+
*
|
|
31
|
+
* @throws When called outside of `runWithEndpointContext`.
|
|
32
|
+
*/
|
|
33
|
+
function getCurrentAuthEndpointContext() {
|
|
34
|
+
const authEndpointContext = tryGetCurrentAuthEndpointContext();
|
|
35
|
+
if (!authEndpointContext) throw new Error("No auth context found. Please make sure you are calling this function within a `runWithEndpointContext` callback.");
|
|
36
|
+
return authEndpointContext;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Use `getCurrentAuthEndpointContext` instead.
|
|
40
|
+
*/
|
|
20
41
|
async function getCurrentAuthContext() {
|
|
21
|
-
|
|
22
|
-
if (!context) throw new Error("No auth context found. Please make sure you are calling this function within a `runWithEndpointContext` callback.");
|
|
23
|
-
return context;
|
|
42
|
+
return getCurrentAuthEndpointContext();
|
|
24
43
|
}
|
|
25
|
-
async function runWithEndpointContext(
|
|
26
|
-
return (await
|
|
44
|
+
async function runWithEndpointContext(authEndpointContext, fn) {
|
|
45
|
+
return (await getOrCreateEndpointContextStorage()).run(authEndpointContext, fn);
|
|
27
46
|
}
|
|
28
47
|
//#endregion
|
|
29
|
-
export { getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, runWithEndpointContext };
|
|
48
|
+
export { getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, runWithEndpointContext, tryGetCurrentAuthEndpointContext };
|
package/dist/context/global.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const symbol = Symbol.for("better-auth:global");
|
|
3
3
|
let bind = null;
|
|
4
4
|
const __context = {};
|
|
5
|
-
const __betterAuthVersion = "1.7.
|
|
5
|
+
const __betterAuthVersion = "1.7.3";
|
|
6
6
|
/**
|
|
7
7
|
* We store context instance in the globalThis.
|
|
8
8
|
*
|
|
@@ -29,8 +29,11 @@ function __getBetterAuthGlobal() {
|
|
|
29
29
|
}
|
|
30
30
|
return globalThis[symbol];
|
|
31
31
|
}
|
|
32
|
+
function __getCurrentEndpointContext() {
|
|
33
|
+
return __getBetterAuthGlobal().context.endpointContextAsyncStorage?.getStore();
|
|
34
|
+
}
|
|
32
35
|
function getBetterAuthVersion() {
|
|
33
36
|
return __getBetterAuthGlobal().version;
|
|
34
37
|
}
|
|
35
38
|
//#endregion
|
|
36
|
-
export { __getBetterAuthGlobal, getBetterAuthVersion };
|
|
39
|
+
export { __getBetterAuthGlobal, __getCurrentEndpointContext, getBetterAuthVersion };
|
package/dist/context/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AuthEndpointContext, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, runWithEndpointContext } from "./endpoint-context.mjs";
|
|
1
|
+
import { AuthEndpointContext, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, runWithEndpointContext, tryGetCurrentAuthEndpointContext } from "./endpoint-context.mjs";
|
|
2
2
|
import { getBetterAuthVersion } from "./global.mjs";
|
|
3
3
|
import { RequestState, RequestStateWeakMap, defineRequestState, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, runWithRequestState } from "./request-state.mjs";
|
|
4
4
|
import { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, queueAfterTransactionHook, runWithAdapter, runWithTransaction } from "./transaction.mjs";
|
|
5
|
-
export { type AuthEndpointContext, type RequestState, type RequestStateWeakMap, defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, queueAfterTransactionHook, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction };
|
|
5
|
+
export { type AuthEndpointContext, type RequestState, type RequestStateWeakMap, defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, queueAfterTransactionHook, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction, tryGetCurrentAuthEndpointContext };
|
package/dist/context/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getBetterAuthVersion } from "./global.mjs";
|
|
2
|
-
import { getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, runWithEndpointContext } from "./endpoint-context.mjs";
|
|
2
|
+
import { getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, runWithEndpointContext, tryGetCurrentAuthEndpointContext } from "./endpoint-context.mjs";
|
|
3
3
|
import { defineRequestState, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, runWithRequestState } from "./request-state.mjs";
|
|
4
4
|
import { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, queueAfterTransactionHook, runWithAdapter, runWithTransaction } from "./transaction.mjs";
|
|
5
|
-
export { defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, queueAfterTransactionHook, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction };
|
|
5
|
+
export { defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentAuthEndpointContext, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, queueAfterTransactionHook, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction, tryGetCurrentAuthEndpointContext };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { __getBetterAuthGlobal } from "./global.mjs";
|
|
2
|
+
import { schemaCheckFor } from "../db/schema-check.mjs";
|
|
2
3
|
import { getAsyncLocalStorage } from "@better-auth/core/async_hooks";
|
|
3
4
|
//#region src/context/transaction.ts
|
|
4
5
|
const ensureAsyncStorage = async () => {
|
|
@@ -55,6 +56,8 @@ const runWithTransaction = async (adapter, fn, options) => {
|
|
|
55
56
|
return ensureAsyncStorage().then(async (als) => {
|
|
56
57
|
called = true;
|
|
57
58
|
if (als.getStore()?.isTransactionActive) return fn();
|
|
59
|
+
const pendingSchemaCheck = schemaCheckFor(adapter)?.();
|
|
60
|
+
if (pendingSchemaCheck) await pendingSchemaCheck;
|
|
58
61
|
const pendingHooks = [];
|
|
59
62
|
let result;
|
|
60
63
|
let error;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { BetterAuthError } from "../../error/index.mjs";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
//#region src/db/adapter/atomic-fallback.ts
|
|
4
|
+
const MAX_ATTEMPTS = 5;
|
|
5
|
+
const scalar = z.union([
|
|
6
|
+
z.string(),
|
|
7
|
+
z.number(),
|
|
8
|
+
z.boolean(),
|
|
9
|
+
z.date()
|
|
10
|
+
]).nullable();
|
|
11
|
+
const readSchema = z.record(z.string(), z.unknown()).nullish();
|
|
12
|
+
const setSchema = z.record(z.string(), z.unknown()).transform((values) => {
|
|
13
|
+
const assignments = {};
|
|
14
|
+
for (const [field, value] of Object.entries(values)) if (value !== void 0) assignments[field] = value;
|
|
15
|
+
return assignments;
|
|
16
|
+
});
|
|
17
|
+
const mutationSchema = z.object({
|
|
18
|
+
increment: z.record(z.string(), z.number()),
|
|
19
|
+
set: setSchema.optional()
|
|
20
|
+
});
|
|
21
|
+
const counterSchema = z.number().nullish();
|
|
22
|
+
function createAtomicFallbacks(context) {
|
|
23
|
+
const { adapter, adapterId } = context;
|
|
24
|
+
const outputId = Object.entries(context.mapKeysTransformOutput ?? {}).find(([, field]) => field === "id")?.[0] ?? "id";
|
|
25
|
+
async function idWhere(row, model, action) {
|
|
26
|
+
const mappedId = context.mapKeysTransformInput?.id || context.getFieldName({
|
|
27
|
+
model,
|
|
28
|
+
field: "id"
|
|
29
|
+
});
|
|
30
|
+
if (row[mappedId] === void 0 || row[mappedId] === null) throw new BetterAuthError(`Adapter "${context.adapterId}" must return the row id for atomic fallbacks.`);
|
|
31
|
+
const id = (await context.transformOutput(row, model, [outputId], void 0))?.id;
|
|
32
|
+
if (typeof id !== "string" && typeof id !== "number") throw new BetterAuthError(`Adapter "${context.adapterId}" must expose a logical string or number id through its output transform.`);
|
|
33
|
+
const [condition] = context.transformWhereClause({
|
|
34
|
+
model,
|
|
35
|
+
where: [{
|
|
36
|
+
field: "id",
|
|
37
|
+
value: id
|
|
38
|
+
}],
|
|
39
|
+
action
|
|
40
|
+
});
|
|
41
|
+
if (!condition) throw new BetterAuthError("The atomic fallback id condition was transformed away.");
|
|
42
|
+
return condition;
|
|
43
|
+
}
|
|
44
|
+
async function readRow({ model, where }) {
|
|
45
|
+
const result = readSchema.safeParse(await adapter.findOne({
|
|
46
|
+
model,
|
|
47
|
+
where
|
|
48
|
+
}));
|
|
49
|
+
if (!result.success) throw new BetterAuthError(`Adapter "${adapterId}" must return a row snapshot or null.`);
|
|
50
|
+
return result.data ?? null;
|
|
51
|
+
}
|
|
52
|
+
async function snapshotGuard(row, fields, request, action) {
|
|
53
|
+
const id = await idWhere(row, request.logicalModel, action);
|
|
54
|
+
const hasOr = request.where.some((clause) => clause.connector === "OR");
|
|
55
|
+
const guard = hasOr ? [id] : [...request.where, id];
|
|
56
|
+
const keys = new Set(fields);
|
|
57
|
+
for (const field of keys) {
|
|
58
|
+
if (field === id.field) continue;
|
|
59
|
+
const value = scalar.safeParse(row[field] ?? null);
|
|
60
|
+
if (!value.success) {
|
|
61
|
+
if (hasOr && request.where.some((clause) => clause.field === field)) throw new BetterAuthError(`Adapter "${adapterId}" must implement native atomic methods for OR predicates on structured values.`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
guard.push({
|
|
65
|
+
field,
|
|
66
|
+
value: value.data,
|
|
67
|
+
operator: "eq",
|
|
68
|
+
connector: "AND",
|
|
69
|
+
mode: "sensitive"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return guard;
|
|
73
|
+
}
|
|
74
|
+
function changedOne(count) {
|
|
75
|
+
if (count !== 0 && count !== 1) throw new BetterAuthError(`Adapter "${adapterId}" must return an affected row count of 0 or 1 from an atomic fallback.`);
|
|
76
|
+
return count === 1;
|
|
77
|
+
}
|
|
78
|
+
async function consumeOne(request) {
|
|
79
|
+
const { model, where } = request;
|
|
80
|
+
const row = await readRow(request);
|
|
81
|
+
if (row === null) return null;
|
|
82
|
+
const guard = await snapshotGuard(row, [...Object.keys(row), ...where.map(({ field }) => field)], request, "consumeOne");
|
|
83
|
+
return changedOne(await adapter.deleteMany({
|
|
84
|
+
model,
|
|
85
|
+
where: guard
|
|
86
|
+
})) ? row : null;
|
|
87
|
+
}
|
|
88
|
+
async function incrementOne(request) {
|
|
89
|
+
const { model, where } = request;
|
|
90
|
+
const mutation = mutationSchema.safeParse(request);
|
|
91
|
+
if (!mutation.success) throw new BetterAuthError("incrementOne requires finite increments and a set object for the atomic fallback.");
|
|
92
|
+
const { increment, set } = mutation.data;
|
|
93
|
+
const deltas = Object.entries(increment);
|
|
94
|
+
const fields = [
|
|
95
|
+
...where.map(({ field }) => field),
|
|
96
|
+
...Object.keys(increment),
|
|
97
|
+
...Object.keys(set ?? {})
|
|
98
|
+
];
|
|
99
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
100
|
+
const row = await readRow(request);
|
|
101
|
+
if (row === null) return null;
|
|
102
|
+
const update = { ...set };
|
|
103
|
+
for (const [field, delta] of deltas) {
|
|
104
|
+
const previous = counterSchema.safeParse(row[field]);
|
|
105
|
+
if (!previous.success) throw new BetterAuthError(`Adapter "${adapterId}" must return finite numeric counter values or null for atomic increments.`);
|
|
106
|
+
const current = previous.data ?? 0;
|
|
107
|
+
const next = current + delta;
|
|
108
|
+
if (!Number.isFinite(next) || delta !== 0 && next === current) throw new BetterAuthError(`Adapter "${adapterId}" cannot represent the requested counter increment safely.`);
|
|
109
|
+
update[field] = next;
|
|
110
|
+
}
|
|
111
|
+
const guard = await snapshotGuard(row, fields, request, "incrementOne");
|
|
112
|
+
if (Object.entries(update).every(([field, value]) => {
|
|
113
|
+
const previous = row[field];
|
|
114
|
+
if (previous instanceof Date && value instanceof Date) return previous.getTime() === value.getTime();
|
|
115
|
+
return Object.is(previous, value);
|
|
116
|
+
})) return row;
|
|
117
|
+
if (changedOne(await adapter.updateMany({
|
|
118
|
+
model,
|
|
119
|
+
where: guard,
|
|
120
|
+
update
|
|
121
|
+
}))) return {
|
|
122
|
+
...row,
|
|
123
|
+
...update
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
throw new BetterAuthError(`Adapter "${adapterId}" could not complete an atomic increment due to contention. Retry the operation or implement incrementOne natively.`);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
consumeOne,
|
|
130
|
+
incrementOne
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { createAtomicFallbacks };
|
|
@@ -3,6 +3,7 @@ import { getAuthTables } from "../get-tables.mjs";
|
|
|
3
3
|
import { getColorDepth } from "../../env/color-depth.mjs";
|
|
4
4
|
import { TTY_COLORS, createLogger } from "../../env/logger.mjs";
|
|
5
5
|
import { safeJSONParse } from "../../utils/json.mjs";
|
|
6
|
+
import { createAtomicFallbacks } from "./atomic-fallback.mjs";
|
|
6
7
|
import { initGetDefaultModelName } from "./get-default-model-name.mjs";
|
|
7
8
|
import { initGetDefaultFieldName } from "./get-default-field-name.mjs";
|
|
8
9
|
import { initGetIdField } from "./get-id-field.mjs";
|
|
@@ -401,6 +402,15 @@ const createAdapterFactory = ({ adapter: customAdapter, config: cfg }) => (optio
|
|
|
401
402
|
transformWhereClause
|
|
402
403
|
});
|
|
403
404
|
let lazyLoadTransaction = null;
|
|
405
|
+
const atomicFallbacks = createAtomicFallbacks({
|
|
406
|
+
adapter: adapterInstance,
|
|
407
|
+
adapterId: config.adapterId,
|
|
408
|
+
mapKeysTransformInput: config.mapKeysTransformInput,
|
|
409
|
+
mapKeysTransformOutput: config.mapKeysTransformOutput,
|
|
410
|
+
getFieldName,
|
|
411
|
+
transformOutput,
|
|
412
|
+
transformWhereClause
|
|
413
|
+
});
|
|
404
414
|
const adapter = {
|
|
405
415
|
transaction: async (cb) => {
|
|
406
416
|
if (!lazyLoadTransaction) if (!config.transaction) lazyLoadTransaction = createAsIsTransaction(adapter);
|
|
@@ -696,12 +706,15 @@ const createAdapterFactory = ({ adapter: customAdapter, config: cfg }) => (optio
|
|
|
696
706
|
model,
|
|
697
707
|
where
|
|
698
708
|
});
|
|
699
|
-
if (typeof adapterInstance.consumeOne !== "function") throw new BetterAuthError(`Adapter "${config.adapterId}" must implement consumeOne for atomic single-use credential consumption.`);
|
|
700
709
|
const res = await withSpan(`db consumeOne ${model}`, {
|
|
701
710
|
[ATTR_DB_OPERATION_NAME]: "consumeOne",
|
|
702
711
|
[ATTR_DB_COLLECTION_NAME]: model
|
|
703
|
-
}, () => adapterInstance.consumeOne({
|
|
712
|
+
}, () => adapterInstance.consumeOne ? adapterInstance.consumeOne({
|
|
713
|
+
model,
|
|
714
|
+
where
|
|
715
|
+
}) : atomicFallbacks.consumeOne({
|
|
704
716
|
model,
|
|
717
|
+
logicalModel: unsafeModel,
|
|
705
718
|
where
|
|
706
719
|
}));
|
|
707
720
|
debugLog({ method: "consumeOne" }, `${formatTransactionId(thisTransactionId)} ${formatStep(2, 3)}`, `${formatMethod("consumeOne")} ${formatAction("DB Result")}:`, {
|
|
@@ -735,7 +748,6 @@ const createAdapterFactory = ({ adapter: customAdapter, config: cfg }) => (optio
|
|
|
735
748
|
increment: unsafeIncrement,
|
|
736
749
|
set: unsafeSet
|
|
737
750
|
});
|
|
738
|
-
if (typeof adapterInstance.incrementOne !== "function") throw new BetterAuthError(`Adapter "${config.adapterId}" must implement incrementOne for atomic guarded counter updates.`);
|
|
739
751
|
const mappedKeys = config.mapKeysTransformInput ?? {};
|
|
740
752
|
const increment = {};
|
|
741
753
|
for (const [field, delta] of Object.entries(unsafeIncrement)) increment[mappedKeys[field] || getFieldName({
|
|
@@ -749,8 +761,14 @@ const createAdapterFactory = ({ adapter: customAdapter, config: cfg }) => (optio
|
|
|
749
761
|
const res = await withSpan(`db incrementOne ${model}`, {
|
|
750
762
|
[ATTR_DB_OPERATION_NAME]: "incrementOne",
|
|
751
763
|
[ATTR_DB_COLLECTION_NAME]: model
|
|
752
|
-
}, () => adapterInstance.incrementOne({
|
|
764
|
+
}, () => adapterInstance.incrementOne ? adapterInstance.incrementOne({
|
|
765
|
+
model,
|
|
766
|
+
where,
|
|
767
|
+
increment,
|
|
768
|
+
set
|
|
769
|
+
}) : atomicFallbacks.incrementOne({
|
|
753
770
|
model,
|
|
771
|
+
logicalModel: unsafeModel,
|
|
754
772
|
where,
|
|
755
773
|
increment,
|
|
756
774
|
set
|
|
@@ -434,9 +434,9 @@ type DBAdapter<Options extends BetterAuthOptions = BetterAuthOptions> = {
|
|
|
434
434
|
* race-safe primitive for consuming single-use credentials
|
|
435
435
|
* (verification tokens, authorization codes, one-time tokens).
|
|
436
436
|
*
|
|
437
|
-
* Always defined on the factory-wrapped adapter.
|
|
438
|
-
*
|
|
439
|
-
*
|
|
437
|
+
* Always defined on the factory-wrapped adapter. Without a native method,
|
|
438
|
+
* the factory uses a snapshot-guarded delete and requires an exact affected
|
|
439
|
+
* row count. The adapter must evaluate the condition and deletion atomically.
|
|
440
440
|
*/
|
|
441
441
|
consumeOne: <T>(data: {
|
|
442
442
|
model: string;
|
|
@@ -461,9 +461,11 @@ type DBAdapter<Options extends BetterAuthOptions = BetterAuthOptions> = {
|
|
|
461
461
|
* primitive for guarded counter updates (e.g. decrementing a remaining-uses
|
|
462
462
|
* counter only while it is still positive).
|
|
463
463
|
*
|
|
464
|
-
* Always defined on the factory-wrapped adapter.
|
|
465
|
-
*
|
|
466
|
-
*
|
|
464
|
+
* Always defined on the factory-wrapped adapter. Without a native method,
|
|
465
|
+
* the factory uses bounded compare-and-swap retries. Contention exhaustion
|
|
466
|
+
* throws rather than returning null. Conditional writes must be atomic.
|
|
467
|
+
* A no-op may return the read snapshot without writing. A non-null result
|
|
468
|
+
* alone does not establish exclusive ownership of the row.
|
|
467
469
|
*/
|
|
468
470
|
incrementOne: <T>(data: {
|
|
469
471
|
model: string;
|
|
@@ -553,19 +555,21 @@ interface CustomAdapter {
|
|
|
553
555
|
where: CleanedWhere[];
|
|
554
556
|
}) => Promise<number>;
|
|
555
557
|
/**
|
|
556
|
-
*
|
|
558
|
+
* Optional native atomic single-row consume.
|
|
559
|
+
*
|
|
557
560
|
* Implementing this method natively (e.g. `DELETE ... RETURNING *`,
|
|
558
561
|
* `findOneAndDelete`, `OUTPUT deleted.*`) gives one round trip and the
|
|
559
562
|
* strongest race-safety guarantee. Implementations must delete at most
|
|
560
563
|
* one matching row.
|
|
561
564
|
*/
|
|
562
|
-
consumeOne
|
|
565
|
+
consumeOne?: <T>(data: {
|
|
563
566
|
model: string;
|
|
564
567
|
where: CleanedWhere[];
|
|
565
568
|
}) => Promise<T | null>;
|
|
566
569
|
/**
|
|
567
|
-
*
|
|
568
|
-
*
|
|
570
|
+
* Optional native atomic guarded counter mutation.
|
|
571
|
+
*
|
|
572
|
+
* Applies `field = field + delta` for each entry in `increment` (negative deltas
|
|
569
573
|
* decrement), with `where` acting as both selector and guard and `set`
|
|
570
574
|
* assigning absolute values in the same operation. Returns the updated row,
|
|
571
575
|
* or `null` when the guard matched no row.
|
|
@@ -574,7 +578,7 @@ interface CustomAdapter {
|
|
|
574
578
|
* RETURNING *`) gives one round trip and the strongest race-safety
|
|
575
579
|
* guarantee.
|
|
576
580
|
*/
|
|
577
|
-
incrementOne
|
|
581
|
+
incrementOne?: <T>(data: {
|
|
578
582
|
model: string;
|
|
579
583
|
where: CleanedWhere[];
|
|
580
584
|
increment: Record<string, number>;
|
package/dist/db/get-tables.mjs
CHANGED
|
@@ -197,16 +197,8 @@ const buildAuthTables = (options) => {
|
|
|
197
197
|
...!options.secondaryStorage || options.session?.storeSessionInDatabase ? sessionTable : {},
|
|
198
198
|
account: {
|
|
199
199
|
modelName: options.account?.modelName || "account",
|
|
200
|
-
indexes:
|
|
201
|
-
fields: ["issuer", "accountId"],
|
|
202
|
-
unique: true
|
|
203
|
-
}], account?.indexes),
|
|
200
|
+
indexes: account?.indexes,
|
|
204
201
|
fields: {
|
|
205
|
-
issuer: {
|
|
206
|
-
type: "string",
|
|
207
|
-
required: true,
|
|
208
|
-
fieldName: options.account?.fields?.issuer || "issuer"
|
|
209
|
-
},
|
|
210
202
|
accountId: {
|
|
211
203
|
type: "string",
|
|
212
204
|
required: true,
|
package/dist/db/index.d.mts
CHANGED
|
@@ -5,6 +5,6 @@ import { BaseRateLimit, RateLimit, rateLimitSchema } from "./schema/rate-limit.m
|
|
|
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, AccountKey, BaseAccount, accountSchema
|
|
8
|
+
import { Account, AccountKey, BaseAccount, accountSchema } from "./schema/account.mjs";
|
|
9
9
|
import { coreSchema } from "./schema/shared.mjs";
|
|
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,
|
|
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, 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
|
|
3
|
+
import { accountSchema } 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,
|
|
8
|
+
export { accountSchema, coreSchema, getAuthTables, rateLimitSchema, sessionSchema, userSchema, verificationSchema };
|
package/dist/db/internal.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { BoundedDatabaseIndexDialect, DBTableIndexSource, ResolvedDBTableIndex, getDatabaseFieldIndexName, getDatabaseIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes, resolveDatabaseTableIndexes } from "./database-index.mjs";
|
|
2
2
|
import { getAuthTablesWithResolvedIndexes } from "./get-tables.mjs";
|
|
3
|
-
|
|
3
|
+
import { ExpectedSchema, IntrospectedColumn, IntrospectedTable, SchemaFinding, SchemaMismatchError, SchemaSource, diffSchema, formatSchemaFinding, getExpectedSchema } from "./schema-diff.mjs";
|
|
4
|
+
import { SchemaCheck, checksSchema, createSchemaCheck, invalidateSchemaChecks, registerSchemaCheck, schemaCheckFor } from "./schema-check.mjs";
|
|
5
|
+
export { type BoundedDatabaseIndexDialect, type DBTableIndexSource, type ExpectedSchema, type IntrospectedColumn, type IntrospectedTable, type ResolvedDBTableIndex, type SchemaCheck, type SchemaFinding, SchemaMismatchError, type SchemaSource, checksSchema, createSchemaCheck, diffSchema, formatSchemaFinding, getAuthTablesWithResolvedIndexes, getDatabaseFieldIndexName, getDatabaseIndexName, getDatabaseIndexStringLength, getExpectedSchema, getPortableDatabaseIdentifierKey, invalidateSchemaChecks, registerSchemaCheck, resolveDatabaseSchemaIndexes, resolveDatabaseTableIndexes, schemaCheckFor };
|
package/dist/db/internal.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { getDatabaseFieldIndexName, getDatabaseIndexName, getDatabaseIndexStringLength, getPortableDatabaseIdentifierKey, resolveDatabaseSchemaIndexes, resolveDatabaseTableIndexes } from "./database-index.mjs";
|
|
2
2
|
import { getAuthTablesWithResolvedIndexes } from "./get-tables.mjs";
|
|
3
|
-
|
|
3
|
+
import { SchemaMismatchError, diffSchema, formatSchemaFinding, getExpectedSchema } from "./schema-diff.mjs";
|
|
4
|
+
import { checksSchema, createSchemaCheck, invalidateSchemaChecks, registerSchemaCheck, schemaCheckFor } from "./schema-check.mjs";
|
|
5
|
+
export { SchemaMismatchError, checksSchema, createSchemaCheck, diffSchema, formatSchemaFinding, getAuthTablesWithResolvedIndexes, getDatabaseFieldIndexName, getDatabaseIndexName, getDatabaseIndexStringLength, getExpectedSchema, getPortableDatabaseIdentifierKey, invalidateSchemaChecks, registerSchemaCheck, resolveDatabaseSchemaIndexes, resolveDatabaseTableIndexes, schemaCheckFor };
|
|
@@ -9,7 +9,6 @@ declare const accountSchema: z.ZodObject<{
|
|
|
9
9
|
createdAt: z.ZodDefault<z.ZodDate>;
|
|
10
10
|
updatedAt: z.ZodDefault<z.ZodDate>;
|
|
11
11
|
providerId: z.ZodString;
|
|
12
|
-
issuer: z.ZodString;
|
|
13
12
|
accountId: z.ZodString;
|
|
14
13
|
userId: z.ZodCoercedString<unknown>;
|
|
15
14
|
accessToken: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -22,20 +21,10 @@ declare const accountSchema: z.ZodObject<{
|
|
|
22
21
|
}, z.core.$strip>;
|
|
23
22
|
type BaseAccount = z.infer<typeof accountSchema>;
|
|
24
23
|
/** The stable provider-side key used to recognize an account. */
|
|
25
|
-
type AccountKey = Readonly<Pick<BaseAccount, "
|
|
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;
|
|
24
|
+
type AccountKey = Readonly<Pick<BaseAccount, "providerId" | "accountId">>;
|
|
36
25
|
/**
|
|
37
26
|
* Account schema type used by better-auth, note that it's possible that account could have additional fields
|
|
38
27
|
*/
|
|
39
28
|
type Account<DBOptions extends BetterAuthOptions["account"] = BetterAuthOptions["account"], Plugins extends BetterAuthOptions["plugins"] = BetterAuthOptions["plugins"]> = Prettify<BaseAccount & InferDBFieldsFromOptions<DBOptions> & InferDBFieldsFromPlugins<"account", Plugins>>;
|
|
40
29
|
//#endregion
|
|
41
|
-
export { Account, AccountKey, BaseAccount, accountSchema
|
|
30
|
+
export { Account, AccountKey, BaseAccount, accountSchema };
|
|
@@ -3,7 +3,6 @@ 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
|
-
issuer: z.string(),
|
|
7
6
|
accountId: z.string(),
|
|
8
7
|
userId: z.coerce.string(),
|
|
9
8
|
accessToken: z.string().nullish(),
|
|
@@ -29,22 +28,5 @@ const accountSchema = coreSchema.extend({
|
|
|
29
28
|
*/
|
|
30
29
|
password: z.string().nullish()
|
|
31
30
|
});
|
|
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
|
-
}
|
|
49
31
|
//#endregion
|
|
50
|
-
export { accountSchema
|
|
32
|
+
export { accountSchema };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { BetterAuthOptions } from "../types/init-options.mjs";
|
|
2
|
+
import { SchemaFinding, SchemaSource } from "./schema-diff.mjs";
|
|
3
|
+
//#region src/db/schema-check.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Whether the adapter validates its schema. Enabled in every environment
|
|
6
|
+
* unless explicitly disabled.
|
|
7
|
+
*/
|
|
8
|
+
declare function checksSchema(options: BetterAuthOptions): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Resolves when the schema can hold what Better Auth writes. Returns nothing
|
|
11
|
+
* once that is known and the database schema revision is unchanged.
|
|
12
|
+
*/
|
|
13
|
+
type SchemaCheck = () => Promise<void> | undefined;
|
|
14
|
+
/** Invalidates cached checks after Better Auth changes this database's schema. */
|
|
15
|
+
declare function invalidateSchemaChecks(database: object): void;
|
|
16
|
+
/**
|
|
17
|
+
* Attaches a check to the adapter it verifies. The adapter object itself is
|
|
18
|
+
* left untouched, so this works for adapters Better Auth does not own.
|
|
19
|
+
*/
|
|
20
|
+
declare function registerSchemaCheck(adapter: object, check: SchemaCheck): void;
|
|
21
|
+
/**
|
|
22
|
+
* The check registered for an adapter, if its store is checked at all.
|
|
23
|
+
*/
|
|
24
|
+
declare function schemaCheckFor(adapter: object): SchemaCheck | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Turns a schema comparison into a check shared by one adapter instance.
|
|
27
|
+
*
|
|
28
|
+
* The first call runs `find` and every concurrent call shares that promise. A
|
|
29
|
+
* clean result is cached until invalidation. A mismatch is kept as one
|
|
30
|
+
* {@link SchemaMismatchError} and rethrown on every later call without asking
|
|
31
|
+
* the store again, until a migration invalidates it. When a database identity is supplied,
|
|
32
|
+
* checks for that identity share its schema revision. Pending callers follow
|
|
33
|
+
* the new check if their revision is invalidated. A failure to reach
|
|
34
|
+
* the store is not kept, so the next call asks again.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const checkSchema = createSchemaCheck(
|
|
39
|
+
* () => findSchemaProblems(db, "postgres", expected),
|
|
40
|
+
* "database",
|
|
41
|
+
* );
|
|
42
|
+
* const pending = checkSchema();
|
|
43
|
+
* if (pending) await pending;
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare function createSchemaCheck(find: () => Promise<SchemaFinding[]>, source: SchemaSource, database?: object): SchemaCheck;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { SchemaCheck, checksSchema, createSchemaCheck, invalidateSchemaChecks, registerSchemaCheck, schemaCheckFor };
|