[PR #8824] fix(types): preserve plugin endpoint types on server-side auth.api #16484

Open
opened 2026-04-13 10:32:20 -05:00 by GiteaMirror · 0 comments
Owner

Original Pull Request: https://github.com/better-auth/better-auth/pull/8824

State: open
Merged: No


Summary

Fixes server-side auth.api losing plugin-contributed endpoint types (e.g., createUser, removeUser from admin()) when betterAuth() is used in a lazy singleton / factory pattern.

Root cause: All plugin type extraction utilities use Array<infer T> which silently fails on ReadonlyArray. Combined with no const generic on betterAuth(), TypeScript can widen the plugin tuple and erase endpoint types.

Changes:

  • Accept readonly BetterAuthPlugin[] in BetterAuthOptions.plugins
  • Handle ReadonlyArray<infer T> in all server-side plugin type extraction:
    • PluginEndpoint (in getEndpoints)
    • InferPluginTypes, InferPluginErrorCodes, InferPluginIDs, InferPluginContext
    • InferDBFieldsFromPluginsInput, InferDBFieldsFromPlugins (also updated tuple destructuring to readonly [infer P, ...infer Rest])
    • InferPluginID, InferPluginOptions (in context.ts — fixes hasPlugin/getPlugin inference)
  • Add const type parameter to betterAuth() (full + minimal) for narrower inference
  • Add type regression tests: lazy singleton, mixed plugins, as const readonly, and $context hasPlugin/getPlugin

Files changed (9):

  • packages/core/src/types/init-options.ts — accept readonly plugins
  • packages/core/src/types/context.tsReadonlyArray in InferPluginID + InferPluginOptions
  • packages/core/src/db/type.ts — readonly constraint + tuple destructuring in InferDBFieldsFromPlugins*
  • packages/better-auth/src/auth/full.tsconst generic
  • packages/better-auth/src/auth/minimal.tsconst generic
  • packages/better-auth/src/api/index.tsReadonlyArray in PluginEndpoint
  • packages/better-auth/src/types/models.tsReadonlyArray in InferPluginTypes
  • packages/better-auth/src/types/plugins.tsReadonlyArray in 3 utility types
  • packages/better-auth/src/types/types.test.ts — 4 type regression tests

Known limitations:

  • satisfies BetterAuthOptions pattern: If options are contextually typed via satisfies before reaching betterAuth(), plugins may already be widened to BetterAuthPlugin[]. The const generic cannot recover types that were already erased. Inline options or as const work correctly.
  • Client-side consumers: client/types.ts and client/plugins/infer-plugin.ts also use Array<infer P>. Those are left out of scope for this server-side fix. Happy to expand or open a follow-up PR for #7043.
  • TS version: The const type parameter requires TypeScript 5.0+. The repo uses TS ^5.9.3. If Better Auth needs to support TS 4.x consumers, this would need a different approach.

Fixes #8823
Refs #3015

**Original Pull Request:** https://github.com/better-auth/better-auth/pull/8824 **State:** open **Merged:** No --- ## Summary Fixes server-side `auth.api` losing plugin-contributed endpoint types (e.g., `createUser`, `removeUser` from `admin()`) when `betterAuth()` is used in a lazy singleton / factory pattern. **Root cause:** All plugin type extraction utilities use `Array<infer T>` which silently fails on `ReadonlyArray`. Combined with no `const` generic on `betterAuth()`, TypeScript can widen the plugin tuple and erase endpoint types. **Changes:** - Accept `readonly BetterAuthPlugin[]` in `BetterAuthOptions.plugins` - Handle `ReadonlyArray<infer T>` in all server-side plugin type extraction: - `PluginEndpoint` (in `getEndpoints`) - `InferPluginTypes`, `InferPluginErrorCodes`, `InferPluginIDs`, `InferPluginContext` - `InferDBFieldsFromPluginsInput`, `InferDBFieldsFromPlugins` (also updated tuple destructuring to `readonly [infer P, ...infer Rest]`) - `InferPluginID`, `InferPluginOptions` (in `context.ts` — fixes `hasPlugin`/`getPlugin` inference) - Add `const` type parameter to `betterAuth()` (full + minimal) for narrower inference - Add type regression tests: lazy singleton, mixed plugins, `as const` readonly, and `$context` hasPlugin/getPlugin **Files changed (9):** - `packages/core/src/types/init-options.ts` — accept readonly plugins - `packages/core/src/types/context.ts` — `ReadonlyArray` in `InferPluginID` + `InferPluginOptions` - `packages/core/src/db/type.ts` — readonly constraint + tuple destructuring in `InferDBFieldsFromPlugins*` - `packages/better-auth/src/auth/full.ts` — `const` generic - `packages/better-auth/src/auth/minimal.ts` — `const` generic - `packages/better-auth/src/api/index.ts` — `ReadonlyArray` in `PluginEndpoint` - `packages/better-auth/src/types/models.ts` — `ReadonlyArray` in `InferPluginTypes` - `packages/better-auth/src/types/plugins.ts` — `ReadonlyArray` in 3 utility types - `packages/better-auth/src/types/types.test.ts` — 4 type regression tests **Known limitations:** - **`satisfies BetterAuthOptions` pattern:** If options are contextually typed via `satisfies` before reaching `betterAuth()`, plugins may already be widened to `BetterAuthPlugin[]`. The `const` generic cannot recover types that were already erased. Inline options or `as const` work correctly. - **Client-side consumers:** `client/types.ts` and `client/plugins/infer-plugin.ts` also use `Array<infer P>`. Those are left out of scope for this server-side fix. Happy to expand or open a follow-up PR for #7043. - **TS version:** The `const` type parameter requires TypeScript 5.0+. The repo uses TS ^5.9.3. If Better Auth needs to support TS 4.x consumers, this would need a different approach. Fixes #8823 Refs #3015
GiteaMirror added the pull-request label 2026-04-13 10:32:20 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#16484