better-auth 1.6.10 → 1.6.12

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 (90) hide show
  1. package/dist/api/index.d.mts +8 -2
  2. package/dist/api/routes/callback.d.mts +1 -1
  3. package/dist/api/routes/callback.mjs +36 -40
  4. package/dist/api/routes/email-verification.d.mts +1 -0
  5. package/dist/api/routes/email-verification.mjs +4 -3
  6. package/dist/api/routes/session.mjs +14 -9
  7. package/dist/api/routes/sign-in.d.mts +1 -0
  8. package/dist/api/routes/sign-in.mjs +2 -1
  9. package/dist/api/routes/sign-up.d.mts +1 -0
  10. package/dist/api/routes/sign-up.mjs +9 -7
  11. package/dist/api/routes/update-user.mjs +5 -5
  12. package/dist/client/index.d.mts +2 -2
  13. package/dist/client/parser.mjs +0 -1
  14. package/dist/client/plugins/index.d.mts +3 -3
  15. package/dist/client/proxy.mjs +2 -1
  16. package/dist/context/helpers.mjs +3 -2
  17. package/dist/cookies/cookie-utils.d.mts +24 -1
  18. package/dist/cookies/cookie-utils.mjs +85 -22
  19. package/dist/cookies/index.d.mts +2 -3
  20. package/dist/cookies/index.mjs +39 -11
  21. package/dist/cookies/session-store.mjs +4 -23
  22. package/dist/db/get-migration.mjs +4 -4
  23. package/dist/db/index.d.mts +2 -2
  24. package/dist/db/index.mjs +3 -2
  25. package/dist/db/internal-adapter.mjs +96 -1
  26. package/dist/db/schema.d.mts +15 -2
  27. package/dist/db/schema.mjs +26 -1
  28. package/dist/db/with-hooks.d.mts +1 -0
  29. package/dist/db/with-hooks.mjs +58 -1
  30. package/dist/index.d.mts +2 -2
  31. package/dist/index.mjs +2 -2
  32. package/dist/oauth2/errors.mjs +16 -1
  33. package/dist/oauth2/link-account.mjs +6 -4
  34. package/dist/oauth2/state.mjs +8 -2
  35. package/dist/package.mjs +1 -1
  36. package/dist/plugins/access/access.d.mts +3 -15
  37. package/dist/plugins/access/access.mjs +11 -6
  38. package/dist/plugins/access/index.d.mts +2 -2
  39. package/dist/plugins/access/types.d.mts +11 -4
  40. package/dist/plugins/admin/access/statement.d.mts +29 -93
  41. package/dist/plugins/admin/admin.mjs +0 -4
  42. package/dist/plugins/admin/client.d.mts +1 -1
  43. package/dist/plugins/admin/routes.mjs +1 -0
  44. package/dist/plugins/anonymous/client.d.mts +1 -0
  45. package/dist/plugins/anonymous/error-codes.d.mts +1 -0
  46. package/dist/plugins/anonymous/error-codes.mjs +1 -0
  47. package/dist/plugins/anonymous/index.d.mts +1 -0
  48. package/dist/plugins/anonymous/index.mjs +16 -2
  49. package/dist/plugins/bearer/index.mjs +4 -9
  50. package/dist/plugins/captcha/index.mjs +2 -2
  51. package/dist/plugins/device-authorization/error-codes.mjs +1 -0
  52. package/dist/plugins/device-authorization/index.d.mts +1 -0
  53. package/dist/plugins/device-authorization/routes.mjs +34 -3
  54. package/dist/plugins/generic-oauth/index.d.mts +1 -1
  55. package/dist/plugins/generic-oauth/index.mjs +6 -6
  56. package/dist/plugins/generic-oauth/routes.mjs +34 -32
  57. package/dist/plugins/generic-oauth/types.d.mts +7 -0
  58. package/dist/plugins/index.d.mts +2 -2
  59. package/dist/plugins/last-login-method/client.mjs +2 -2
  60. package/dist/plugins/magic-link/index.d.mts +8 -1
  61. package/dist/plugins/magic-link/index.mjs +4 -17
  62. package/dist/plugins/mcp/authorize.mjs +8 -2
  63. package/dist/plugins/mcp/index.mjs +73 -34
  64. package/dist/plugins/multi-session/index.mjs +2 -2
  65. package/dist/plugins/oauth-proxy/index.mjs +44 -31
  66. package/dist/plugins/oauth-proxy/utils.mjs +3 -10
  67. package/dist/plugins/oidc-provider/authorize.mjs +8 -2
  68. package/dist/plugins/oidc-provider/index.mjs +63 -37
  69. package/dist/plugins/one-tap/index.mjs +13 -8
  70. package/dist/plugins/open-api/generator.mjs +16 -5
  71. package/dist/plugins/organization/access/statement.d.mts +68 -201
  72. package/dist/plugins/organization/adapter.mjs +61 -56
  73. package/dist/plugins/organization/client.d.mts +3 -1
  74. package/dist/plugins/organization/error-codes.d.mts +2 -0
  75. package/dist/plugins/organization/error-codes.mjs +3 -1
  76. package/dist/plugins/organization/routes/crud-access-control.d.mts +2 -2
  77. package/dist/plugins/organization/routes/crud-invites.mjs +7 -2
  78. package/dist/plugins/organization/types.d.mts +12 -2
  79. package/dist/plugins/two-factor/index.mjs +3 -2
  80. package/dist/plugins/username/index.d.mts +24 -2
  81. package/dist/plugins/username/index.mjs +49 -3
  82. package/dist/state.d.mts +2 -2
  83. package/dist/state.mjs +18 -4
  84. package/dist/test-utils/headers.mjs +2 -7
  85. package/dist/test-utils/test-instance.d.mts +25 -6
  86. package/dist/test-utils/test-instance.mjs +11 -2
  87. package/dist/utils/index.d.mts +1 -1
  88. package/dist/utils/url.d.mts +2 -1
  89. package/dist/utils/url.mjs +9 -3
  90. package/package.json +15 -14
