@delmaredigital/payload-better-auth 0.3.12 → 0.3.14

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.
package/README.md CHANGED
@@ -552,24 +552,62 @@ betterAuthStrategy({
552
552
  |--------|------|-------------|
553
553
  | `usersCollection` | `string` | The collection slug for users (default: `'users'`) |
554
554
 
555
- ### `getServerSession(payload, headers)`
555
+ ### `getServerSession<TUser>(payload, headers)`
556
556
 
557
- Get the current session on the server.
557
+ Get the current session on the server. Pass your Payload `User` type for full type safety:
558
558
 
559
559
  ```ts
560
- const session = await getServerSession(payload, headersList)
561
- // Returns: { user: { id, email, name, ... }, session: { id, expiresAt, ... } } | null
560
+ import { getServerSession } from '@delmaredigital/payload-better-auth'
561
+ import type { User } from '@/payload-types'
562
+
563
+ const session = await getServerSession<User>(payload, headersList)
564
+ // session.user.role, session.user.firstName, etc. are fully typed
565
+ ```
566
+
567
+ ### `getServerUser<TUser>(payload, headers)`
568
+
569
+ Get the current user on the server (shorthand for `session.user`):
570
+
571
+ ```ts
572
+ import { getServerUser } from '@delmaredigital/payload-better-auth'
573
+ import type { User } from '@/payload-types'
574
+
575
+ const user = await getServerUser<User>(payload, headersList)
576
+ // user.role, user.firstName, etc. are fully typed
562
577
  ```
563
578
 
564
- ### `getServerUser(payload, headers)`
579
+ ### `createSessionHelpers<TUser>(options?)`
565
580
 
566
- Get the current user on the server (shorthand for `session.user`).
581
+ Create typed session helpers bound to your User type. Define once, import everywhere — no generics needed at call sites:
567
582
 
568
583
  ```ts
569
- const user = await getServerUser(payload, headersList)
570
- // Returns: { id, email, name, ... } | null
584
+ // lib/auth.ts
585
+ import { createSessionHelpers } from '@delmaredigital/payload-better-auth'
586
+ import type { User } from '@/payload-types'
587
+
588
+ export const { getServerSession, getServerUser } = createSessionHelpers<User>()
571
589
  ```
572
590
 
591
+ ```ts
592
+ // app/page.tsx
593
+ import { getServerSession } from '@/lib/auth'
594
+
595
+ const session = await getServerSession(payload, headersList)
596
+ // session.user is typed as User — no generic needed
597
+ ```
598
+
599
+ **Serial IDs (Payload default):** Better Auth always returns string IDs from `api.getSession()`, which causes Payload relationship fields to reject them. Pass `idType: 'number'` to coerce ID fields to numbers automatically:
600
+
601
+ ```ts
602
+ export const { getServerSession, getServerUser } = createSessionHelpers<User>({
603
+ idType: 'number', // coerces user.id, session.userId, etc. to numbers
604
+ })
605
+ ```
606
+
607
+ | Option | Type | Description |
608
+ |--------|------|-------------|
609
+ | `idType` | `'number' \| 'text'` | Set to `'number'` when using serial IDs to coerce string IDs to numbers. Matches the adapter's `adapterConfig.idType` option. |
610
+
573
611
  ### `withBetterAuthDefaults(options)`
574
612
 
575
613
  Applies sensible defaults to Better Auth options. Useful for simplifying common configurations.
package/dist/index.d.ts CHANGED
@@ -22,8 +22,8 @@ export { extractApiKeyFromRequest, getApiKeyInfo, hasScope, hasAnyScope as hasAn
22
22
  export type { ApiKeyInfo, ApiKeyAccessConfig, } from './utils/apiKeyAccess.js';
23
23
  export { detectAuthConfig } from './utils/detectAuthConfig.js';
24
24
  export type { AuthDetectionResult } from './utils/detectAuthConfig.js';
25
- export { getServerSession, getServerUser } from './utils/session.js';
26
- export type { Session } from './utils/session.js';
25
+ export { getServerSession, getServerUser, createSessionHelpers } from './utils/session.js';
26
+ export type { Session, SessionHelperOptions } from './utils/session.js';
27
27
  export { firstUserAdminHooks } from './utils/firstUserAdmin.js';
28
28
  export type { FirstUserAdminOptions } from './utils/firstUserAdmin.js';
29
29
  export { withBetterAuthDefaults, apiKeyWithDefaults } from './utils/betterAuthDefaults.js';
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ export { extractApiKeyFromRequest, getApiKeyInfo, hasScope, hasAnyScope as hasAn
20
20
  // Auth config detection utility
21
21
  export { detectAuthConfig } from './utils/detectAuthConfig.js';
22
22
  // Session utilities
23
- export { getServerSession, getServerUser } from './utils/session.js';
23
+ export { getServerSession, getServerUser, createSessionHelpers } from './utils/session.js';
24
24
  // First user admin hook utility
25
25
  export { firstUserAdminHooks } from './utils/firstUserAdmin.js';
26
26
  // Better Auth defaults utility
@@ -4,14 +4,15 @@
4
4
  * @packageDocumentation
5
5
  */
6
6
  import type { BasePayload } from 'payload';
7
- export type Session = {
8
- user: {
9
- id: string;
10
- email: string;
11
- name?: string;
12
- image?: string;
13
- [key: string]: unknown;
14
- };
7
+ type DefaultUser = {
8
+ id: string;
9
+ email: string;
10
+ name?: string;
11
+ image?: string;
12
+ [key: string]: unknown;
13
+ };
14
+ export type Session<TUser = DefaultUser> = {
15
+ user: TUser;
15
16
  session: {
16
17
  id: string;
17
18
  expiresAt: Date;
@@ -21,35 +22,43 @@ export type Session = {
21
22
  /**
22
23
  * Get the current session from headers.
23
24
  *
25
+ * Accepts an optional generic type parameter to narrow the user type.
26
+ * Pass your Payload-generated `User` type for full type safety.
27
+ *
24
28
  * @example
25
29
  * ```ts
26
30
  * import { headers } from 'next/headers'
27
- * import { getServerSession } from '@delmare/payload-better-auth'
31
+ * import { getServerSession } from '@delmaredigital/payload-better-auth'
32
+ * import type { User } from '@/payload-types'
28
33
  *
29
34
  * export default async function Page() {
30
35
  * const headersList = await headers()
31
- * const session = await getServerSession(payload, headersList)
36
+ * const session = await getServerSession<User>(payload, headersList)
32
37
  *
33
38
  * if (!session) {
34
39
  * redirect('/login')
35
40
  * }
36
41
  *
42
+ * // session.user.role is fully typed
37
43
  * return <div>Hello {session.user.name}</div>
38
44
  * }
39
45
  * ```
40
46
  */
41
- export declare function getServerSession(payload: BasePayload, headers: Headers): Promise<Session | null>;
47
+ export declare function getServerSession<TUser = DefaultUser>(payload: BasePayload, headers: Headers): Promise<Session<TUser> | null>;
42
48
  /**
43
49
  * Get the current user from the session.
44
50
  *
51
+ * Accepts an optional generic type parameter to narrow the user type.
52
+ *
45
53
  * @example
46
54
  * ```ts
47
55
  * import { headers } from 'next/headers'
48
- * import { getServerUser } from '@delmare/payload-better-auth'
56
+ * import { getServerUser } from '@delmaredigital/payload-better-auth'
57
+ * import type { User } from '@/payload-types'
49
58
  *
50
59
  * export default async function Page() {
51
60
  * const headersList = await headers()
52
- * const user = await getServerUser(payload, headersList)
61
+ * const user = await getServerUser<User>(payload, headersList)
53
62
  *
54
63
  * if (!user) {
55
64
  * redirect('/login')
@@ -59,4 +68,54 @@ export declare function getServerSession(payload: BasePayload, headers: Headers)
59
68
  * }
60
69
  * ```
61
70
  */
62
- export declare function getServerUser(payload: BasePayload, headers: Headers): Promise<Session['user'] | null>;
71
+ export declare function getServerUser<TUser = DefaultUser>(payload: BasePayload, headers: Headers): Promise<TUser | null>;
72
+ export type SessionHelperOptions = {
73
+ /**
74
+ * ID type strategy matching your adapter's `adapterConfig.idType`.
75
+ *
76
+ * Set to `'number'` when using Payload's default serial IDs.
77
+ * Better Auth always returns string IDs from `api.getSession()` —
78
+ * this option coerces `id` and `*Id` / `*_id` fields to numbers
79
+ * so they work directly in Payload relationship fields.
80
+ *
81
+ * @default undefined (no coercion)
82
+ */
83
+ idType?: 'number' | 'text';
84
+ };
85
+ /**
86
+ * Create typed session helpers bound to your User type.
87
+ *
88
+ * Define once in a shared file, then import the typed helpers
89
+ * everywhere — no generics needed at call sites.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * // lib/auth.ts
94
+ * import { createSessionHelpers } from '@delmaredigital/payload-better-auth'
95
+ * import type { User } from '@/payload-types'
96
+ *
97
+ * export const { getServerSession, getServerUser } = createSessionHelpers<User>()
98
+ * ```
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * // With serial IDs (Payload default) — coerces string IDs to numbers
103
+ * export const { getServerSession, getServerUser } = createSessionHelpers<User>({
104
+ * idType: 'number',
105
+ * })
106
+ * ```
107
+ *
108
+ * ```ts
109
+ * // app/page.tsx
110
+ * import { getServerSession } from '@/lib/auth'
111
+ *
112
+ * const session = await getServerSession(payload, headersList)
113
+ * // session.user is typed as User — no generic needed
114
+ * // session.user.id is a number when idType: 'number'
115
+ * ```
116
+ */
117
+ export declare function createSessionHelpers<TUser = DefaultUser>(options?: SessionHelperOptions): {
118
+ getServerSession: (payload: BasePayload, headers: Headers) => Promise<Session<TUser> | null>;
119
+ getServerUser: (payload: BasePayload, headers: Headers) => Promise<TUser | null>;
120
+ };
121
+ export {};
@@ -5,19 +5,24 @@
5
5
  */ /**
6
6
  * Get the current session from headers.
7
7
  *
8
+ * Accepts an optional generic type parameter to narrow the user type.
9
+ * Pass your Payload-generated `User` type for full type safety.
10
+ *
8
11
  * @example
9
12
  * ```ts
10
13
  * import { headers } from 'next/headers'
11
- * import { getServerSession } from '@delmare/payload-better-auth'
14
+ * import { getServerSession } from '@delmaredigital/payload-better-auth'
15
+ * import type { User } from '@/payload-types'
12
16
  *
13
17
  * export default async function Page() {
14
18
  * const headersList = await headers()
15
- * const session = await getServerSession(payload, headersList)
19
+ * const session = await getServerSession<User>(payload, headersList)
16
20
  *
17
21
  * if (!session) {
18
22
  * redirect('/login')
19
23
  * }
20
24
  *
25
+ * // session.user.role is fully typed
21
26
  * return <div>Hello {session.user.name}</div>
22
27
  * }
23
28
  * ```
@@ -40,14 +45,17 @@
40
45
  /**
41
46
  * Get the current user from the session.
42
47
  *
48
+ * Accepts an optional generic type parameter to narrow the user type.
49
+ *
43
50
  * @example
44
51
  * ```ts
45
52
  * import { headers } from 'next/headers'
46
- * import { getServerUser } from '@delmare/payload-better-auth'
53
+ * import { getServerUser } from '@delmaredigital/payload-better-auth'
54
+ * import type { User } from '@/payload-types'
47
55
  *
48
56
  * export default async function Page() {
49
57
  * const headersList = await headers()
50
- * const user = await getServerUser(payload, headersList)
58
+ * const user = await getServerUser<User>(payload, headersList)
51
59
  *
52
60
  * if (!user) {
53
61
  * redirect('/login')
@@ -60,3 +68,70 @@
60
68
  const session = await getServerSession(payload, headers);
61
69
  return session?.user ?? null;
62
70
  }
71
+ /**
72
+ * Coerce numeric-string ID fields to numbers on a shallow object.
73
+ * Matches the adapter's heuristic: `id`, fields ending in `Id` or `_id`.
74
+ */ function coerceIds(obj) {
75
+ if (!obj || typeof obj !== 'object') return obj;
76
+ const result = {
77
+ ...obj
78
+ };
79
+ for (const [key, value] of Object.entries(result)){
80
+ if (typeof value !== 'string') continue;
81
+ if (key === 'id' || /(?:Id|_id)$/.test(key)) {
82
+ if (/^\d+$/.test(value)) {
83
+ result[key] = parseInt(value, 10);
84
+ }
85
+ }
86
+ }
87
+ return result;
88
+ }
89
+ /**
90
+ * Create typed session helpers bound to your User type.
91
+ *
92
+ * Define once in a shared file, then import the typed helpers
93
+ * everywhere — no generics needed at call sites.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * // lib/auth.ts
98
+ * import { createSessionHelpers } from '@delmaredigital/payload-better-auth'
99
+ * import type { User } from '@/payload-types'
100
+ *
101
+ * export const { getServerSession, getServerUser } = createSessionHelpers<User>()
102
+ * ```
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * // With serial IDs (Payload default) — coerces string IDs to numbers
107
+ * export const { getServerSession, getServerUser } = createSessionHelpers<User>({
108
+ * idType: 'number',
109
+ * })
110
+ * ```
111
+ *
112
+ * ```ts
113
+ * // app/page.tsx
114
+ * import { getServerSession } from '@/lib/auth'
115
+ *
116
+ * const session = await getServerSession(payload, headersList)
117
+ * // session.user is typed as User — no generic needed
118
+ * // session.user.id is a number when idType: 'number'
119
+ * ```
120
+ */ export function createSessionHelpers(options) {
121
+ const shouldCoerceIds = options?.idType === 'number';
122
+ const typedGetServerSession = async (payload, headers)=>{
123
+ const session = await getServerSession(payload, headers);
124
+ if (!session || !shouldCoerceIds) return session;
125
+ return {
126
+ user: coerceIds(session.user),
127
+ session: coerceIds(session.session)
128
+ };
129
+ };
130
+ return {
131
+ getServerSession: typedGetServerSession,
132
+ getServerUser: async (payload, headers)=>{
133
+ const session = await typedGetServerSession(payload, headers);
134
+ return session?.user ?? null;
135
+ }
136
+ };
137
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delmaredigital/payload-better-auth",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "Better Auth adapter and plugins for Payload CMS",
5
5
  "type": "module",
6
6
  "license": "MIT",