BA
Better Auth•2mo ago
Daniel

The inferred type of 'authClient' cannot be named without a reference...

I was trying to create a shared auth client package in my monorepo, since I have multiple frontends authenticating with the same server.. But I keep getting an error like this (twice) even before adding any plugins... Literally.. this is my entire code:
import { createAuthClient } from "better-auth/client";

export const authClient = (baseURL: string) => {
return createAuthClient({
baseURL,
});
};
import { createAuthClient } from "better-auth/client";

export const authClient = (baseURL: string) => {
return createAuthClient({
baseURL,
});
};
I can't disable declaration in my tsconfig, since it's a shared package in my monorepo, and I need the type safety. I can't add an explicit return type like ReturnType<typeof createAuthClient> since that ruins type safety for any plugins I might add.. Is this a bug in better-auth? Or did I get something wrong with the configuration
9 Replies
Daniel
DanielOP•2mo ago
I'm getting this error twice: The inferred type of 'authClient' cannot be named without a reference to '../node_modules/better-auth/dist/shared/better-auth.ClXlabtY.js'. This is likely not portable. A type annotation is necessary.ts(2742) The inferred type of 'authClient' cannot be named without a reference to '../node_modules/better-auth/dist/shared/better-auth.ZSfSbnQT.js'. This is likely not portable. A type annotation is necessary.ts(2742) There seems to be a few issues open about this from March, but they're marked as solved, which is really confusing.. I really want to work with better-auth on this project. But this is like the third typescript-related issue I ran into and it's really giving me a headache. Maybe I'm just doing something wrong.
DN_Dev
DN_Dev•2mo ago
I had similar issues, worked around this by doing things like this:
export const BETTER_AUTH_DEFAULT_SESSION = {
modelName: BETTER_AUTH_TABLE.AUTH_SESSIONS,
fields: {
userId: BETTER_AUTH_FIELD.USER_ID,
expiresAt: BETTER_AUTH_FIELD.EXPIRES_AT,
ipAddress: BETTER_AUTH_FIELD.IP_ADDRESS,
userAgent: BETTER_AUTH_FIELD.USER_AGENT,
createdAt: BETTER_AUTH_FIELD.CREATED_AT,
updatedAt: BETTER_AUTH_FIELD.UPDATED_AT,
},
disableSessionRefresh: false,
preserveSessionInDatabase: true,
storeSessionInDatabase: true,
cookieCache: {
enabled: !isDevelopmentMode(),
maxAge: SESSION_COOKIE_CACHE_MAX_AGE_IN_SECS,
},
updateAge: SESSION_UPDATE_AGE_IN_SECS,
expiresIn: SESSION_MAX_AGE_IN_SECS,
freshAge: SESSION_FRESH_AGE_IN_SECS,
} satisfies BetterAuthOptions["session"];

---

export const defaultBetterAuthConfig = {
user: { ...BETTER_AUTH_DEFAULT_USER },
session: {
...BETTER_AUTH_DEFAULT_SESSION,
},
account: {
...BETTER_AUTH_DEFAULT_ACCOUNT,
},
verification: {
...BETTER_AUTH_DEFAULT_VERIFICATION,
},
rateLimit: {
...BETTER_AUTH_DEFAULT_RATE_LIMIT,
},
emailAndPassword: {
...BETTER_AUTH_DEFAULT_EMAIL_AND_PASSWORD,
},
emailVerification: {
...BETTER_AUTH_DEFAULT_EMAIL_VERIFICATION,
},
advanced: {
...BETTER_AUTH_DEFAULT_ADVANCED,
},
onAPIError: {
...BETTER_AUTH_ON_API_ERROR,
},
} as const;
export const BETTER_AUTH_DEFAULT_SESSION = {
modelName: BETTER_AUTH_TABLE.AUTH_SESSIONS,
fields: {
userId: BETTER_AUTH_FIELD.USER_ID,
expiresAt: BETTER_AUTH_FIELD.EXPIRES_AT,
ipAddress: BETTER_AUTH_FIELD.IP_ADDRESS,
userAgent: BETTER_AUTH_FIELD.USER_AGENT,
createdAt: BETTER_AUTH_FIELD.CREATED_AT,
updatedAt: BETTER_AUTH_FIELD.UPDATED_AT,
},
disableSessionRefresh: false,
preserveSessionInDatabase: true,
storeSessionInDatabase: true,
cookieCache: {
enabled: !isDevelopmentMode(),
maxAge: SESSION_COOKIE_CACHE_MAX_AGE_IN_SECS,
},
updateAge: SESSION_UPDATE_AGE_IN_SECS,
expiresIn: SESSION_MAX_AGE_IN_SECS,
freshAge: SESSION_FRESH_AGE_IN_SECS,
} satisfies BetterAuthOptions["session"];