@@ -1,5 +1,6 @@
1
1
  //#region src/cookies/cookie-utils.ts
2
2
  function tryDecode(str) {
3
+ if (str.indexOf("%") === -1) return str;
3
4
  try {
4
5
  return decodeURIComponent(str);
5
6
  } catch {
@@ -49,9 +50,9 @@ function parseSetCookieHeader(setCookie) {
49
50
  splitSetCookieHeader(setCookie).forEach((cookieString) => {
50
51
  const [nameValue, ...attributes] = cookieString.split(";").map((part) => part.trim());
51
52
  const [name, ...valueParts] = (nameValue || "").split("=");
52
- const value = valueParts.join("=");
53
- if (!name || value === void 0) return;
54
- const attrObj = { value: value.includes("%") ? tryDecode(value) : value };
53
+ const value = unquoteCookieValue(valueParts.join("="));
54
+ if (!name) return;
55
+ const attrObj = { value: tryDecode(value) };
55
56
  attributes.forEach((attribute) => {
56
57
  const [attrName, ...attrValueParts] = attribute.split("=");
57
58
  const attrValue = attrValueParts.join("=");
@@ -103,6 +104,69 @@ function toCookieOptions(attributes) {
103
104
  };
104
105
  }
105
106
  /**
107
+ * Cookie-name token char set per RFC 7230 §3.2.6.
108
+ *
109
+ * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
110
+ */
111
+ const cookieNameRegex = /^[\w!#$%&'*.^`|~+-]+$/;
112
+ /**
113
+ * Cookie-value char set per RFC 6265 §4.1.1, plus space and comma.
114
+ *
115
+ * @see https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1
116
+ * @see https://github.com/golang/go/issues/7243
117
+ */
118
+ const cookieValueRegex = /^[ !#-:<-[\]-~]*$/;
119
+ /**
120
+ * Strip surrounding double-quotes per RFC 6265 §4.1.1 quoted-string form.
121
+ *
122
+ * @see https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1
123
+ */
124
+ function unquoteCookieValue(value) {
125
+ if (value.length < 2 || !value.startsWith("\"") || !value.endsWith("\"")) return value;
126
+ return value.slice(1, -1);
127
+ }
128
+ /**
129
+ * Trim leading/trailing OWS (space / horizontal tab) per RFC 7230 §3.2.3.
130
+ * Narrower than `String.prototype.trim()`, which strips CR/LF and other
131
+ * whitespace and would let CTLs escape `cookieValueRegex`.
132
+ *
133
+ * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.3
134
+ */
135
+ function trimOWS(s) {
136
+ let start = 0;
137
+ let end = s.length;
138
+ while (start < end) {
139
+ const c = s.charCodeAt(start);
140
+ if (c !== 32 && c !== 9) break;
141
+ start++;
142
+ }
143
+ while (end > start) {
144
+ const c = s.charCodeAt(end - 1);
145
+ if (c !== 32 && c !== 9) break;
146
+ end--;
147
+ }
148
+ return start === 0 && end === s.length ? s : s.slice(start, end);
149
+ }
150
+ /**
151
+ * Tolerates `;` separators without the SP that RFC 6265 §4.2.1 mandates,
152
+ * since proxies and runtimes commonly strip it. Silently drops entries
153
+ * whose name violates RFC 7230 token or whose value violates RFC 6265
154
+ * cookie-octet (plus space and comma). Strips optional surrounding
155
+ * double-quotes per RFC 6265 §4.1.1.
156
+ */
157
+ function parseCookies(cookie) {
158
+ const cookieMap = /* @__PURE__ */ new Map();
159
+ if (cookie.length < 2) return cookieMap;
160
+ for (const chunk of cookie.split(";")) {
161
+ const eq = chunk.indexOf("=");
162
+ if (eq === -1) continue;
163
+ const key = trimOWS(chunk.slice(0, eq));
164
+ const val = unquoteCookieValue(trimOWS(chunk.slice(eq + 1)));
165
+ if (cookieNameRegex.test(key) && cookieValueRegex.test(val)) cookieMap.set(key, tryDecode(val));
166
+ }
167
+ return cookieMap;
168
+ }
169
+ /**
106
170
  * Add or replace a cookie in the request `Cookie` header.
107
171
  *
108
172
  * Cookie pairs are joined with `; `, but `headers.append("cookie", ...)`
@@ -111,30 +175,29 @@ function toCookieOptions(attributes) {
111
175
  * parse-mutate-serialize.
112
176
  */
113
177
  function setRequestCookie(headers, name, value) {
114
- const cookieMap = /* @__PURE__ */ new Map();
115
- for (const pair of (headers.get("cookie") || "").split(";")) {
116
- const trimmed = pair.trim();
117
- const eq = trimmed.indexOf("=");
118
- if (eq > 0) cookieMap.set(trimmed.slice(0, eq), trimmed.slice(eq + 1));
119
- }
120
- cookieMap.set(name, value);
121
- headers.set("cookie", Array.from(cookieMap, ([k, v]) => `${k}=${v}`).join("; "));
178
+ const cookieMap = parseCookies(headers.get("cookie") || "");
179
+ if (cookieNameRegex.test(name)) cookieMap.set(name, value);
180
+ headers.set("cookie", Array.from(cookieMap, ([k, v]) => `${k}=${encodeURIComponent(v)}`).join("; "));
181
+ }
182
+ /**
183
+ * Merge `Set-Cookie` header values into the target's `Cookie` header.
184
+ * Mutates `target`.
185
+ *
186
+ * Name/value-level merge only. RFC 6265 §5 user-agent semantics
187
+ * (expiration, domain/path scoping, ordering) are out of scope. Suitable
188
+ * for single-request proxy, middleware, and test contexts.
189
+ */
190
+ function applySetCookies(target, setCookieValues) {
191
+ const cookieMap = parseCookies(target.get("cookie") || "");
192
+ for (const setCookie of setCookieValues) for (const [name, attr] of parseSetCookieHeader(setCookie)) if (cookieNameRegex.test(name)) cookieMap.set(name, attr.value);
193
+ target.set("cookie", Array.from(cookieMap, ([k, v]) => `${k}=${encodeURIComponent(v)}`).join("; "));
122
194
  }
123
195
  function setCookieToHeader(headers) {
124
196
  return (context) => {
125
197
  const setCookieHeader = context.response.headers.get("set-cookie");
126
198
  if (!setCookieHeader) return;
127
- const cookieMap = /* @__PURE__ */ new Map();
128
- (headers.get("cookie") || "").split(";").forEach((cookie) => {
129
- const [name, ...rest] = cookie.trim().split("=");
130
- if (name && rest.length > 0) cookieMap.set(name, rest.join("="));
131
- });
132
- parseSetCookieHeader(setCookieHeader).forEach((value, name) => {
133
- cookieMap.set(name, value.value);
134
- });
135
- const updatedCookies = Array.from(cookieMap.entries()).map(([name, value]) => `${name}=${value}`).join("; ");
136
- headers.set("cookie", updatedCookies);
199
+ applySetCookies(headers, [setCookieHeader]);
137
200
  };
138
201
  }
139
202
  //#endregion
140
- export { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
203
+ export { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, applySetCookies, cookieNameRegex, parseCookies, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
@@ -1,5 +1,5 @@
1
1
  import { Session, User } from "../types/models.mjs";
2
- import { CookieAttributes, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions } from "./cookie-utils.mjs";
2
+ import { CookieAttributes, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, applySetCookies, cookieNameRegex, parseCookies, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions } from "./cookie-utils.mjs";
3
3
  import { createSessionStore, getAccountCookie, getChunkedCookie } from "./session-store.mjs";
4
4
  import { BetterAuthCookie, BetterAuthCookies, BetterAuthOptions, GenericEndpointContext } from "@better-auth/core";
5
5
  import * as better_call0 from "better-call";
@@ -95,7 +95,6 @@ declare function setSessionCookie(ctx: GenericEndpointContext, session: {
95
95
  */
96
96
  declare function expireCookie(ctx: GenericEndpointContext, cookie: BetterAuthCookie): void;
97
97
  declare function deleteSessionCookie(ctx: GenericEndpointContext, skipDontRememberMe?: boolean | undefined): void;
98
- declare function parseCookies(cookieHeader: string): Map<string, string>;
99
98
  type EligibleCookies = (string & {}) | (keyof BetterAuthCookies & {});
100
99
  declare const getSessionCookie: (request: Request | Headers, config?: {
101
100
  cookiePrefix?: string;
@@ -116,4 +115,4 @@ declare const getCookieCache: <S extends {
116
115
  version?: string | ((session: Session & Record<string, any>, user: User & Record<string, any>) => string) | ((session: Session & Record<string, any>, user: User & Record<string, any>) => Promise<string>);
117
116
  } | undefined) => Promise<S | null>;
118
117
  //#endregion
119
- export { CookieAttributes, EligibleCookies, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, createCookieGetter, createSessionStore, deleteSessionCookie, expireCookie, getAccountCookie, getChunkedCookie, getCookieCache, getCookies, getSessionCookie, parseCookies, parseSetCookieHeader, setCookieCache, setCookieToHeader, setRequestCookie, setSessionCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
118
+ export { CookieAttributes, EligibleCookies, HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, applySetCookies, cookieNameRegex, createCookieGetter, createSessionStore, deleteSessionCookie, expireCookie, getAccountCookie, getChunkedCookie, getCookieCache, getCookies, getSessionCookie, parseCookies, parseSetCookieHeader, setCookieCache, setCookieToHeader, setRequestCookie, setSessionCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
@@ -4,7 +4,7 @@ import { parseUserOutput } from "../db/schema.mjs";
4
4
  import { getDate } from "../utils/date.mjs";
5
5
  import { isPromise } from "../utils/is-promise.mjs";
6
6
  import { sec } from "../utils/time.mjs";
7
- import { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions } from "./cookie-utils.mjs";
7
+ import { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, applySetCookies, cookieNameRegex, parseCookies, parseSetCookieHeader, setCookieToHeader, setRequestCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions } from "./cookie-utils.mjs";
8
8
  import { createAccountStore, createSessionStore, getAccountCookie, getChunkedCookie, setAccountCookie } from "./session-store.mjs";
9
9
  import { env, isProduction } from "@better-auth/core/env";
10
10
  import { BetterAuthError } from "@better-auth/core/error";
@@ -134,9 +134,46 @@ async function setSessionCookie(ctx, session, dontRememberMe, overrides) {
134
134
  ctx.context.setNewSession(session);
135
135
  }
136
136
  /**
137
+ * Remove any prior `Set-Cookie` entries on the current response whose cookie
138
+ * name matches `cookieName` or any chunked variant (`${cookieName}.0`, etc.).
139
+ *
140
+ * Prevents a valid cookie value from leaking on the wire when the same cookie
141
+ * is set and then expired within a single request (e.g. `/sign-in/email`
142
+ * writes credential session cookies and the 2FA after-hook expires them).
143
+ * Browsers honor the expiring entry, but anything reading the raw response
144
+ * headers — proxy/LB logs, server-side SDK consumers, observability tools —
145
+ * sees the earlier valid value and could replay it (bypassing the 2FA gate
146
+ * when the cookie cache is enabled).
147
+ *
148
+ * Scrubs both the local middleware scope's `responseHeaders` and the outer
149
+ * endpoint scope's `ctx.context.responseHeaders`, because plugin after-hooks
150
+ * run in a fresh local scope while accumulated response headers live on the
151
+ * outer one. `scoped.context` is required by {@link GenericEndpointContext}
152
+ * but unit-test mocks pass a minimal object via `as any`, so we use optional
153
+ * chaining defensively. The `Set` collapses the case where both scopes
154
+ * reference the same `Headers`.
155
+ */
156
+ function removeSetCookieEntries(ctx, cookieName) {
157
+ const scoped = ctx;
158
+ const targets = /* @__PURE__ */ new Set();
159
+ if (scoped.responseHeaders) targets.add(scoped.responseHeaders);
160
+ if (scoped.context?.responseHeaders) targets.add(scoped.context.responseHeaders);
161
+ const exact = `${cookieName}=`;
162
+ const chunk = `${cookieName}.`;
163
+ for (const headers of targets) {
164
+ const existing = typeof headers.getSetCookie === "function" ? headers.getSetCookie() : splitSetCookieHeader(headers.get("set-cookie") || "");
165
+ if (!existing.length) continue;
166
+ const survivors = existing.filter((entry) => !entry.startsWith(exact) && !entry.startsWith(chunk));
167
+ if (survivors.length === existing.length) continue;
168
+ headers.delete("set-cookie");
169
+ for (const entry of survivors) headers.append("set-cookie", entry);
170
+ }
171
+ }
172
+ /**
137
173
  * Expires a cookie by setting `maxAge: 0` while preserving its attributes
138
174
  */
139
175
  function expireCookie(ctx, cookie) {
176
+ removeSetCookieEntries(ctx, cookie.name);
140
177
  ctx.setCookie(cookie.name, "", {
141
178
  ...cookie.attributes,
142
179
  maxAge: 0
@@ -157,15 +194,6 @@ function deleteSessionCookie(ctx, skipDontRememberMe) {
157
194
  sessionStore.setCookies(cleanCookies);
158
195
  if (!skipDontRememberMe) expireCookie(ctx, ctx.context.authCookies.dontRememberToken);
159
196
  }
160
- function parseCookies(cookieHeader) {
161
- const cookies = cookieHeader.split("; ");
162
- const cookieMap = /* @__PURE__ */ new Map();
163
- cookies.forEach((cookie) => {
164
- const [name, value] = cookie.split(/=(.*)/s);
165
- cookieMap.set(name, value);
166
- });
167
- return cookieMap;
168
- }
169
197
  const getSessionCookie = (request, config) => {
170
198
  const cookies = (request instanceof Headers || !("headers" in request) ? request : request.headers).get("cookie");
171
199
  if (!cookies) return null;
@@ -258,4 +286,4 @@ const getCookieCache = async (request, config) => {
258
286
  return null;
259
287
  };
260
288
  //#endregion
261
- export { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, createCookieGetter, createSessionStore, deleteSessionCookie, expireCookie, getAccountCookie, getChunkedCookie, getCookieCache, getCookies, getSessionCookie, parseCookies, parseSetCookieHeader, setCookieCache, setCookieToHeader, setRequestCookie, setSessionCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
289
+ export { HOST_COOKIE_PREFIX, SECURE_COOKIE_PREFIX, applySetCookies, cookieNameRegex, createCookieGetter, createSessionStore, deleteSessionCookie, expireCookie, getAccountCookie, getChunkedCookie, getCookieCache, getCookies, getSessionCookie, parseCookies, parseSetCookieHeader, setCookieCache, setCookieToHeader, setRequestCookie, setSessionCookie, splitSetCookieHeader, stripSecureCookiePrefix, toCookieOptions };
@@ -1,4 +1,5 @@
1
1
  import { symmetricDecodeJWT, symmetricEncodeJWT } from "../crypto/jwt.mjs";
2
+ import { parseCookies } from "./cookie-utils.mjs";
2
3
  import { safeJSONParse } from "@better-auth/core/utils/json";
3
4
  import * as z from "zod";
4
5
  //#region src/cookies/session-store.ts
@@ -6,20 +7,6 @@ const ALLOWED_COOKIE_SIZE = 4096;
6
7
  const ESTIMATED_EMPTY_COOKIE_SIZE = 200;
7
8
  const CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE;
8
9
  /**
9
- * Parse cookies from the request headers
10
- */
11
- function parseCookiesFromContext(ctx) {
12
- const cookieHeader = ctx.headers?.get("cookie");
13
- if (!cookieHeader) return {};
14
- const cookies = {};
15
- const pairs = cookieHeader.split("; ");
16
- for (const pair of pairs) {
17
- const [name, ...valueParts] = pair.split("=");
18
- if (name && valueParts.length > 0) cookies[name] = valueParts.join("=");
19
- }
20
- return cookies;
21
- }
22
- /**
23
10
  * Extract the chunk index from a cookie name
24
11
  */
25
12
  function getChunkIndex(cookieName) {
@@ -33,8 +20,8 @@ function getChunkIndex(cookieName) {
33
20
  */
34
21
  function readExistingChunks(cookieName, ctx) {
35
22
  const chunks = {};
36
- const cookies = parseCookiesFromContext(ctx);
37
- for (const [name, value] of Object.entries(cookies)) if (name.startsWith(cookieName)) chunks[name] = value;
23
+ const cookies = parseCookies(ctx.headers?.get("cookie") || "");
24
+ for (const [name, value] of cookies) if (name.startsWith(cookieName)) chunks[name] = value;
38
25
  return chunks;
39
26
  }
40
27
  /**
@@ -140,13 +127,7 @@ function getChunkedCookie(ctx, cookieName) {
140
127
  const chunks = [];
141
128
  const cookieHeader = ctx.headers?.get("cookie");
142
129
  if (!cookieHeader) return null;
143
- const cookies = {};
144
- const pairs = cookieHeader.split("; ");
145
- for (const pair of pairs) {
146
- const [name, ...valueParts] = pair.split("=");
147
- if (name && valueParts.length > 0) cookies[name] = valueParts.join("=");
148
- }
149
- for (const [name, val] of Object.entries(cookies)) if (name.startsWith(cookieName + ".")) {
130
+ for (const [name, val] of parseCookies(cookieHeader)) if (name.startsWith(cookieName + ".")) {
150
131
  const indexStr = name.split(".").at(-1);
151
132
  const index = parseInt(indexStr || "0", 10);
152
133
  if (!isNaN(index)) chunks.push({
@@ -269,13 +269,14 @@ async function getMigrations(config) {
269
269
  return `${model}.${field}`;
270
270
  }
271
271
  }
272
+ const deferredIndexes = [];
272
273
  if (toBeAdded.length) for (const table of toBeAdded) for (const [fieldName, field] of Object.entries(table.fields)) {
273
274
  const type = getType(field, fieldName);
274
275
  const builder = db.schema.alterTable(table.table);
275
276
  if (field.index) {
276
277
  const indexName = `${table.table}_${fieldName}_${field.unique ? "uidx" : "idx"}`;
277
278
  const indexBuilder = db.schema.createIndex(indexName).on(table.table).columns([fieldName]);
278
- migrations.push(field.unique ? indexBuilder.unique() : indexBuilder);
279
+ deferredIndexes.push(field.unique ? indexBuilder.unique() : indexBuilder);
279
280
  }
280
281
  const built = builder.addColumn(fieldName, type, (col) => {
281
282
  col = field.required !== false ? col.notNull() : col;
@@ -287,7 +288,6 @@ async function getMigrations(config) {
287
288
  });
288
289
  migrations.push(built);
289
290
  }
290
- const toBeIndexed = [];
291
291
  if (toBeCreated.length) for (const table of toBeCreated) {
292
292
  const idType = getType({ type: useNumberId ? "number" : "string" }, "id");
293
293
  let dbT = db.schema.createTable(table.table).addColumn("id", idType, (col) => {
@@ -315,12 +315,12 @@ async function getMigrations(config) {
315
315
  });
316
316
  if (field.index) {
317
317
  const builder = db.schema.createIndex(`${table.table}_${fieldName}_${field.unique ? "uidx" : "idx"}`).on(table.table).columns([fieldName]);
318
- toBeIndexed.push(field.unique ? builder.unique() : builder);
318
+ deferredIndexes.push(field.unique ? builder.unique() : builder);
319
319
  }
320
320
  }
321
321
  migrations.push(dbT);
322
322
  }
323
- if (toBeIndexed.length) for (const index of toBeIndexed) migrations.push(index);
323
+ for (const index of deferredIndexes) migrations.push(index);
324
324
  async function runMigrations() {
325
325
  for (const migration of migrations) await migration.execute();
326
326
  }
@@ -3,7 +3,7 @@ import { convertFromDB, convertToDB } from "./field-converter.mjs";
3
3
  import { getSchema } from "./get-schema.mjs";
4
4
  import { DatabaseHooksEntry, getWithHooks } from "./with-hooks.mjs";
5
5
  import { createInternalAdapter } from "./internal-adapter.mjs";
6
- import { getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
6
+ import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
7
7
  import { FieldAttributeToSchema, toZodSchema } from "./to-zod.mjs";
8
8
  export * from "@better-auth/core/db";
9
- export { DatabaseHooksEntry, FieldAttributeToObject, FieldAttributeToSchema, InferAdditionalFieldsFromPluginOptions, InferFieldsInputClient, InferFieldsOutput, RemoveFieldsWithReturnedFalse, convertFromDB, convertToDB, createInternalAdapter, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
9
+ export { DatabaseHooksEntry, FieldAttributeToObject, FieldAttributeToSchema, InferAdditionalFieldsFromPluginOptions, InferFieldsInputClient, InferFieldsOutput, RemoveFieldsWithReturnedFalse, buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
package/dist/db/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { __exportAll, __reExport } from "../_virtual/_rolldown/runtime.mjs";
2
2
  import { getSchema } from "./get-schema.mjs";
3
- import { getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
3
+ import { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput } from "./schema.mjs";
4
4
  import { convertFromDB, convertToDB } from "./field-converter.mjs";
5
5
  import { getWithHooks } from "./with-hooks.mjs";
6
6
  import { createInternalAdapter } from "./internal-adapter.mjs";
@@ -8,6 +8,7 @@ import { toZodSchema } from "./to-zod.mjs";
8
8
  export * from "@better-auth/core/db";
9
9
  //#region src/db/index.ts
10
10
  var db_exports = /* @__PURE__ */ __exportAll({
11
+ buildSyntheticUserOutput: () => buildSyntheticUserOutput,
11
12
  convertFromDB: () => convertFromDB,
12
13
  convertToDB: () => convertToDB,
13
14
  createInternalAdapter: () => createInternalAdapter,
@@ -28,4 +29,4 @@ var db_exports = /* @__PURE__ */ __exportAll({
28
29
  import * as import__better_auth_core_db from "@better-auth/core/db";
29
30
  __reExport(db_exports, import__better_auth_core_db);
30
31
  //#endregion
31
- export { convertFromDB, convertToDB, createInternalAdapter, db_exports, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
32
+ export { buildSyntheticUserOutput, convertFromDB, convertToDB, createInternalAdapter, db_exports, getSchema, getSessionDefaultFields, getWithHooks, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput, toZodSchema };
@@ -15,8 +15,9 @@ const createInternalAdapter = (adapter, ctx) => {
15
15
  const logger = ctx.logger;
16
16
  const options = ctx.options;
17
17
  const secondaryStorage = options.secondaryStorage;
18
+ const verificationConsumeLocks = /* @__PURE__ */ new Map();
18
19
  const sessionExpiration = options.session?.expiresIn || 3600 * 24 * 7;
19
- const { createWithHooks, updateWithHooks, updateManyWithHooks, deleteWithHooks, deleteManyWithHooks } = getWithHooks(adapter, ctx);
20
+ const { createWithHooks, updateWithHooks, updateManyWithHooks, deleteWithHooks, deleteManyWithHooks, consumeOneWithHooks } = getWithHooks(adapter, ctx);
20
21
  async function refreshUserSessions(user) {
21
22
  if (!secondaryStorage) return;
22
23
  const listRaw = await secondaryStorage.get(`active-sessions-${user.id}`);
@@ -35,6 +36,22 @@ const createInternalAdapter = (adapter, ctx) => {
35
36
  }), Math.floor(sessionTTL));
36
37
  }));
37
38
  }
39
+ async function withVerificationConsumeLock(key, fn) {
40
+ const previous = verificationConsumeLocks.get(key) ?? Promise.resolve();
41
+ let release;
42
+ const current = new Promise((resolve) => {
43
+ release = resolve;
44
+ });
45
+ const next = previous.catch(() => {}).then(() => current);
46
+ verificationConsumeLocks.set(key, next);
47
+ await previous.catch(() => {});
48
+ try {
49
+ return await fn();
50
+ } finally {
51
+ release();
52
+ if (verificationConsumeLocks.get(key) === next) verificationConsumeLocks.delete(key);
53
+ }
54
+ }
38
55
  return {
39
56
  createOAuthUser: async (user, account) => {
40
57
  return runWithTransaction(adapter, async () => {
@@ -618,6 +635,84 @@ const createInternalAdapter = (adapter, ctx) => {
618
635
  value: storedIdentifier
619
636
  }], "verification", void 0);
620
637
  },
638
+ consumeVerificationValue: async (identifier) => {
639
+ const storageOption = getStorageOption(identifier, options.verification?.storeIdentifier);
640
+ const storedIdentifier = await processIdentifier(identifier, storageOption);
641
+ const identifiersToTry = storageOption && storageOption !== "plain" ? [storedIdentifier, identifier] : [storedIdentifier];
642
+ const hydrateCachedVerification = (raw) => {
643
+ if (!raw) return null;
644
+ const candidate = typeof raw === "string" ? safeJSONParse(raw) : typeof raw === "object" ? raw : null;
645
+ if (!candidate) return null;
646
+ const expiresAt = new Date(candidate.expiresAt);
647
+ if (!Number.isFinite(expiresAt.getTime())) return null;
648
+ return {
649
+ ...candidate,
650
+ expiresAt
651
+ };
652
+ };
653
+ let consumed = null;
654
+ if (secondaryStorage && !options.verification?.storeInDatabase) {
655
+ const consumeCacheKey = async (key) => {
656
+ if (secondaryStorage.getAndDelete) return hydrateCachedVerification(await secondaryStorage.getAndDelete(key));
657
+ return withVerificationConsumeLock(key, async () => {
658
+ const parsed = hydrateCachedVerification(await secondaryStorage.get(key));
659
+ if (!parsed) return null;
660
+ await secondaryStorage.delete(key);
661
+ return parsed;
662
+ });
663
+ };
664
+ for (const stored of identifiersToTry) {
665
+ const cached = await consumeCacheKey(`verification:${stored}`);
666
+ if (!cached) continue;
667
+ await Promise.all(identifiersToTry.filter((candidate) => candidate !== stored).map((candidate) => secondaryStorage.delete(`verification:${candidate}`)));
668
+ consumed = cached;
669
+ break;
670
+ }
671
+ } else {
672
+ const consumeByIdentifier = async (id) => withVerificationConsumeLock(`verification:${id}`, () => runWithTransaction(adapter, async () => {
673
+ const txAdapter = await getCurrentAdapter(adapter);
674
+ const where = [{
675
+ field: "identifier",
676
+ value: id
677
+ }];
678
+ const latest = (await txAdapter.findMany({
679
+ model: "verification",
680
+ where,
681
+ sortBy: {
682
+ field: "createdAt",
683
+ direction: "desc"
684
+ },
685
+ limit: 1
686
+ }))[0] ?? null;
687
+ if (!latest) return null;
688
+ return consumeOneWithHooks("verification", [{
689
+ field: "id",
690
+ value: latest.id
691
+ }], async () => {
692
+ const row = await txAdapter.consumeOne({
693
+ model: "verification",
694
+ where: [{
695
+ field: "id",
696
+ value: latest.id
697
+ }]
698
+ });
699
+ if (!row) return null;
700
+ await txAdapter.deleteMany({
701
+ model: "verification",
702
+ where
703
+ });
704
+ return row;
705
+ }, latest);
706
+ }));
707
+ for (const stored of identifiersToTry) {
708
+ consumed = await consumeByIdentifier(stored);
709
+ if (consumed) break;
710
+ }
711
+ if (consumed && secondaryStorage) await Promise.all(identifiersToTry.map((stored) => secondaryStorage.delete(`verification:${stored}`)));
712
+ }
713
+ if (!consumed || consumed.expiresAt < /* @__PURE__ */ new Date()) return null;
714
+ return consumed;
715
+ },
621
716
  updateVerificationByIdentifier: async (identifier, data) => {
622
717
  const storedIdentifier = await processIdentifier(identifier, getStorageOption(identifier, options.verification?.storeIdentifier));
623
718
  if (secondaryStorage) {
@@ -4,8 +4,21 @@ import { BetterAuthPluginDBSchema, DBFieldAttribute } from "@better-auth/core/db
4
4
 
5
5
  //#region src/db/schema.d.ts
6
6
  declare function parseUserOutput<T extends User$1>(options: BetterAuthOptions, user: T): T;
7
+ /**
8
+ * Builds a synthetic user object that matches the shape of a real user
9
+ * returned from the database. This ensures enumeration protection works
10
+ * correctly by making synthetic and real user responses indistinguishable.
11
+ *
12
+ * The function iterates over the user output schema and:
13
+ * - Includes all fields that should be returned (returned !== false)
14
+ * - Uses provided values when available
15
+ * - Sets optional fields to null when no value is provided
16
+ * - Applies default values where defined
17
+ * - Always includes the 'id' field (not part of schema but always present)
18
+ */
19
+ declare function buildSyntheticUserOutput(options: BetterAuthOptions, data: Record<string, unknown>): Record<string, unknown>;
7
20
  declare function parseSessionOutput<T extends Session$1>(options: BetterAuthOptions, session: T): T;
8
- declare function parseAccountOutput<T extends Account>(options: BetterAuthOptions, account: T): Omit<T, "idToken" | "accessToken" | "refreshToken" | "accessTokenExpiresAt" | "refreshTokenExpiresAt" | "password">;
21
+ declare function parseAccountOutput<T extends Account>(options: BetterAuthOptions, account: T): Omit<T, "idToken" | "accessToken" | "refreshToken" | "password" | "accessTokenExpiresAt" | "refreshTokenExpiresAt">;
9
22
  declare function parseInputData<T extends Record<string, any>>(data: T, schema: {
10
23
  fields: Record<string, DBFieldAttribute>;
11
24
  action?: ("create" | "update") | undefined;
@@ -45,4 +58,4 @@ declare function mergeSchema<S extends BetterAuthPluginDBSchema>(schema: S, newS
45
58
  } | undefined;
46
59
  } | undefined } | undefined): S;
47
60
  //#endregion
48
- export { getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
61
+ export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
@@ -24,6 +24,31 @@ function getFields(options, modelName, mode) {
24
24
  function parseUserOutput(options, user) {
25
25
  return filterOutputFields(user, getFields(options, "user", "output"));
26
26
  }
27
+ /**
28
+ * Builds a synthetic user object that matches the shape of a real user
29
+ * returned from the database. This ensures enumeration protection works
30
+ * correctly by making synthetic and real user responses indistinguishable.
31
+ *
32
+ * The function iterates over the user output schema and:
33
+ * - Includes all fields that should be returned (returned !== false)
34
+ * - Uses provided values when available
35
+ * - Sets optional fields to null when no value is provided
36
+ * - Applies default values where defined
37
+ * - Always includes the 'id' field (not part of schema but always present)
38
+ */
39
+ function buildSyntheticUserOutput(options, data) {
40
+ const schema = getFields(options, "user", "output");
41
+ const result = {};
42
+ for (const key in schema) {
43
+ const fieldAttr = schema[key];
44
+ if (fieldAttr.returned === false) continue;
45
+ if (key in data && data[key] !== void 0) result[key] = data[key];
46
+ else if (fieldAttr.defaultValue !== void 0) result[key] = typeof fieldAttr.defaultValue === "function" ? fieldAttr.defaultValue() : fieldAttr.defaultValue;
47
+ else if (!fieldAttr.required) result[key] = null;
48
+ }
49
+ if ("id" in data) result.id = data.id;
50
+ return result;
51
+ }
27
52
  function parseSessionOutput(options, session) {
28
53
  return filterOutputFields(session, getFields(options, "session", "output"));
29
54
  }
@@ -121,4 +146,4 @@ function mergeSchema(schema, newSchema) {
121
146
  return schema;
122
147
  }
123
148
  //#endregion
124
- export { getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
149
+ export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
@@ -31,6 +31,7 @@ declare function getWithHooks(adapter: DBAdapter<BetterAuthOptions>, ctx: {
31
31
  fn: (where: Where[]) => void | Promise<any>;
32
32
  executeMainFn?: boolean;
33
33
  } | undefined) => Promise<any>;
34
+ consumeOneWithHooks: <T extends Record<string, any>>(model: BaseModelNames, hookWhere: Where[], consumeFn: () => Promise<T | null>, preSnapshot?: T | null) => Promise<T | null>;
34
35
  };
35
36
  //#endregion
36
37
  export { DatabaseHooksEntry, getWithHooks };
@@ -185,12 +185,69 @@ function getWithHooks(adapter, ctx) {
185
185
  }
186
186
  return deleted;
187
187
  }
188
+ /**
189
+ * Wraps an atomic consume operation in the plugin `delete.before` and
190
+ * `delete.after` hook lifecycle. The caller supplies a `consumeFn` that
191
+ * performs the actual single-row delete-and-return (typically the
192
+ * adapter's `consumeOne`). The first concurrent caller wins, subsequent
193
+ * racers resolve to `null` without firing `delete.after` hooks.
194
+ *
195
+ * `preSnapshot` lets the caller hand in a row it already fetched so
196
+ * `delete.before` hooks don't trigger a second read. Without it, the
197
+ * helper falls back to a best-effort `findMany` against `hookWhere`.
198
+ * The snapshot only feeds `delete.before`; the `consumeFn` return value
199
+ * is the race gate.
200
+ *
201
+ * Returning `false` from a `delete.before` hook aborts the consume and
202
+ * the helper resolves to `null` (no `consumeFn` call, no after hooks).
203
+ */
204
+ async function consumeOneWithHooks(model, hookWhere, consumeFn, preSnapshot) {
205
+ const context = await getCurrentAuthContext().catch(() => null);
206
+ const beforeHooks = hooksEntries.flatMap(({ source, hooks }) => {
207
+ const fn = hooks[model]?.delete?.before;
208
+ return fn ? [{
209
+ source,
210
+ fn
211
+ }] : [];
212
+ });
213
+ let snapshot = preSnapshot ?? null;
214
+ if (beforeHooks.length) {
215
+ if (!snapshot) try {
216
+ snapshot = (await (await getCurrentAdapter(adapter)).findMany({
217
+ model,
218
+ where: hookWhere,
219
+ limit: 1
220
+ }))[0] || null;
221
+ } catch {}
222
+ if (snapshot) {
223
+ for (const { source, fn } of beforeHooks) if (await withSpan(`db delete.before ${model}`, {
224
+ [ATTR_HOOK_TYPE]: "delete.before",
225
+ [ATTR_DB_COLLECTION_NAME]: model,
226
+ [ATTR_CONTEXT]: source
227
+ }, () => fn(snapshot, context)) === false) return null;
228
+ }
229
+ }
230
+ const consumed = await consumeFn();
231
+ if (!consumed) return null;
232
+ for (const { source, hooks } of hooksEntries) {
233
+ const toRun = hooks[model]?.delete?.after;
234
+ if (toRun) await queueAfterTransactionHook(async () => {
235
+ await withSpan(`db delete.after ${model}`, {
236
+ [ATTR_HOOK_TYPE]: "delete.after",
237
+ [ATTR_DB_COLLECTION_NAME]: model,
238
+ [ATTR_CONTEXT]: source
239
+ }, () => toRun(consumed, context));
240
+ });
241
+ }
242
+ return consumed;
243
+ }
188
244
  return {
189
245
  createWithHooks,
190
246
  updateWithHooks,
191
247
  updateManyWithHooks,
192
248
  deleteWithHooks,
193
- deleteManyWithHooks
249
+ deleteManyWithHooks,
250
+ consumeOneWithHooks
194
251
  };
195
252
  }
196
253
  //#endregion
package/dist/index.d.mts CHANGED
@@ -10,7 +10,7 @@ import { betterAuth } from "./auth/full.mjs";
10
10
  import { generateState, parseState } from "./oauth2/state.mjs";
11
11
  import { StateData, generateGenericState, parseGenericState } from "./state.mjs";
12
12
  import { HIDE_METADATA } from "./utils/hide-metadata.mjs";
13
- import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./utils/url.mjs";
13
+ import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL, trimTrailingSlashes } from "./utils/url.mjs";
14
14
  import { APIError } from "./api/index.mjs";
15
15
  import { StandardSchemaV1 } from "@better-auth/core";
16
16
  import { getCurrentAdapter } from "@better-auth/core/context";
@@ -27,4 +27,4 @@ export * from "@better-auth/core/utils/json";
27
27
  export * from "@better-auth/core/social-providers";
28
28
  export * from "better-call";
29
29
  export * from "zod";
30
- export { APIError, Account, AdditionalSessionFieldsInput, AdditionalUserFieldsInput, Auth, BetterAuthAdvancedOptions, BetterAuthClientOptions, BetterAuthClientPlugin, BetterAuthCookies, BetterAuthOptions, BetterAuthPlugin, BetterAuthRateLimitOptions, ClientAtomListener, ClientStore, DBAdapter, DBAdapterInstance, DBAdapterSchemaCreation, DBTransactionAdapter, ExtractPluginField, FilteredAPI, HIDE_METADATA, HasRequiredKeys, InferAPI, InferActions, InferAdditionalFromClient, InferClientAPI, InferErrorCodes, InferOptionSchema, InferPluginContext, InferPluginErrorCodes, InferPluginFieldFromTuple, InferPluginIDs, InferPluginTypes, InferSessionAPI, InferSessionFromClient, InferUserFromClient, IsAny, IsSignal, type JSONWebKeySet, type JWTPayload, JoinConfig, JoinOption, OverrideMerge, Prettify, PrettifyDeep, RateLimit, RequiredKeysOf, Session, SessionQueryParams, type StandardSchemaV1, StateData, StoreIdentifierOption, StripEmptyObjects, type TelemetryEvent, UnionToIntersection, User, Verification, Where, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, getTelemetryAuthConfig, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL };
30
+ export { APIError, Account, AdditionalSessionFieldsInput, AdditionalUserFieldsInput, Auth, BetterAuthAdvancedOptions, BetterAuthClientOptions, BetterAuthClientPlugin, BetterAuthCookies, BetterAuthOptions, BetterAuthPlugin, BetterAuthRateLimitOptions, ClientAtomListener, ClientStore, DBAdapter, DBAdapterInstance, DBAdapterSchemaCreation, DBTransactionAdapter, ExtractPluginField, FilteredAPI, HIDE_METADATA, HasRequiredKeys, InferAPI, InferActions, InferAdditionalFromClient, InferClientAPI, InferErrorCodes, InferOptionSchema, InferPluginContext, InferPluginErrorCodes, InferPluginFieldFromTuple, InferPluginIDs, InferPluginTypes, InferSessionAPI, InferSessionFromClient, InferUserFromClient, IsAny, IsSignal, type JSONWebKeySet, type JWTPayload, JoinConfig, JoinOption, OverrideMerge, Prettify, PrettifyDeep, RateLimit, RequiredKeysOf, Session, SessionQueryParams, type StandardSchemaV1, StateData, StoreIdentifierOption, StripEmptyObjects, type TelemetryEvent, UnionToIntersection, User, Verification, Where, betterAuth, createTelemetry, generateGenericState, generateState, getBaseURL, getCurrentAdapter, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, getTelemetryAuthConfig, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, parseGenericState, parseState, resolveBaseURL, resolveDynamicBaseURL, trimTrailingSlashes };