Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-request-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nestm/better-auth": patch
---

Export a plugin-aware `BetterAuthRequestState` contract and make the resolved-session request marker interoperable across duplicate package copies.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
## Install

```bash
pnpm add @nestm/better-auth better-auth
pnpm add @nestm/better-auth@alpha better-auth
```

## Quick start
Expand Down Expand Up @@ -251,6 +251,11 @@ materialized.
`.getSession(headers)`. The raw instance is injectable via `@InjectBetterAuth()` or the
`BETTER_AUTH_INSTANCE` token; the resolved mount path via `BETTER_AUTH_BASE_PATH`.

HTTP adapters and application request augmentations can extend `BetterAuthRequestState` instead
of recreating Better Auth's plugin-aware `session` and `user` fields. Its resolved-session marker
uses the global symbol registry so guards and decorators remain compatible across duplicate package
copies.

## Body parsing

Unlike previous NestJS integrations, **you do not need `bodyParser: false`**. The mount
Expand Down
2 changes: 1 addition & 1 deletion src/better-auth.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type DatabaseHookPhase = "before" | "after";
* deliberately skipped) the session for a request, so a second guard pass and
* `@Session()` can tell "no session" apart from "guard never ran".
*/
export const SESSION_RESOLVED = Symbol("better_auth:session_resolved");
export const SESSION_RESOLVED = Symbol.for("@nestm/better-auth:session-resolved");

/** Namespaced metadata keys used by the guard-facing decorators. */
export const METADATA_KEY = {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export {
type AuthContextOf,
type AuthHookContext,
type AuthUser,
type BetterAuthRequestState,
type BetterAuthTypeRegistry,
type InferAuth,
type RegisteredAuth,
Expand Down
8 changes: 8 additions & 0 deletions src/types/auth.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Auth, BetterAuthOptions } from "better-auth";
import type { createAuthMiddleware } from "better-auth/api";
import { SESSION_RESOLVED } from "../better-auth.constants.ts";

/**
* Any better-auth instance, regardless of plugin/options generics. This is a
Expand Down Expand Up @@ -60,6 +61,13 @@ export type UserSession<TAuth extends AnyAuth = RegisteredAuth> = TAuth["$Infer"
/** The user model as inferred from the auth instance. */
export type AuthUser<TAuth extends AnyAuth = RegisteredAuth> = UserSession<TAuth>["user"];

/** Request fields populated by `BetterAuthGuard`, including the resolved-session marker. */
export interface BetterAuthRequestState<TAuth extends AnyAuth = RegisteredAuth> {
session?: UserSession<TAuth> | null;
user?: AuthUser<TAuth> | null;
[SESSION_RESOLVED]?: boolean;
}

/** The resolved better-auth `AuthContext` for the given auth instance. */
export type AuthContextOf<TAuth extends AnyAuth = RegisteredAuth> = Awaited<TAuth["$context"]>;

Expand Down
4 changes: 4 additions & 0 deletions tests/unit/exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,8 @@ describe("barrel exports", () => {
it("Public is an alias of AllowAnonymous", () => {
expect(barrel.Public).toBe(barrel.AllowAnonymous);
});

it("uses a globally registered resolved-session marker", () => {
expect(barrel.SESSION_RESOLVED).toBe(Symbol.for("@nestm/better-auth:session-resolved"));
});
});
27 changes: 24 additions & 3 deletions tests/unit/type-assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
*/
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import type { AnyAuth, AuthUser, RegisteredAuth, UserSession } from "../../src/index.ts";
import { BetterAuthService, resolveAuthBasePath } from "../../src/index.ts";
import type {
AnyAuth,
AuthUser,
BetterAuthRequestState,
RegisteredAuth,
UserSession,
} from "../../src/index.ts";
import { BetterAuthService, resolveAuthBasePath, SESSION_RESOLVED } from "../../src/index.ts";
import type { BetterAuthModuleOptions } from "../../src/index.ts";

const strictAuth = betterAuth({
Expand All @@ -31,12 +37,27 @@ const tenantId: string = session.user.tenantId;
declare const service: BetterAuthService<typeof strictAuth>;
const api: (typeof strictAuth)["api"] = service.api;

declare const requestState: BetterAuthRequestState<typeof strictAuth>;
const requestSession: StrictSession | null | undefined = requestState.session;
const requestUser: StrictUser | null | undefined = requestState.user;
const sessionResolved: boolean | undefined = requestState[SESSION_RESOLVED];

// Un-augmented registry falls back to plain Auth without erroring.
type DefaultSession = UserSession<RegisteredAuth>;
declare const defaultSession: DefaultSession;
const email: string = defaultSession.user.email;

const basePath: string = resolveAuthBasePath(strictAuth);

export { asAny, asOptions, tenantId, api, email, basePath };
export {
asAny,
asOptions,
tenantId,
api,
requestSession,
requestUser,
sessionResolved,
email,
basePath,
};
export type { StrictSession, StrictUser };
Loading