---

export const defaultBetterAuthConfig = {
user: { ...BETTER_AUTH_DEFAULT_USER },
session: {
...BETTER_AUTH_DEFAULT_SESSION,
},
account: {
...BETTER_AUTH_DEFAULT_ACCOUNT,
},
verification: {
...BETTER_AUTH_DEFAULT_VERIFICATION,
},
rateLimit: {
...BETTER_AUTH_DEFAULT_RATE_LIMIT,
},
emailAndPassword: {
...BETTER_AUTH_DEFAULT_EMAIL_AND_PASSWORD,
},
emailVerification: {
...BETTER_AUTH_DEFAULT_EMAIL_VERIFICATION,
},
advanced: {
...BETTER_AUTH_DEFAULT_ADVANCED,
},
onAPIError: {
...BETTER_AUTH_ON_API_ERROR,
},
} as const;
You just have to break the config down into smaller parts, only the keys would not be type safe but I found that to be fine And for the plugins, I did
export const baseDefaultUsernamePlugin = Object.freeze({
schema: {
user: {
modelName: BETTER_AUTH_TABLE.AUTH_USERS,
fields: { displayUsername: BETTER_AUTH_FIELD.DISPLAY_USERNAME },
},
},
minUsernameLength: USERNAME_MIN_LENGTH,
maxUsernameLength: USERNAME_MAX_LENGTH,
} satisfies Parameters<typeof username>[0]);
export const baseDefaultUsernamePlugin = Object.freeze({
schema: {
user: {
modelName: BETTER_AUTH_TABLE.AUTH_USERS,
fields: { displayUsername: BETTER_AUTH_FIELD.DISPLAY_USERNAME },
},
},
minUsernameLength: USERNAME_MIN_LENGTH,
maxUsernameLength: USERNAME_MAX_LENGTH,
} satisfies Parameters<typeof username>[0]);
Spread your configs into your betterAuth function in each app that uses it, and you should have decent enough type safety there
Daniel
DanielOP•2mo ago
This just seems like terrible developer experience 🥲 I'm not sure if I fully understand yet.. but I did something like:
import { createAuthClient } from "better-auth/client";
import { adminClient } from "better-auth/client/plugins";

const defaultConfig = {
baseURL: "http://localhost:3000",
plugins: [adminClient()],
} satisfies Parameters<typeof createAuthClient>[0];

export const authClient = (
baseURL: string
): ReturnType<typeof createAuthClient<typeof defaultConfig>> => {
return createAuthClient({
...defaultConfig,
baseURL,
});
};
import { createAuthClient } from "better-auth/client";
import { adminClient } from "better-auth/client/plugins";

const defaultConfig = {
baseURL: "http://localhost:3000",
plugins: [adminClient()],
} satisfies Parameters<typeof createAuthClient>[0];

