better-auth 1.6.16 → 1.6.18

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 (93) hide show
  1. package/dist/api/index.d.mts +2 -2
  2. package/dist/api/index.mjs +3 -4
  3. package/dist/api/middlewares/origin-check.mjs +5 -1
  4. package/dist/api/rate-limiter/index.mjs +259 -73
  5. package/dist/api/routes/account.mjs +22 -7
  6. package/dist/api/routes/callback.mjs +2 -2
  7. package/dist/api/routes/index.d.mts +1 -1
  8. package/dist/api/routes/password.mjs +3 -4
  9. package/dist/api/routes/session.d.mts +12 -1
  10. package/dist/api/routes/session.mjs +13 -1
  11. package/dist/api/routes/sign-in.mjs +5 -5
  12. package/dist/api/routes/sign-up.mjs +2 -2
  13. package/dist/api/routes/update-session.mjs +2 -3
  14. package/dist/api/routes/update-user.mjs +10 -12
  15. package/dist/auth/base.mjs +11 -7
  16. package/dist/client/equality.d.mts +19 -0
  17. package/dist/client/equality.mjs +42 -0
  18. package/dist/client/index.d.mts +5 -4
  19. package/dist/client/index.mjs +2 -1
  20. package/dist/client/lynx/index.d.mts +4 -2
  21. package/dist/client/path-to-object.d.mts +5 -2
  22. package/dist/client/plugins/index.d.mts +4 -1
  23. package/dist/client/plugins/index.mjs +4 -1
  24. package/dist/client/query.d.mts +4 -3
  25. package/dist/client/query.mjs +27 -17
  26. package/dist/client/react/index.d.mts +4 -2
  27. package/dist/client/session-atom.mjs +129 -4
  28. package/dist/client/session-refresh.d.mts +3 -18
  29. package/dist/client/session-refresh.mjs +38 -49
  30. package/dist/client/solid/index.d.mts +4 -2
  31. package/dist/client/svelte/index.d.mts +4 -2
  32. package/dist/client/types.d.mts +27 -16
  33. package/dist/client/vanilla.d.mts +4 -2
  34. package/dist/client/vue/index.d.mts +4 -2
  35. package/dist/context/create-context.mjs +2 -1
  36. package/dist/context/store-capabilities.mjs +12 -0
  37. package/dist/cookies/index.mjs +25 -2
  38. package/dist/db/internal-adapter.mjs +51 -0
  39. package/dist/package.mjs +1 -1
  40. package/dist/plugins/access/access.mjs +49 -19
  41. package/dist/plugins/admin/routes.mjs +10 -3
  42. package/dist/plugins/captcha/constants.mjs +8 -1
  43. package/dist/plugins/captcha/index.mjs +8 -2
  44. package/dist/plugins/captcha/types.d.mts +21 -0
  45. package/dist/plugins/captcha/verify-handlers/captchafox.mjs +2 -0
  46. package/dist/plugins/captcha/verify-handlers/cloudflare-turnstile.mjs +7 -2
  47. package/dist/plugins/captcha/verify-handlers/google-recaptcha.mjs +7 -2
  48. package/dist/plugins/captcha/verify-handlers/h-captcha.mjs +2 -0
  49. package/dist/plugins/device-authorization/routes.mjs +16 -9
  50. package/dist/plugins/email-otp/routes.mjs +22 -52
  51. package/dist/plugins/generic-oauth/index.mjs +7 -2
  52. package/dist/plugins/generic-oauth/routes.mjs +16 -12
  53. package/dist/plugins/haveibeenpwned/index.d.mts +1 -1
  54. package/dist/plugins/haveibeenpwned/index.mjs +5 -1
  55. package/dist/plugins/index.d.mts +6 -2
  56. package/dist/plugins/index.mjs +4 -1
  57. package/dist/plugins/jwt/index.mjs +2 -2
  58. package/dist/plugins/mcp/client/index.mjs +1 -0
  59. package/dist/plugins/mcp/index.mjs +8 -0
  60. package/dist/plugins/multi-session/index.mjs +7 -5
  61. package/dist/plugins/oauth-popup/client.d.mts +82 -0
  62. package/dist/plugins/oauth-popup/client.mjs +203 -0
  63. package/dist/plugins/oauth-popup/constants.d.mts +11 -0
  64. package/dist/plugins/oauth-popup/constants.mjs +11 -0
  65. package/dist/plugins/oauth-popup/error-codes.d.mts +11 -0
  66. package/dist/plugins/oauth-popup/error-codes.mjs +10 -0
  67. package/dist/plugins/oauth-popup/index.d.mts +67 -0
  68. package/dist/plugins/oauth-popup/index.mjs +227 -0
  69. package/dist/plugins/oauth-popup/types.d.mts +30 -0
  70. package/dist/plugins/oauth-proxy/index.mjs +2 -2
  71. package/dist/plugins/oauth-proxy/utils.mjs +16 -2
  72. package/dist/plugins/oidc-provider/index.mjs +10 -0
  73. package/dist/plugins/one-tap/client.mjs +12 -6
  74. package/dist/plugins/one-tap/index.d.mts +1 -0
  75. package/dist/plugins/one-tap/index.mjs +9 -5
  76. package/dist/plugins/one-time-token/index.mjs +1 -3
  77. package/dist/plugins/open-api/generator.d.mts +66 -57
  78. package/dist/plugins/open-api/generator.mjs +185 -67
  79. package/dist/plugins/open-api/index.d.mts +2 -2
  80. package/dist/plugins/organization/adapter.d.mts +29 -1
  81. package/dist/plugins/organization/adapter.mjs +66 -6
  82. package/dist/plugins/organization/routes/crud-invites.mjs +49 -34
  83. package/dist/plugins/organization/routes/crud-members.mjs +42 -6
  84. package/dist/plugins/organization/routes/crud-team.mjs +36 -3
  85. package/dist/plugins/phone-number/routes.mjs +41 -36
  86. package/dist/plugins/siwe/index.mjs +2 -3
  87. package/dist/plugins/two-factor/backup-codes/index.mjs +1 -1
  88. package/dist/plugins/two-factor/otp/index.mjs +11 -13
  89. package/dist/plugins/two-factor/totp/index.mjs +1 -1
  90. package/dist/plugins/two-factor/verify-two-factor.mjs +6 -2
  91. package/dist/plugins/username/index.mjs +6 -6
  92. package/dist/test-utils/test-instance.d.mts +26 -23
  93. package/package.json +9 -9
@@ -80,7 +80,7 @@ const signInSocial = () => createAuthEndpoint("/sign-in/social", {
80
80
  }
81
81
  const { token, nonce } = c.body.idToken;
82
82
  if (!await provider.verifyIdToken(token, nonce)) {
83
- c.context.logger.error("Invalid id token", { provider: c.body.provider });
83
+ c.context.logger.warn("Invalid id token", { provider: c.body.provider });
84
84
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_TOKEN);
85
85
  }
86
86
  const userInfo = await provider.getUserInfo({
@@ -205,26 +205,26 @@ const signInEmail = () => createAuthEndpoint("/sign-in/email", {
205
205
  const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
206
206
  if (!user) {
207
207
  await ctx.context.password.hash(password);
208
- ctx.context.logger.error("User not found", { email });
208
+ ctx.context.logger.warn("User not found");
209
209
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
210
210
  }
211
211
  const credentialAccount = user.accounts.find((a) => a.providerId === "credential");
212
212
  if (!credentialAccount) {
213
213
  await ctx.context.password.hash(password);
214
- ctx.context.logger.error("Credential account not found", { email });
214
+ ctx.context.logger.warn("Credential account not found");
215
215
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
216
216
  }
217
217
  const currentPassword = credentialAccount?.password;
218
218
  if (!currentPassword) {
219
219
  await ctx.context.password.hash(password);
220
- ctx.context.logger.error("Password not found", { email });
220
+ ctx.context.logger.warn("Password not found");
221
221
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
222
222
  }
223
223
  if (!await ctx.context.password.verify({
224
224
  hash: currentPassword,
225
225
  password
226
226
  })) {
227
- ctx.context.logger.error("Invalid password");
227
+ ctx.context.logger.warn("Invalid password");
228
228
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
229
229
  }
230
230
  if (ctx.context.options?.emailAndPassword?.requireEmailVerification && !user.user.emailVerified) {
@@ -150,12 +150,12 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
150
150
  if (!password || typeof password !== "string") throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_PASSWORD);
151
151
  const minPasswordLength = ctx.context.password.config.minPasswordLength;
152
152
  if (password.length < minPasswordLength) {
153
- ctx.context.logger.error("Password is too short");
153
+ ctx.context.logger.warn("Password is too short");
154
154
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);
155
155
  }
156
156
  const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
157
157
  if (password.length > maxPasswordLength) {
158
- ctx.context.logger.error("Password is too long");
158
+ ctx.context.logger.warn("Password is too long");
159
159
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
160
160
  }
161
161
  const shouldReturnGenericDuplicateResponse = ctx.context.options.emailAndPassword.requireEmailVerification || ctx.context.options.emailAndPassword.autoSignIn === false;
@@ -1,6 +1,6 @@
1
1
  import { parseSessionInput, parseSessionOutput } from "../../db/schema.mjs";
2
2
  import { deleteSessionCookie, setSessionCookie } from "../../cookies/index.mjs";
3
- import { sessionMiddleware } from "./session.mjs";
3
+ import { isStateful, sessionMiddleware } from "./session.mjs";
4
4
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
5
5
  import { createAuthEndpoint } from "@better-auth/core/api";
6
6
  import * as z from "zod";
@@ -38,8 +38,7 @@ const updateSession = () => createAuthEndpoint("/update-session", {
38
38
  ...additionalFields,
39
39
  updatedAt: /* @__PURE__ */ new Date()
40
40
  });
41
- const isStateful = !!ctx.context.options.database || !!ctx.context.options.secondaryStorage;
42
- if (!updatedSession && isStateful) {
41
+ if (!updatedSession && isStateful(ctx)) {
43
42
  deleteSessionCookie(ctx);
44
43
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.FAILED_TO_GET_SESSION);
45
44
  }
@@ -2,7 +2,7 @@ import { originCheck } from "../middlewares/origin-check.mjs";
2
2
  import { parseUserInput, parseUserOutput } from "../../db/schema.mjs";
3
3
  import { generateRandomString } from "../../crypto/random.mjs";
4
4
  import { deleteSessionCookie, setSessionCookie } from "../../cookies/index.mjs";
5
- import { getSessionFromCtx, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
5
+ import { getSessionFromCtx, isStateful, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
6
6
  import { createEmailVerificationToken } from "./email-verification.mjs";
7
7
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
8
8
  import { createAuthEndpoint } from "@better-auth/core/api";
@@ -150,12 +150,12 @@ const changePassword = createAuthEndpoint("/change-password", {
150
150
  const session = ctx.context.session;
151
151
  const minPasswordLength = ctx.context.password.config.minPasswordLength;
152
152
  if (newPassword.length < minPasswordLength) {
153
- ctx.context.logger.error("Password is too short");
153
+ ctx.context.logger.warn("Password is too short");
154
154
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);
155
155
  }
156
156
  const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
157
157
  if (newPassword.length > maxPasswordLength) {
158
- ctx.context.logger.error("Password is too long");
158
+ ctx.context.logger.warn("Password is too long");
159
159
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
160
160
  }
161
161
  const account = (await ctx.context.internalAdapter.findAccounts(session.user.id)).find((account) => account.providerId === "credential" && account.password);
@@ -182,7 +182,7 @@ const changePassword = createAuthEndpoint("/change-password", {
182
182
  user: parseUserOutput(ctx.context.options, session.user)
183
183
  });
184
184
  });
185
- const setPassword = createAuthEndpoint({
185
+ const setPassword = createAuthEndpoint.serverOnly({
186
186
  method: "POST",
187
187
  body: z.object({ newPassword: z.string().meta({ description: "The new password to set is required" }) }),
188
188
  use: [sensitiveSessionMiddleware]
@@ -191,12 +191,12 @@ const setPassword = createAuthEndpoint({
191
191
  const session = ctx.context.session;
192
192
  const minPasswordLength = ctx.context.password.config.minPasswordLength;
193
193
  if (newPassword.length < minPasswordLength) {
194
- ctx.context.logger.error("Password is too short");
194
+ ctx.context.logger.warn("Password is too short");
195
195
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);
196
196
  }
197
197
  const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
198
198
  if (newPassword.length > maxPasswordLength) {
199
- ctx.context.logger.error("Password is too long");
199
+ ctx.context.logger.warn("Password is too long");
200
200
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
201
201
  }
202
202
  const account = (await ctx.context.internalAdapter.findAccounts(session.user.id)).find((account) => account.providerId === "credential" && account.password);
@@ -354,17 +354,15 @@ const deleteUserCallback = createAuthEndpoint("/delete-user/callback", {
354
354
  code: "NOT_FOUND"
355
355
  });
356
356
  }
357
- const session = await getSessionFromCtx(ctx);
357
+ const session = await getSessionFromCtx(ctx, { disableCookieCache: isStateful(ctx) });
358
358
  if (!session) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.FAILED_TO_GET_USER_INFO);
359
- const token = await ctx.context.internalAdapter.findVerificationValue(`delete-account-${ctx.query.token}`);
360
- if (!token || token.expiresAt < /* @__PURE__ */ new Date()) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.INVALID_TOKEN);
361
- if (token.value !== session.user.id) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.INVALID_TOKEN);
359
+ const token = await ctx.context.internalAdapter.consumeVerificationValue(`delete-account-${ctx.query.token}`);
360
+ if (!token || token.value !== session.user.id) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.INVALID_TOKEN);
362
361
  const beforeDelete = ctx.context.options.user.deleteUser?.beforeDelete;
363
362
  if (beforeDelete) await beforeDelete(session.user, ctx.request);
364
363
  await ctx.context.internalAdapter.deleteUser(session.user.id);
365
364
  await ctx.context.internalAdapter.deleteUserSessions(session.user.id);
366
365
  await ctx.context.internalAdapter.deleteAccounts(session.user.id);
367
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(`delete-account-${ctx.query.token}`);
368
366
  deleteSessionCookie(ctx);
369
367
  const afterDelete = ctx.context.options.user.deleteUser?.afterDelete;
370
368
  if (afterDelete) await afterDelete(session.user, ctx.request);
@@ -414,7 +412,7 @@ const changeEmail = createAuthEndpoint("/change-email", {
414
412
  }
415
413
  const newEmail = ctx.body.newEmail.toLowerCase();
416
414
  if (newEmail === ctx.context.session.user.email) {
417
- ctx.context.logger.error("Email is the same");
415
+ ctx.context.logger.warn("Email is the same");
418
416
  throw APIError.fromStatus("BAD_REQUEST", { message: "Email is the same" });
419
417
  }
420
418
  /**
@@ -14,16 +14,20 @@ const createBetterAuth = (options, initFn) => {
14
14
  let handlerCtx;
15
15
  if (isDynamicBaseURLConfig(options.baseURL)) handlerCtx = await resolveRequestContext(ctx, request, resolveDynamicTrustedProxyHeaders(ctx.options));
16
16
  else {
17
- handlerCtx = ctx;
17
+ handlerCtx = Object.create(Object.getPrototypeOf(ctx), Object.getOwnPropertyDescriptors(ctx));
18
+ let trustOptions = ctx.options;
18
19
  if (!ctx.options.baseURL) {
19
20
  const baseURL = getBaseURL(void 0, basePath, request, void 0, ctx.options.advanced?.trustedProxyHeaders);
20
- if (baseURL) {
21
- ctx.baseURL = baseURL;
22
- ctx.options.baseURL = getOrigin(ctx.baseURL) || void 0;
23
- } else throw new BetterAuthError("Could not get base URL from request. Please provide a valid base URL.");
21
+ if (!baseURL) throw new BetterAuthError("Could not get base URL from request. Please provide a valid base URL.");
22
+ handlerCtx.baseURL = baseURL;
23
+ handlerCtx.options = {
24
+ ...ctx.options,
25
+ baseURL: getOrigin(baseURL) || void 0
26
+ };
27
+ trustOptions = handlerCtx.options;
24
28
  }
25
- handlerCtx.trustedOrigins = await getTrustedOrigins(ctx.options, request);
26
- handlerCtx.trustedProviders = await getTrustedProviders(ctx.options, request);
29
+ handlerCtx.trustedOrigins = await getTrustedOrigins(trustOptions, request);
30
+ handlerCtx.trustedProviders = await getTrustedProviders(trustOptions, request);
27
31
  }
28
32
  const { handler } = router(handlerCtx, options);
29
33
  return runWithAdapter(handlerCtx.adapter, () => handler(request));
@@ -0,0 +1,19 @@
1
+ import { Store, StoreValue } from "nanostores";
2
+
3
+ //#region src/client/equality.d.ts
4
+ /**
5
+ * Deep structural equality for JSON-serializable values.
6
+ * Handles: primitives, null, arrays, and plain objects.
7
+ * Short-circuits on referential equality at every recursion level.
8
+ */
9
+ declare function isJsonEqual(a: unknown, b: unknown): boolean;
10
+ /**
11
+ * Attach an equality gate to a nanostores atom via `onSet`.
12
+ * When `isEqual(currentValue, newValue)` returns true, the `set()` call
13
+ * is aborted: no listeners fire, no framework re-renders occur.
14
+ *
15
+ * Returns the unsubscribe function from `onSet`.
16
+ */
17
+ declare function withEquality<S extends Store>(store: S, isEqual: (a: StoreValue<S>, b: StoreValue<S>) => boolean): () => void;
18
+ //#endregion
19
+ export { isJsonEqual, withEquality };
@@ -0,0 +1,42 @@
1
+ import { onSet } from "nanostores";
2
+ //#region src/client/equality.ts
3
+ function isPlainObject(value) {
4
+ if (typeof value !== "object" || value === null) return false;
5
+ const prototype = Object.getPrototypeOf(value);
6
+ return prototype === Object.prototype || prototype === null;
7
+ }
8
+ /**
9
+ * Deep structural equality for JSON-serializable values.
10
+ * Handles: primitives, null, arrays, and plain objects.
11
+ * Short-circuits on referential equality at every recursion level.
12
+ */
13
+ function isJsonEqual(a, b) {
14
+ if (a === b) return true;
15
+ if (Array.isArray(a) && Array.isArray(b)) {
16
+ if (a.length !== b.length) return false;
17
+ for (let i = 0; i < a.length; i++) if (!isJsonEqual(a[i], b[i])) return false;
18
+ return true;
19
+ }
20
+ if (isPlainObject(a) && isPlainObject(b)) {
21
+ const keysA = Object.keys(a);
22
+ const keysB = Object.keys(b);
23
+ if (keysA.length !== keysB.length) return false;
24
+ for (const key of keysA) if (!(key in b) || !isJsonEqual(a[key], b[key])) return false;
25
+ return true;
26
+ }
27
+ return false;
28
+ }
29
+ /**
30
+ * Attach an equality gate to a nanostores atom via `onSet`.
31
+ * When `isEqual(currentValue, newValue)` returns true, the `set()` call
32
+ * is aborted: no listeners fire, no framework re-renders occur.
33
+ *
34
+ * Returns the unsubscribe function from `onSet`.
35
+ */
36
+ function withEquality(store, isEqual) {
37
+ return onSet(store, ({ newValue, abort }) => {
38
+ if (isEqual(store.value, newValue)) abort();
39
+ });
40
+ }
41
+ //#endregion
42
+ export { isJsonEqual, withEquality };
@@ -1,12 +1,13 @@
1
1
  import { ExtractPluginField, HasRequiredKeys, InferPluginFieldFromTuple, IsAny, OverrideMerge, Prettify, PrettifyDeep, RequiredKeysOf, StripEmptyObjects, UnionToIntersection } from "../types/helper.mjs";
2
- import { CamelCase, InferCtx, InferRoute, InferRoutes, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest } from "./path-to-object.mjs";
2
+ import { CamelCase, InferCtx, InferRoute, InferRoutes, InferSessionUpdateCtx, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest } from "./path-to-object.mjs";
3
3
  import { BetterAuthClientOptions, BetterAuthClientPlugin, ClientAtomListener, ClientStore, InferActions, InferAdditionalFromClient, InferClientAPI, InferErrorCodes, InferSessionFromClient, InferUserFromClient, IsSignal, SessionQueryParams } from "./types.mjs";
4
4
  import { BroadcastChannel, BroadcastListener, BroadcastMessage, getGlobalBroadcastChannel, kBroadcastChannel } from "./broadcast-channel.mjs";
5
+ import { isJsonEqual, withEquality } from "./equality.mjs";
5
6
  import { FocusListener, FocusManager, kFocusManager } from "./focus-manager.mjs";
6
7
  import { OnlineListener, OnlineManager, kOnlineManager } from "./online-manager.mjs";
7
8
  import { parseJSON } from "./parser.mjs";
8
- import { AuthQueryAtom, useAuthQuery } from "./query.mjs";
9
- import { SessionRefreshOptions, SessionResponse, createSessionRefreshManager } from "./session-refresh.mjs";
9
+ import { AuthQueryAtom, AuthQueryState, useAuthQuery } from "./query.mjs";
10
+ import { SessionRefreshOptions, createSessionRefreshManager } from "./session-refresh.mjs";
10
11
  import { AuthClient, createAuthClient } from "./vanilla.mjs";
11
12
  import { AccessControl, ArrayElement, ExactRoleStatements, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, Statements, SubArray, Subset } from "../plugins/access/types.mjs";
12
13
  import { AuthorizeResponse, createAccessControl, role } from "../plugins/access/access.mjs";
@@ -31,4 +32,4 @@ declare function InferAuth<O extends {
31
32
  options: BetterAuthOptions;
32
33
  }>(): O["options"];
33
34
  //#endregion
34
- export { AccessControl, ArrayElement, AuthClient, AuthQueryAtom, AuthorizeResponse, BetterAuthClientOptions, BetterAuthClientPlugin, BroadcastChannel, BroadcastListener, BroadcastMessage, CamelCase, ClientAtomListener, ClientStore, type DBPrimitive, DefaultOrganizationPlugin, DynamicAccessControlEndpoints, ExactRoleStatements, ExtractPluginField, type FocusListener, type FocusManager, HasRequiredKeys, InferActions, InferAdditionalFromClient, InferAuth, InferClientAPI, InferCtx, InferErrorCodes, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPlugin, InferPluginFieldFromTuple, InferRoute, InferRoutes, InferSessionFromClient, InferSignUpEmailCtx, InferTeam, InferUserFromClient, InferUserUpdateCtx, Invitation, InvitationInput, InvitationStatus, IsAny, IsSignal, Member, MemberInput, MergeRoutes, type OnlineListener, type OnlineManager, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, OverrideMerge, PathToObject, Prettify, PrettifyDeep, ProxyRequest, RequiredKeysOf, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, SessionQueryParams, SessionRefreshOptions, SessionResponse, Statements, StripEmptyObjects, SubArray, Subset, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, type UnionToIntersection, createAccessControl, createAuthClient, createSessionRefreshManager, defaultRolesSchema, getGlobalBroadcastChannel, getOrgAdapter, hasPermission, invitationSchema, invitationStatus, kBroadcastChannel, kFocusManager, kOnlineManager, memberSchema, organization, organizationRoleSchema, organizationSchema, parseJSON, parseRoles, role, roleSchema, teamMemberSchema, teamSchema, useAuthQuery };
35
+ export { AccessControl, ArrayElement, AuthClient, AuthQueryAtom, AuthQueryState, AuthorizeResponse, BetterAuthClientOptions, BetterAuthClientPlugin, BroadcastChannel, BroadcastListener, BroadcastMessage, CamelCase, ClientAtomListener, ClientStore, type DBPrimitive, DefaultOrganizationPlugin, DynamicAccessControlEndpoints, ExactRoleStatements, ExtractPluginField, type FocusListener, type FocusManager, HasRequiredKeys, InferActions, InferAdditionalFromClient, InferAuth, InferClientAPI, InferCtx, InferErrorCodes, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPlugin, InferPluginFieldFromTuple, InferRoute, InferRoutes, InferSessionFromClient, InferSessionUpdateCtx, InferSignUpEmailCtx, InferTeam, InferUserFromClient, InferUserUpdateCtx, Invitation, InvitationInput, InvitationStatus, IsAny, IsSignal, Member, MemberInput, MergeRoutes, type OnlineListener, type OnlineManager, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, OverrideMerge, PathToObject, Prettify, PrettifyDeep, ProxyRequest, RequiredKeysOf, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, SessionQueryParams, SessionRefreshOptions, Statements, StripEmptyObjects, SubArray, Subset, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, type UnionToIntersection, createAccessControl, createAuthClient, createSessionRefreshManager, defaultRolesSchema, getGlobalBroadcastChannel, getOrgAdapter, hasPermission, invitationSchema, invitationStatus, isJsonEqual, kBroadcastChannel, kFocusManager, kOnlineManager, memberSchema, organization, organizationRoleSchema, organizationSchema, parseJSON, parseRoles, role, roleSchema, teamMemberSchema, teamSchema, useAuthQuery, withEquality };
@@ -1,5 +1,6 @@
1
1
  import { PACKAGE_VERSION } from "../version.mjs";
2
2
  import { getGlobalBroadcastChannel, kBroadcastChannel } from "./broadcast-channel.mjs";
3
+ import { isJsonEqual, withEquality } from "./equality.mjs";
3
4
  import { kFocusManager } from "./focus-manager.mjs";
4
5
  import { kOnlineManager } from "./online-manager.mjs";
5
6
  import { parseJSON } from "./parser.mjs";
@@ -18,4 +19,4 @@ function InferAuth() {
18
19
  return {};
19
20
  }
20
21
  //#endregion
21
- export { InferAuth, InferPlugin, createAuthClient, createSessionRefreshManager, getGlobalBroadcastChannel, kBroadcastChannel, kFocusManager, kOnlineManager, parseJSON, useAuthQuery };
22
+ export { InferAuth, InferPlugin, createAuthClient, createSessionRefreshManager, getGlobalBroadcastChannel, isJsonEqual, kBroadcastChannel, kFocusManager, kOnlineManager, parseJSON, useAuthQuery, withEquality };
@@ -1,7 +1,7 @@
1
1
  import { ExtractPluginField, HasRequiredKeys, InferPluginFieldFromTuple, IsAny, OverrideMerge, Prettify, PrettifyDeep, RequiredKeysOf, StripEmptyObjects, UnionToIntersection } from "../../types/helper.mjs";
2
2
  import { InferActions, InferClientAPI, InferErrorCodes, IsSignal, SessionQueryParams } from "../types.mjs";
3
3
  import { useStore } from "./lynx-store.mjs";
4
- import { BetterAuthClientOptions, BetterAuthClientPlugin } from "@better-auth/core";
4
+ import { BetterAuthClientOptions } from "@better-auth/core";
5
5
  import { BASE_ERROR_CODES } from "@better-auth/core/error";
6
6
  import * as nanostores from "nanostores";
7
7
  import * as _better_fetch_fetch0 from "@better-fetch/fetch";
@@ -12,7 +12,9 @@ export * from "@better-fetch/fetch";
12
12
  //#region src/client/lynx/index.d.ts
13
13
  type InferResolvedHooks<O extends BetterAuthClientOptions> = O extends {
14
14
  plugins: Array<infer Plugin>;
15
- } ? UnionToIntersection<Plugin extends BetterAuthClientPlugin ? Plugin["getAtoms"] extends ((fetch: any) => infer Atoms) ? Atoms extends Record<string, any> ? { [key in keyof Atoms as IsSignal<key> extends true ? never : key extends string ? `use${Capitalize<key>}` : never]: () => ReturnType<Atoms[key]["get"]> } : {} : {} : {}> : {};
15
+ } ? UnionToIntersection<Plugin extends {
16
+ getAtoms?: infer GetAtoms;
17
+ } ? GetAtoms extends ((fetch: any) => infer Atoms) ? Atoms extends Record<string, any> ? { [key in keyof Atoms as IsSignal<key> extends true ? never : key extends string ? `use${Capitalize<key>}` : never]: () => ReturnType<Atoms[key]["get"]> } : {} : {} : {}> : {};
16
18
  declare function createAuthClient<Option extends BetterAuthClientOptions>(options?: Option | undefined): UnionToIntersection<InferResolvedHooks<Option>> & InferClientAPI<Option> & InferActions<Option> & {
17
19
  useSession: () => {
18
20
  data: InferClientAPI<Option> extends {
@@ -30,6 +30,9 @@ type InferUserUpdateCtx<ClientOpts extends BetterAuthClientOptions, FetchOptions
30
30
  name?: string | undefined;
31
31
  fetchOptions?: FetchOptions | undefined;
32
32
  } & Partial<UnionToIntersection<InferAdditionalFromClient<ClientOpts, "user", "input">>>;
33
+ type InferSessionUpdateCtx<ClientOpts extends BetterAuthClientOptions, FetchOptions extends ClientFetchOption> = {
34
+ fetchOptions?: FetchOptions | undefined;
35
+ } & Partial<UnionToIntersection<InferAdditionalFromClient<ClientOpts, "session", "input">>>;
33
36
  type InferCtxQuery<C extends InputContext<any, any>, FetchOptions extends ClientFetchOption> = C["query"] extends Record<string, any> ? {
34
37
  query: C["query"];
35
38
  fetchOptions?: FetchOptions | undefined;
@@ -51,7 +54,7 @@ type InferRoute<API, COpts extends BetterAuthClientOptions> = API extends Record
51
54
  scope: "http";
52
55
  } | {
53
56
  scope: "server";
54
- } ? {} : PathToObject<T["path"], T extends ((ctx: infer C) => infer R) ? C extends InputContext<any, any> ? <FetchOptions extends ClientFetchOption<Partial<C["body"]> & Record<string, any>, Partial<C["query"]> & Record<string, any>, C["params"]>>(...data: HasRequiredKeys<InferCtx<C, FetchOptions>> extends true ? [Prettify$1<T["path"] extends `/sign-up/email` ? InferSignUpEmailCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>, FetchOptions?] : [Prettify$1<T["path"] extends `/update-user` ? InferUserUpdateCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>?, FetchOptions?]) => Promise<BetterFetchResponse<T["options"]["metadata"] extends {
57
+ } ? {} : PathToObject<T["path"], T extends ((ctx: infer C) => infer R) ? C extends InputContext<any, any> ? <FetchOptions extends ClientFetchOption<Partial<C["body"]> & Record<string, any>, Partial<C["query"]> & Record<string, any>, C["params"]>>(...data: HasRequiredKeys<InferCtx<C, FetchOptions>> extends true ? [Prettify$1<T["path"] extends `/sign-up/email` ? InferSignUpEmailCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>, FetchOptions?] : [Prettify$1<T["path"] extends `/update-user` ? InferUserUpdateCtx<COpts, FetchOptions> : T["path"] extends `/update-session` ? InferSessionUpdateCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>?, FetchOptions?]) => Promise<BetterFetchResponse<T["options"]["metadata"] extends {
55
58
  CUSTOM_SESSION: boolean;
56
59
  } ? MergeCustomSessionWithInferred<NonNullable<Awaited<R>>, COpts> : T["path"] extends "/get-session" ? {
57
60
  user: InferUserFromClient<COpts>;
@@ -69,4 +72,4 @@ type ProxyRequest = {
69
72
  [key: string]: any;
70
73
  };
71
74
  //#endregion
72
- export { CamelCase, InferCtx, InferRoute, InferRoutes, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest };
75
+ export { CamelCase, InferCtx, InferRoute, InferRoutes, InferSessionUpdateCtx, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest };
@@ -19,6 +19,8 @@ import { JWKOptions, JWSAlgorithms, Jwk, JwtOptions } from "../../plugins/jwt/ty
19
19
  import { AuthorizationQuery, Client, CodeVerificationValue, OAuthAccessToken, OIDCMetadata, OIDCOptions, TokenBody } from "../../plugins/oidc-provider/types.mjs";
20
20
  import { MULTI_SESSION_ERROR_CODES } from "../../plugins/multi-session/error-codes.mjs";
21
21
  import { MultiSessionConfig } from "../../plugins/multi-session/index.mjs";
22
+ import { POPUP_TOKEN_STORAGE_KEY } from "../../plugins/oauth-popup/constants.mjs";
23
+ import { OAUTH_POPUP_ERROR_CODES } from "../../plugins/oauth-popup/error-codes.mjs";
22
24
  import { OneTimeTokenOptions } from "../../plugins/one-time-token/index.mjs";
23
25
  import { PhoneNumberOptions, UserWithPhoneNumber } from "../../plugins/phone-number/types.mjs";
24
26
  import { BackupCodeOptions, backupCode2fa, encodeBackupCodes, generateBackupCodes, getBackupCodes, verifyBackupCode } from "../../plugins/two-factor/backup-codes/index.mjs";
@@ -44,6 +46,7 @@ import { jwtClient } from "../../plugins/jwt/client.mjs";
44
46
  import { LastLoginMethodClientConfig, lastLoginMethodClient } from "../../plugins/last-login-method/client.mjs";
45
47
  import { magicLinkClient } from "../../plugins/magic-link/client.mjs";
46
48
  import { multiSessionClient } from "../../plugins/multi-session/client.mjs";
49
+ import { SignInPopupOptions, SignInPopupResult, createSignInPopup, getStoredPopupToken, oauthPopupClient, popupBearerFetchPlugin } from "../../plugins/oauth-popup/client.mjs";
47
50
  import { OidcClientPlugin, oidcClient } from "../../plugins/oidc-provider/client.mjs";
48
51
  import { GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, oneTapClient } from "../../plugins/one-tap/client.mjs";
49
52
  import { oneTimeTokenClient } from "../../plugins/one-time-token/client.mjs";
@@ -53,4 +56,4 @@ import { phoneNumberClient } from "../../plugins/phone-number/client.mjs";
53
56
  import { siweClient } from "../../plugins/siwe/client.mjs";
54
57
  import { usernameClient } from "../../plugins/username/client.mjs";
55
58
  import { InferServerPlugin } from "./infer-plugin.mjs";
56
- export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminClientOptions, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, type FieldAttributeToObject, GENERIC_OAUTH_ERROR_CODES, GenericOAuthConfig, GenericOAuthOptions, GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, GumroadOptions, HasRequiredKeys, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginFieldFromTuple, InferServerPlugin, InferTeam, Invitation, InvitationInput, InvitationStatus, IsAny, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodClientConfig, LineOptions, MULTI_SESSION_ERROR_CODES, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OIDCMetadata, OIDCOptions, ORGANIZATION_ERROR_CODES, OTPOptions, OidcClientPlugin, OktaOptions, OneTimeTokenOptions, Organization, OrganizationClientOptions, OrganizationInput, OrganizationRole, OrganizationSchema, OverrideMerge, PHONE_NUMBER_ERROR_CODES, PatreonOptions, PhoneNumberOptions, Prettify, PrettifyDeep, type RemoveFieldsWithReturnedFalse, RequiredKeysOf, SessionWithImpersonatedBy, SlackOptions, StripEmptyObjects, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamInput, TeamMember, TeamMemberInput, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UnionToIntersection, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, adminClient, anonymousClient, auth0, backupCode2fa, clientSideHasPermission, customSessionClient, defaultRolesSchema, deviceAuthorizationClient, emailOTPClient, encodeBackupCodes, generateBackupCodes, genericOAuthClient, getBackupCodes, gumroad, hubspot, inferAdditionalFields, inferOrgAdditionalFields, invitationSchema, invitationStatus, jwtClient, keycloak, lastLoginMethodClient, line, magicLinkClient, memberSchema, microsoftEntraId, multiSessionClient, oidcClient, okta, oneTapClient, oneTimeTokenClient, organizationClient, organizationRoleSchema, organizationSchema, otp2fa, patreon, phoneNumberClient, roleSchema, schema, siweClient, slack, teamMemberSchema, teamSchema, totp2fa, twoFactorClient, usernameClient, verifyBackupCode };
59
+ export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminClientOptions, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, type FieldAttributeToObject, GENERIC_OAUTH_ERROR_CODES, GenericOAuthConfig, GenericOAuthOptions, GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, GumroadOptions, HasRequiredKeys, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginFieldFromTuple, InferServerPlugin, InferTeam, Invitation, InvitationInput, InvitationStatus, IsAny, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodClientConfig, LineOptions, MULTI_SESSION_ERROR_CODES, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAUTH_POPUP_ERROR_CODES, OAuthAccessToken, OIDCMetadata, OIDCOptions, ORGANIZATION_ERROR_CODES, OTPOptions, OidcClientPlugin, OktaOptions, OneTimeTokenOptions, Organization, OrganizationClientOptions, OrganizationInput, OrganizationRole, OrganizationSchema, OverrideMerge, PHONE_NUMBER_ERROR_CODES, POPUP_TOKEN_STORAGE_KEY, PatreonOptions, PhoneNumberOptions, Prettify, PrettifyDeep, type RemoveFieldsWithReturnedFalse, RequiredKeysOf, SessionWithImpersonatedBy, SignInPopupOptions, SignInPopupResult, SlackOptions, StripEmptyObjects, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamInput, TeamMember, TeamMemberInput, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UnionToIntersection, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, adminClient, anonymousClient, auth0, backupCode2fa, clientSideHasPermission, createSignInPopup, customSessionClient, defaultRolesSchema, deviceAuthorizationClient, emailOTPClient, encodeBackupCodes, generateBackupCodes, genericOAuthClient, getBackupCodes, getStoredPopupToken, gumroad, hubspot, inferAdditionalFields, inferOrgAdditionalFields, invitationSchema, invitationStatus, jwtClient, keycloak, lastLoginMethodClient, line, magicLinkClient, memberSchema, microsoftEntraId, multiSessionClient, oauthPopupClient, oidcClient, okta, oneTapClient, oneTimeTokenClient, organizationClient, organizationRoleSchema, organizationSchema, otp2fa, patreon, phoneNumberClient, popupBearerFetchPlugin, roleSchema, schema, siweClient, slack, teamMemberSchema, teamSchema, totp2fa, twoFactorClient, usernameClient, verifyBackupCode };
@@ -14,6 +14,9 @@ import { lastLoginMethodClient } from "../../plugins/last-login-method/client.mj
14
14
  import { magicLinkClient } from "../../plugins/magic-link/client.mjs";
15
15
  import { MULTI_SESSION_ERROR_CODES } from "../../plugins/multi-session/error-codes.mjs";
16
16
  import { multiSessionClient } from "../../plugins/multi-session/client.mjs";
17
+ import { POPUP_TOKEN_STORAGE_KEY } from "../../plugins/oauth-popup/constants.mjs";
18
+ import { OAUTH_POPUP_ERROR_CODES } from "../../plugins/oauth-popup/error-codes.mjs";
19
+ import { createSignInPopup, getStoredPopupToken, oauthPopupClient, popupBearerFetchPlugin } from "../../plugins/oauth-popup/client.mjs";
17
20
  import { oidcClient } from "../../plugins/oidc-provider/client.mjs";
18
21
  import { oneTapClient } from "../../plugins/one-tap/client.mjs";
19
22
  import { oneTimeTokenClient } from "../../plugins/one-time-token/client.mjs";
@@ -27,4 +30,4 @@ import { twoFactorClient } from "../../plugins/two-factor/client.mjs";
27
30
  import { USERNAME_ERROR_CODES } from "../../plugins/username/error-codes.mjs";
28
31
  import { usernameClient } from "../../plugins/username/client.mjs";
29
32
  import { InferServerPlugin } from "./infer-plugin.mjs";
30
- export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, EMAIL_OTP_ERROR_CODES, GENERIC_OAUTH_ERROR_CODES, InferServerPlugin, MULTI_SESSION_ERROR_CODES, ORGANIZATION_ERROR_CODES, PHONE_NUMBER_ERROR_CODES, TWO_FACTOR_ERROR_CODES, USERNAME_ERROR_CODES, adminClient, anonymousClient, clientSideHasPermission, customSessionClient, deviceAuthorizationClient, emailOTPClient, genericOAuthClient, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oidcClient, oneTapClient, oneTimeTokenClient, organizationClient, phoneNumberClient, siweClient, twoFactorClient, usernameClient };
33
+ export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, EMAIL_OTP_ERROR_CODES, GENERIC_OAUTH_ERROR_CODES, InferServerPlugin, MULTI_SESSION_ERROR_CODES, OAUTH_POPUP_ERROR_CODES, ORGANIZATION_ERROR_CODES, PHONE_NUMBER_ERROR_CODES, POPUP_TOKEN_STORAGE_KEY, TWO_FACTOR_ERROR_CODES, USERNAME_ERROR_CODES, adminClient, anonymousClient, clientSideHasPermission, createSignInPopup, customSessionClient, deviceAuthorizationClient, emailOTPClient, genericOAuthClient, getStoredPopupToken, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oauthPopupClient, oidcClient, oneTapClient, oneTimeTokenClient, organizationClient, phoneNumberClient, popupBearerFetchPlugin, siweClient, twoFactorClient, usernameClient };
@@ -4,7 +4,7 @@ import { PreinitializedWritableAtom } from "nanostores";
4
4
  import { BetterFetch, BetterFetchError } from "@better-fetch/fetch";
5
5
 
6
6
  //#region src/client/query.d.ts
7
- type AuthQueryAtom<T> = PreinitializedWritableAtom<{
7
+ type AuthQueryState<T> = {
8
8
  data: null | T;
9
9
  error: null | BetterFetchError;
10
10
  isPending: boolean;
@@ -12,11 +12,12 @@ type AuthQueryAtom<T> = PreinitializedWritableAtom<{
12
12
  refetch: (queryParams?: {
13
13
  query?: SessionQueryParams;
14
14
  } | undefined) => Promise<void>;
15
- }>;
15
+ };
16
+ type AuthQueryAtom<T> = PreinitializedWritableAtom<AuthQueryState<T>>;
16
17
  declare const useAuthQuery: <T>(initializedAtom: PreinitializedWritableAtom<any> | PreinitializedWritableAtom<any>[], path: string, $fetch: BetterFetch, options?: (((value: {
17
18
  data: null | T;
18
19
  error: null | BetterFetchError;
19
20
  isPending: boolean;
20
21
  }) => ClientFetchOption) | ClientFetchOption) | undefined) => AuthQueryAtom<T>;
21
22
  //#endregion
22
- export { AuthQueryAtom, useAuthQuery };
23
+ export { AuthQueryAtom, AuthQueryState, useAuthQuery };
@@ -1,6 +1,10 @@
1
+ import { isJsonEqual, withEquality } from "./equality.mjs";
1
2
  import { atom, onMount } from "nanostores";
2
3
  //#region src/client/query.ts
3
4
  const isServer = () => typeof window === "undefined";
5
+ function isAuthQueryStateEqual(a, b) {
6
+ return isJsonEqual(a.data, b.data) && a.error === b.error && a.isPending === b.isPending && a.isRefetching === b.isRefetching && a.refetch === b.refetch;
7
+ }
4
8
  const useAuthQuery = (initializedAtom, path, $fetch, options) => {
5
9
  const value = atom({
6
10
  data: null,
@@ -9,6 +13,7 @@ const useAuthQuery = (initializedAtom, path, $fetch, options) => {
9
13
  isRefetching: false,
10
14
  refetch: (queryParams) => fn(queryParams)
11
15
  });
16
+ withEquality(value, isAuthQueryStateEqual);
12
17
  const fn = async (queryParams) => {
13
18
  return new Promise((resolve) => {
14
19
  const opts = typeof options === "function" ? options({
@@ -23,8 +28,10 @@ const useAuthQuery = (initializedAtom, path, $fetch, options) => {
23
28
  ...queryParams?.query
24
29
  },
25
30
  async onSuccess(context) {
31
+ const current = value.get();
32
+ const stableData = current.data != null && context.data != null && isJsonEqual(current.data, context.data) ? current.data : context.data;
26
33
  value.set({
27
- data: context.data,
34
+ data: stableData,
28
35
  error: null,
29
36
  isPending: false,
30
37
  isRefetching: false,
@@ -73,23 +80,26 @@ const useAuthQuery = (initializedAtom, path, $fetch, options) => {
73
80
  };
74
81
  initializedAtom = Array.isArray(initializedAtom) ? initializedAtom : [initializedAtom];
75
82
  let isInitialized = false;
76
- for (const initAtom of initializedAtom) initAtom.subscribe(async () => {
77
- if (isServer()) return;
78
- if (isInitialized) await fn();
79
- else onMount(value, () => {
80
- const timeoutId = setTimeout(async () => {
81
- if (!isInitialized) {
82
- isInitialized = true;
83
- await fn();
84
- }
85
- }, 0);
86
- return () => {
87
- value.off();
88
- initAtom.off();
89
- clearTimeout(timeoutId);
90
- };
83
+ const cleanups = [];
84
+ for (const initAtom of initializedAtom) {
85
+ const unbind = initAtom.subscribe(async () => {
86
+ if (isServer()) return;
87
+ if (isInitialized) await fn();
88
+ else onMount(value, () => {
89
+ const timeoutId = setTimeout(async () => {
90
+ if (!isInitialized) {
91
+ isInitialized = true;
92
+ await fn();
93
+ }
94
+ }, 0);
95
+ return () => {
96
+ for (const u of cleanups) u();
97
+ clearTimeout(timeoutId);
98
+ };
99
+ });
91
100
  });
92
- });
101
+ cleanups.push(unbind);
102
+ }
93
103
  return value;
94
104
  };
95
105
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import { ExtractPluginField, HasRequiredKeys, InferPluginFieldFromTuple, IsAny, OverrideMerge, Prettify, PrettifyDeep, RequiredKeysOf, StripEmptyObjects, UnionToIntersection } from "../../types/helper.mjs";
2
2
  import { InferActions, InferClientAPI, InferErrorCodes, IsSignal, SessionQueryParams } from "../types.mjs";
3
3
  import { useStore } from "./react-store.mjs";
4
- import { BetterAuthClientOptions, BetterAuthClientPlugin } from "@better-auth/core";
4
+ import { BetterAuthClientOptions } from "@better-auth/core";
5
5
  import { BASE_ERROR_CODES } from "@better-auth/core/error";
6
6
  import * as nanostores from "nanostores";
7
7
  import * as _better_fetch_fetch0 from "@better-fetch/fetch";
@@ -12,7 +12,9 @@ export * from "@better-fetch/fetch";
12
12
  //#region src/client/react/index.d.ts
13
13
  type InferResolvedHooks<O extends BetterAuthClientOptions> = O extends {
14
14
  plugins: Array<infer Plugin>;
15
- } ? UnionToIntersection<Plugin extends BetterAuthClientPlugin ? Plugin["getAtoms"] extends ((fetch: any) => infer Atoms) ? Atoms extends Record<string, any> ? { [key in keyof Atoms as IsSignal<key> extends true ? never : key extends string ? `use${Capitalize<key>}` : never]: () => ReturnType<Atoms[key]["get"]> } : {} : {} : {}> : {};
15
+ } ? UnionToIntersection<Plugin extends {
16
+ getAtoms?: infer GetAtoms;
17
+ } ? GetAtoms extends ((fetch: any) => infer Atoms) ? Atoms extends Record<string, any> ? { [key in keyof Atoms as IsSignal<key> extends true ? never : key extends string ? `use${Capitalize<key>}` : never]: () => ReturnType<Atoms[key]["get"]> } : {} : {} : {}> : {};
16
18
  declare function createAuthClient<Option extends BetterAuthClientOptions>(options?: Option | undefined): UnionToIntersection<InferResolvedHooks<Option>> & InferClientAPI<Option> & InferActions<Option> & {
17
19
  useSession: () => {
18
20
  data: InferClientAPI<Option> extends {