export const authClient = (
baseURL: string
): ReturnType<typeof createAuthClient<typeof defaultConfig>> => {
return createAuthClient({
...defaultConfig,
baseURL,
});
};
for example, and the error still appears, just now for defaultConfig instead of authClient
The inferred type of 'defaultConfig' cannot be named without a reference to '../node_modules/better-auth/dist/shared/better-auth.2lcgvW_O.js'. This is likely not portable. A type annotation is necessary.ts(2742)
The inferred type of 'defaultConfig' cannot be named without a reference to '../node_modules/better-auth/dist/shared/better-auth.2lcgvW_O.js'. This is likely not portable. A type annotation is necessary.ts(2742)
DN_Dev
DN_Dev•2mo ago
Yeah it's not great when it comes to this. If you spread the entire objects you'll get these issues unfortunately. Instead of satisfies Parameters<typeof createAuthClient>[0] you should use as const and type each key if you need to instead e.g.
type AuthClient = Parameters<typeof createAuthClient>[0]
const defaultConfig = {
baseURL: "http://localhost:3000" satisfies AuthClient["baseURL"],
plugins: [adminClient()] satisfies AuthClient["plugins"],
} as const
type AuthClient = Parameters<typeof createAuthClient>[0]
const defaultConfig = {
baseURL: "http://localhost:3000" satisfies AuthClient["baseURL"],
plugins: [adminClient()] satisfies AuthClient["plugins"],
} as const
Something like that...unfortunately that means you lose auto suggestion for config keys but I found that to be manageable. Each key can be copied from the indexed access type i.e. AuthClient["copy-this-here"]
Daniel
DanielOP•2mo ago
I did this, and I still don't get type safety for authClient.admin for example.. Is that what you meant? Cause then I could just set the return type explicitly to ReturnType<typeof createAuthClient>
DN_Dev
DN_Dev•2mo ago
Sorry, typing plugins explicitly like that would mean you lose type safety there. Here's an e.g. of my plugins array which is passed to my betterAuth instance.
const plugins = [
twoFactor({
...baseDefaultTwoFactorPlugin,
}),
username({
...baseDefaultUsernamePlugin,
usernameValidator: getUsernameValidator(),
}),
magicLink({
...getBaseDefaultMagicLinkPlugin({
sendMagicLink: () => {},
}),
}),
emailOTP({
...getBaseDefaultEmailOtpPlugin({
sendVerificationOTP: async () => {
return;
},
}),
}),
haveIBeenPwned({
...baseDefaultHaveIBeenPwnedPlugin,
}),
captcha({
...getBaseDefaultCaptchaPlugin({
provider: "google-recaptcha",
secretKey: ENV_SERVER.GOOGLE_RECAPTCHA_SECRET_KEY,
}),
}),
admin({ ...baseDefaultAdminPlugin }),
organization({ ...baseDefaultOrganizationPlugin }),
];
const plugins = [
twoFactor({
...baseDefaultTwoFactorPlugin,
}),
username({
...baseDefaultUsernamePlugin,
usernameValidator: getUsernameValidator(),
}),
magicLink({
...getBaseDefaultMagicLinkPlugin({
sendMagicLink: () => {},
}),
}),
emailOTP({
...getBaseDefaultEmailOtpPlugin({
sendVerificationOTP: async () => {
return;
},
}),
}),
haveIBeenPwned({
...baseDefaultHaveIBeenPwnedPlugin,
}),
captcha({
...getBaseDefaultCaptchaPlugin({
provider: "google-recaptcha",
secretKey: ENV_SERVER.GOOGLE_RECAPTCHA_SECRET_KEY,
}),
}),
admin({ ...baseDefaultAdminPlugin }),
organization({ ...baseDefaultOrganizationPlugin }),
];
Above is what you would need to do in each app that uses plugins. I export the default configs from the auth package in my monorepo. E.g.
export const baseDefaultAdminPlugin = Object.freeze({
schema: {
user: {
modelName: BETTER_AUTH_TABLE.AUTH_ADMINS,
fields: {
banned: BETTER_AUTH_FIELD.IS_BANNED,
banReason: BETTER_AUTH_FIELD.BAN_REASON,
banExpires: BETTER_AUTH_FIELD.BAN_EXPIRES_AT,
},
},
session: {
modelName: BETTER_AUTH_TABLE.AUTH_SESSIONS,
fields: { impersonatedBy: BETTER_AUTH_FIELD.IMPERSONATED_BY },
},
},
} satisfies Parameters<typeof admin>[0]);
export const baseDefaultAdminPlugin = Object.freeze({
schema: {
user: {
modelName: BETTER_AUTH_TABLE.AUTH_ADMINS,
fields: {
banned: BETTER_AUTH_FIELD.IS_BANNED,
banReason: BETTER_AUTH_FIELD.BAN_REASON,
banExpires: BETTER_AUTH_FIELD.BAN_EXPIRES_AT,
},
},
session: {
modelName: BETTER_AUTH_TABLE.AUTH_SESSIONS,
fields: { impersonatedBy: BETTER_AUTH_FIELD.IMPERSONATED_BY },
},
},
} satisfies Parameters<typeof admin>[0]);
Daniel
DanielOP•2mo ago
man. at this point I might as well just create the client in each app separately and just disable declaration in my tsconfig, since they don't export anything anyway. @bekacru any chance this will be addressed in a future update? it really sucks to have to either lose my one source of truth, or have to know how to do awesome typescript backflips like @DN_Dev
DN_Dev
DN_Dev•2mo ago
Yeah that's basically what I had to do, and export as much code as I could to each auth instance in the apps
bekacru
bekacru•2mo ago
will be addressed

Did you find this page helpful?