@delmaredigital/payload-better-auth 0.3.8 → 0.3.10
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/dist/adapter/collections.d.ts +0 -1
- package/dist/adapter/collections.js +0 -2
- package/dist/adapter/index.d.ts +0 -1
- package/dist/adapter/index.js +0 -2
- package/dist/components/BeforeLogin.d.ts +0 -1
- package/dist/components/BeforeLogin.js +0 -2
- package/dist/components/LoginView.d.ts +0 -1
- package/dist/components/LoginView.js +0 -2
- package/dist/components/LoginViewWrapper.d.ts +0 -1
- package/dist/components/LoginViewWrapper.js +0 -2
- package/dist/components/LogoutButton.d.ts +0 -1
- package/dist/components/LogoutButton.js +0 -2
- package/dist/components/PasskeyRegisterButton.d.ts +0 -1
- package/dist/components/PasskeyRegisterButton.js +0 -2
- package/dist/components/PasskeySignInButton.d.ts +0 -1
- package/dist/components/PasskeySignInButton.js +0 -2
- package/dist/components/auth/ForgotPasswordView.d.ts +0 -1
- package/dist/components/auth/ForgotPasswordView.js +0 -2
- package/dist/components/auth/ResetPasswordView.d.ts +0 -1
- package/dist/components/auth/ResetPasswordView.js +0 -2
- package/dist/components/auth/index.d.ts +0 -1
- package/dist/components/auth/index.js +0 -2
- package/dist/components/management/ApiKeysManagementClient.d.ts +0 -1
- package/dist/components/management/ApiKeysManagementClient.js +0 -2
- package/dist/components/management/PasskeysManagementClient.d.ts +0 -1
- package/dist/components/management/PasskeysManagementClient.js +0 -2
- package/dist/components/management/SecurityNavLinks.d.ts +0 -1
- package/dist/components/management/SecurityNavLinks.js +0 -2
- package/dist/components/management/TwoFactorManagementClient.d.ts +0 -1
- package/dist/components/management/TwoFactorManagementClient.js +0 -2
- package/dist/components/management/index.d.ts +0 -1
- package/dist/components/management/index.js +0 -2
- package/dist/components/management/views/ApiKeysView.d.ts +0 -1
- package/dist/components/management/views/ApiKeysView.js +0 -2
- package/dist/components/management/views/PasskeysView.d.ts +0 -1
- package/dist/components/management/views/PasskeysView.js +0 -2
- package/dist/components/management/views/TwoFactorView.d.ts +0 -1
- package/dist/components/management/views/TwoFactorView.js +0 -2
- package/dist/components/management/views/index.d.ts +0 -1
- package/dist/components/management/views/index.js +0 -2
- package/dist/components/twoFactor/TwoFactorSetupView.d.ts +0 -1
- package/dist/components/twoFactor/TwoFactorSetupView.js +0 -2
- package/dist/components/twoFactor/TwoFactorVerifyView.d.ts +0 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.js +0 -2
- package/dist/components/twoFactor/index.d.ts +0 -1
- package/dist/components/twoFactor/index.js +0 -2
- package/dist/exports/client.d.ts +0 -1
- package/dist/exports/client.js +0 -2
- package/dist/exports/components.d.ts +0 -1
- package/dist/exports/components.js +0 -2
- package/dist/exports/management.d.ts +0 -1
- package/dist/exports/management.js +0 -2
- package/dist/exports/rsc.d.ts +0 -1
- package/dist/exports/rsc.js +0 -2
- package/dist/generated-types.d.ts +0 -1
- package/dist/generated-types.js +0 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/plugin/index.d.ts +0 -1
- package/dist/plugin/index.js +0 -2
- package/dist/scripts/generate-types.d.ts +0 -1
- package/dist/scripts/generate-types.js +0 -2
- package/dist/types/apiKey.d.ts +0 -1
- package/dist/types/apiKey.js +0 -2
- package/dist/types/betterAuth.d.ts +0 -1
- package/dist/types/betterAuth.js +0 -2
- package/dist/utils/access.d.ts +0 -1
- package/dist/utils/access.js +0 -2
- package/dist/utils/apiKeyAccess.d.ts +0 -1
- package/dist/utils/apiKeyAccess.js +0 -2
- package/dist/utils/betterAuthDefaults.d.ts +0 -1
- package/dist/utils/betterAuthDefaults.js +0 -2
- package/dist/utils/detectAuthConfig.d.ts +0 -1
- package/dist/utils/detectAuthConfig.js +0 -2
- package/dist/utils/detectEnabledPlugins.d.ts +0 -1
- package/dist/utils/detectEnabledPlugins.js +0 -2
- package/dist/utils/firstUserAdmin.d.ts +0 -1
- package/dist/utils/firstUserAdmin.js +0 -2
- package/dist/utils/generateScopes.d.ts +0 -1
- package/dist/utils/generateScopes.js +0 -2
- package/dist/utils/session.d.ts +0 -1
- package/dist/utils/session.js +0 -2
- package/package.json +34 -91
- package/dist/adapter/collections.d.ts.map +0 -1
- package/dist/adapter/collections.js.map +0 -1
- package/dist/adapter/index.d.ts.map +0 -1
- package/dist/adapter/index.js.map +0 -1
- package/dist/components/BeforeLogin.d.ts.map +0 -1
- package/dist/components/BeforeLogin.js.map +0 -1
- package/dist/components/LoginView.d.ts.map +0 -1
- package/dist/components/LoginView.js.map +0 -1
- package/dist/components/LoginViewWrapper.d.ts.map +0 -1
- package/dist/components/LoginViewWrapper.js.map +0 -1
- package/dist/components/LogoutButton.d.ts.map +0 -1
- package/dist/components/LogoutButton.js.map +0 -1
- package/dist/components/PasskeyRegisterButton.d.ts.map +0 -1
- package/dist/components/PasskeyRegisterButton.js.map +0 -1
- package/dist/components/PasskeySignInButton.d.ts.map +0 -1
- package/dist/components/PasskeySignInButton.js.map +0 -1
- package/dist/components/auth/ForgotPasswordView.d.ts.map +0 -1
- package/dist/components/auth/ForgotPasswordView.js.map +0 -1
- package/dist/components/auth/ResetPasswordView.d.ts.map +0 -1
- package/dist/components/auth/ResetPasswordView.js.map +0 -1
- package/dist/components/auth/index.d.ts.map +0 -1
- package/dist/components/auth/index.js.map +0 -1
- package/dist/components/management/ApiKeysManagementClient.d.ts.map +0 -1
- package/dist/components/management/ApiKeysManagementClient.js.map +0 -1
- package/dist/components/management/PasskeysManagementClient.d.ts.map +0 -1
- package/dist/components/management/PasskeysManagementClient.js.map +0 -1
- package/dist/components/management/SecurityNavLinks.d.ts.map +0 -1
- package/dist/components/management/SecurityNavLinks.js.map +0 -1
- package/dist/components/management/TwoFactorManagementClient.d.ts.map +0 -1
- package/dist/components/management/TwoFactorManagementClient.js.map +0 -1
- package/dist/components/management/index.d.ts.map +0 -1
- package/dist/components/management/index.js.map +0 -1
- package/dist/components/management/views/ApiKeysView.d.ts.map +0 -1
- package/dist/components/management/views/ApiKeysView.js.map +0 -1
- package/dist/components/management/views/PasskeysView.d.ts.map +0 -1
- package/dist/components/management/views/PasskeysView.js.map +0 -1
- package/dist/components/management/views/TwoFactorView.d.ts.map +0 -1
- package/dist/components/management/views/TwoFactorView.js.map +0 -1
- package/dist/components/management/views/index.d.ts.map +0 -1
- package/dist/components/management/views/index.js.map +0 -1
- package/dist/components/twoFactor/TwoFactorSetupView.d.ts.map +0 -1
- package/dist/components/twoFactor/TwoFactorSetupView.js.map +0 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.d.ts.map +0 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.js.map +0 -1
- package/dist/components/twoFactor/index.d.ts.map +0 -1
- package/dist/components/twoFactor/index.js.map +0 -1
- package/dist/exports/client.d.ts.map +0 -1
- package/dist/exports/client.js.map +0 -1
- package/dist/exports/components.d.ts.map +0 -1
- package/dist/exports/components.js.map +0 -1
- package/dist/exports/management.d.ts.map +0 -1
- package/dist/exports/management.js.map +0 -1
- package/dist/exports/rsc.d.ts.map +0 -1
- package/dist/exports/rsc.js.map +0 -1
- package/dist/generated-types.d.ts.map +0 -1
- package/dist/generated-types.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plugin/index.d.ts.map +0 -1
- package/dist/plugin/index.js.map +0 -1
- package/dist/scripts/generate-types.d.ts.map +0 -1
- package/dist/scripts/generate-types.js.map +0 -1
- package/dist/types/apiKey.d.ts.map +0 -1
- package/dist/types/apiKey.js.map +0 -1
- package/dist/types/betterAuth.d.ts.map +0 -1
- package/dist/types/betterAuth.js.map +0 -1
- package/dist/utils/access.d.ts.map +0 -1
- package/dist/utils/access.js.map +0 -1
- package/dist/utils/apiKeyAccess.d.ts.map +0 -1
- package/dist/utils/apiKeyAccess.js.map +0 -1
- package/dist/utils/betterAuthDefaults.d.ts.map +0 -1
- package/dist/utils/betterAuthDefaults.js.map +0 -1
- package/dist/utils/detectAuthConfig.d.ts.map +0 -1
- package/dist/utils/detectAuthConfig.js.map +0 -1
- package/dist/utils/detectEnabledPlugins.d.ts.map +0 -1
- package/dist/utils/detectEnabledPlugins.js.map +0 -1
- package/dist/utils/firstUserAdmin.d.ts.map +0 -1
- package/dist/utils/firstUserAdmin.js.map +0 -1
- package/dist/utils/generateScopes.d.ts.map +0 -1
- package/dist/utils/generateScopes.js.map +0 -1
- package/dist/utils/session.d.ts.map +0 -1
- package/dist/utils/session.js.map +0 -1
- package/src/adapter/collections.ts +0 -621
- package/src/adapter/index.ts +0 -712
- package/src/components/BeforeLogin.tsx +0 -39
- package/src/components/LoginView.tsx +0 -1516
- package/src/components/LoginViewWrapper.tsx +0 -35
- package/src/components/LogoutButton.tsx +0 -58
- package/src/components/PasskeyRegisterButton.tsx +0 -105
- package/src/components/PasskeySignInButton.tsx +0 -96
- package/src/components/auth/ForgotPasswordView.tsx +0 -274
- package/src/components/auth/ResetPasswordView.tsx +0 -331
- package/src/components/auth/index.ts +0 -8
- package/src/components/management/ApiKeysManagementClient.tsx +0 -988
- package/src/components/management/PasskeysManagementClient.tsx +0 -409
- package/src/components/management/SecurityNavLinks.tsx +0 -117
- package/src/components/management/TwoFactorManagementClient.tsx +0 -560
- package/src/components/management/index.ts +0 -20
- package/src/components/management/views/ApiKeysView.tsx +0 -57
- package/src/components/management/views/PasskeysView.tsx +0 -42
- package/src/components/management/views/TwoFactorView.tsx +0 -42
- package/src/components/management/views/index.ts +0 -10
- package/src/components/twoFactor/TwoFactorSetupView.tsx +0 -515
- package/src/components/twoFactor/TwoFactorVerifyView.tsx +0 -238
- package/src/components/twoFactor/index.ts +0 -8
- package/src/exports/client.ts +0 -77
- package/src/exports/components.ts +0 -30
- package/src/exports/management.ts +0 -25
- package/src/exports/rsc.ts +0 -11
- package/src/generated-types.ts +0 -269
- package/src/index.ts +0 -135
- package/src/plugin/index.ts +0 -834
- package/src/scripts/generate-types.ts +0 -269
- package/src/types/apiKey.ts +0 -63
- package/src/types/betterAuth.ts +0 -253
- package/src/utils/access.ts +0 -410
- package/src/utils/apiKeyAccess.ts +0 -443
- package/src/utils/betterAuthDefaults.ts +0 -102
- package/src/utils/detectAuthConfig.ts +0 -47
- package/src/utils/detectEnabledPlugins.ts +0 -69
- package/src/utils/firstUserAdmin.ts +0 -164
- package/src/utils/generateScopes.ts +0 -150
- package/src/utils/session.ts +0 -91
package/src/adapter/index.ts
DELETED
|
@@ -1,712 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Payload CMS Adapter for Better Auth
|
|
3
|
-
*
|
|
4
|
-
* Uses Better Auth's createAdapterFactory for schema-aware transformations,
|
|
5
|
-
* eliminating hardcoded field mappings and supporting all Better Auth plugins.
|
|
6
|
-
*
|
|
7
|
-
* @packageDocumentation
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
createAdapterFactory,
|
|
12
|
-
type AdapterFactoryConfig,
|
|
13
|
-
type CustomAdapter,
|
|
14
|
-
} from 'better-auth/adapters'
|
|
15
|
-
import type { Adapter, BetterAuthOptions } from 'better-auth'
|
|
16
|
-
import type {
|
|
17
|
-
BasePayload,
|
|
18
|
-
Where as PayloadWhere,
|
|
19
|
-
CollectionSlug,
|
|
20
|
-
} from 'payload'
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export type PayloadAdapterConfig = {
|
|
24
|
-
/**
|
|
25
|
-
* The Payload instance or a function that returns it.
|
|
26
|
-
* Use a function for lazy initialization to avoid circular dependencies.
|
|
27
|
-
*/
|
|
28
|
-
payloadClient: BasePayload | (() => Promise<BasePayload>)
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Adapter configuration options
|
|
32
|
-
*/
|
|
33
|
-
adapterConfig?: {
|
|
34
|
-
/**
|
|
35
|
-
* Enable debug logging for troubleshooting
|
|
36
|
-
*/
|
|
37
|
-
enableDebugLogs?: boolean
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* ID type used by Payload.
|
|
41
|
-
* If not specified, auto-detects from Better Auth's generateId setting.
|
|
42
|
-
* - 'number' for SERIAL/auto-increment (Payload default)
|
|
43
|
-
* - 'text' for UUID
|
|
44
|
-
*/
|
|
45
|
-
idType?: 'number' | 'text'
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Additional fields to convert to numeric IDs beyond the *Id heuristic.
|
|
49
|
-
* Use when you have ID fields that don't follow the naming convention.
|
|
50
|
-
* @example ['customOrgRef', 'legacyIdentifier']
|
|
51
|
-
*/
|
|
52
|
-
idFieldsAllowlist?: string[]
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Fields to exclude from numeric ID conversion.
|
|
56
|
-
* Use when a field ends in 'Id' but isn't actually an ID reference.
|
|
57
|
-
* @example ['visitorId', 'correlationId']
|
|
58
|
-
*/
|
|
59
|
-
idFieldsBlocklist?: string[]
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Detect ID type from Better Auth options.
|
|
65
|
-
* Defaults to 'number' (SERIAL) since Payload uses SERIAL IDs by default.
|
|
66
|
-
*/
|
|
67
|
-
function detectIdType(options: BetterAuthOptions): 'number' | 'text' {
|
|
68
|
-
const generateId = options.advanced?.database?.generateId
|
|
69
|
-
// If explicitly set to something other than 'serial', use text (UUID)
|
|
70
|
-
if (generateId !== undefined && generateId !== 'serial') {
|
|
71
|
-
return 'text'
|
|
72
|
-
}
|
|
73
|
-
// Default to number (SERIAL) - Payload's default
|
|
74
|
-
return 'number'
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Creates a Better Auth adapter that uses Payload CMS as the database.
|
|
79
|
-
*
|
|
80
|
-
* Uses Better Auth's createAdapterFactory for proper schema-aware transformations,
|
|
81
|
-
* automatically supporting all Better Auth plugins without hardcoded field mappings.
|
|
82
|
-
*
|
|
83
|
-
* @example Basic usage
|
|
84
|
-
* ```ts
|
|
85
|
-
* import { payloadAdapter } from '@delmaredigital/payload-better-auth/adapter'
|
|
86
|
-
*
|
|
87
|
-
* const auth = betterAuth({
|
|
88
|
-
* database: payloadAdapter({
|
|
89
|
-
* payloadClient: payload,
|
|
90
|
-
* }),
|
|
91
|
-
* // For serial IDs (Payload default), configure Better Auth:
|
|
92
|
-
* advanced: {
|
|
93
|
-
* database: {
|
|
94
|
-
* generateId: 'serial',
|
|
95
|
-
* },
|
|
96
|
-
* },
|
|
97
|
-
* })
|
|
98
|
-
* ```
|
|
99
|
-
*
|
|
100
|
-
* @example Custom collection names
|
|
101
|
-
* ```ts
|
|
102
|
-
* const auth = betterAuth({
|
|
103
|
-
* database: payloadAdapter({ payloadClient: payload }),
|
|
104
|
-
* // Use BetterAuthOptions to customize collection names.
|
|
105
|
-
* // Provide SINGULAR names - they get pluralized automatically:
|
|
106
|
-
* user: { modelName: 'member' }, // → 'members' collection
|
|
107
|
-
* session: { modelName: 'auth_session' }, // → 'auth_sessions' collection
|
|
108
|
-
* })
|
|
109
|
-
* ```
|
|
110
|
-
*/
|
|
111
|
-
export function payloadAdapter({
|
|
112
|
-
payloadClient,
|
|
113
|
-
adapterConfig = {},
|
|
114
|
-
}: PayloadAdapterConfig): (options: BetterAuthOptions) => Adapter {
|
|
115
|
-
const { enableDebugLogs = false, idFieldsAllowlist = [], idFieldsBlocklist = [] } = adapterConfig
|
|
116
|
-
const idFieldsAllowlistSet = new Set(idFieldsAllowlist)
|
|
117
|
-
const idFieldsBlocklistSet = new Set(idFieldsBlocklist)
|
|
118
|
-
|
|
119
|
-
// Resolve payload client (supports lazy initialization)
|
|
120
|
-
async function resolvePayloadClient(): Promise<BasePayload> {
|
|
121
|
-
return typeof payloadClient === 'function'
|
|
122
|
-
? await payloadClient()
|
|
123
|
-
: payloadClient
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
function convertOperator(
|
|
128
|
-
operator: string,
|
|
129
|
-
value: unknown
|
|
130
|
-
): Record<string, unknown> {
|
|
131
|
-
switch (operator) {
|
|
132
|
-
case 'eq':
|
|
133
|
-
return { equals: value }
|
|
134
|
-
case 'ne':
|
|
135
|
-
return { not_equals: value }
|
|
136
|
-
case 'gt':
|
|
137
|
-
return { greater_than: value }
|
|
138
|
-
case 'gte':
|
|
139
|
-
return { greater_than_equal: value }
|
|
140
|
-
case 'lt':
|
|
141
|
-
return { less_than: value }
|
|
142
|
-
case 'lte':
|
|
143
|
-
return { less_than_equal: value }
|
|
144
|
-
case 'in':
|
|
145
|
-
return { in: value }
|
|
146
|
-
case 'contains':
|
|
147
|
-
return { contains: value }
|
|
148
|
-
case 'starts_with':
|
|
149
|
-
return { like: `${value}%` }
|
|
150
|
-
case 'ends_with':
|
|
151
|
-
return { like: `%${value}` }
|
|
152
|
-
default:
|
|
153
|
-
return { equals: value }
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Extract single ID from where clause for optimization
|
|
159
|
-
*/
|
|
160
|
-
function extractSingleId(
|
|
161
|
-
where: Array<{ field: string; value: unknown; operator: string }>
|
|
162
|
-
): string | number | null {
|
|
163
|
-
if (where.length !== 1) return null
|
|
164
|
-
const w = where[0]
|
|
165
|
-
if (w.field === 'id' && w.operator === 'eq') {
|
|
166
|
-
const value = w.value
|
|
167
|
-
if (typeof value === 'string' || typeof value === 'number') {
|
|
168
|
-
return value
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return null
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Return the adapter factory function
|
|
175
|
-
return (options: BetterAuthOptions): Adapter => {
|
|
176
|
-
// Determine ID type: explicit config > auto-detect
|
|
177
|
-
// Defaults to 'number' (SERIAL) since Payload uses SERIAL IDs by default
|
|
178
|
-
const idType = adapterConfig.idType ?? detectIdType(options)
|
|
179
|
-
const generateId = options.advanced?.database?.generateId
|
|
180
|
-
|
|
181
|
-
// Warn if using number IDs but Better Auth is explicitly configured to generate its own IDs
|
|
182
|
-
// This would cause Better Auth to generate UUIDs which won't work with SERIAL columns
|
|
183
|
-
// Don't warn if generateId is undefined - that's the expected default case
|
|
184
|
-
if (idType === 'number' && generateId !== undefined && generateId !== 'serial') {
|
|
185
|
-
console.warn(
|
|
186
|
-
'[payload-adapter] Warning: Using SERIAL (number) IDs but `generateId` is set to a non-serial value. ' +
|
|
187
|
-
'Either set `advanced: { database: { generateId: "serial" } }` to let Payload generate IDs, ' +
|
|
188
|
-
'or set `adapterConfig: { idType: "text" }` if using UUIDs.'
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Warn if modelName appears to be already plural (ends with 's')
|
|
193
|
-
// With usePlural: true, providing 'users' would become 'userss'
|
|
194
|
-
const coreModels = ['user', 'session', 'account', 'verification'] as const
|
|
195
|
-
for (const model of coreModels) {
|
|
196
|
-
const modelName = options[model]?.modelName
|
|
197
|
-
if (modelName && modelName.endsWith('s')) {
|
|
198
|
-
console.warn(
|
|
199
|
-
`[payload-adapter] Warning: modelName '${modelName}' for '${model}' appears to be plural. ` +
|
|
200
|
-
`Use singular form (e.g., '${modelName.slice(0, -1)}') - it gets pluralized automatically. ` +
|
|
201
|
-
`Using plural names will result in double-pluralization (e.g., '${modelName}s').`
|
|
202
|
-
)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Create adapter config for createAdapterFactory
|
|
207
|
-
const factoryConfig: AdapterFactoryConfig = {
|
|
208
|
-
adapterId: 'payload-adapter',
|
|
209
|
-
adapterName: 'Payload CMS Adapter',
|
|
210
|
-
// Payload collections are plural by default (users, sessions, etc.)
|
|
211
|
-
// Users can customize via BetterAuthOptions: user: { modelName: 'custom_users' }
|
|
212
|
-
usePlural: true,
|
|
213
|
-
// Let Payload generate IDs when using serial/auto-increment
|
|
214
|
-
disableIdGeneration: idType === 'number',
|
|
215
|
-
// Payload supports these features
|
|
216
|
-
supportsNumericIds: true,
|
|
217
|
-
supportsDates: true,
|
|
218
|
-
supportsBooleans: true,
|
|
219
|
-
supportsJSON: true,
|
|
220
|
-
supportsArrays: false,
|
|
221
|
-
// Payload doesn't expose transaction API at collection level
|
|
222
|
-
transaction: false,
|
|
223
|
-
// Enable debug logs if configured
|
|
224
|
-
debugLogs: enableDebugLogs,
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// We need to resolve the payload client before creating the adapter
|
|
228
|
-
// The factory pattern requires we return an adapter synchronously,
|
|
229
|
-
// so we'll resolve it lazily on first operation
|
|
230
|
-
let resolvedPayload: BasePayload | null = null
|
|
231
|
-
let resolvePromise: Promise<BasePayload> | null = null
|
|
232
|
-
|
|
233
|
-
const getPayload = async (): Promise<BasePayload> => {
|
|
234
|
-
if (resolvedPayload) return resolvedPayload
|
|
235
|
-
if (!resolvePromise) {
|
|
236
|
-
resolvePromise = resolvePayloadClient().then((p) => {
|
|
237
|
-
resolvedPayload = p
|
|
238
|
-
return p
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
return resolvePromise
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Helper to convert ID based on type
|
|
245
|
-
const convertId = (id: string | number): string | number => {
|
|
246
|
-
if (idType === 'number' && typeof id === 'string') {
|
|
247
|
-
const num = parseInt(id, 10)
|
|
248
|
-
return isNaN(num) ? id : num
|
|
249
|
-
}
|
|
250
|
-
if (idType === 'text' && typeof id === 'number') {
|
|
251
|
-
return String(id)
|
|
252
|
-
}
|
|
253
|
-
return id
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Create the adapter using createAdapterFactory
|
|
257
|
-
// The factory handles all schema-aware transformations for us
|
|
258
|
-
const adapterFactory = createAdapterFactory({
|
|
259
|
-
config: factoryConfig,
|
|
260
|
-
adapter: ({
|
|
261
|
-
schema,
|
|
262
|
-
getModelName,
|
|
263
|
-
getFieldName,
|
|
264
|
-
debugLog,
|
|
265
|
-
}) => {
|
|
266
|
-
// Log initialization
|
|
267
|
-
if (enableDebugLogs) {
|
|
268
|
-
debugLog('Adapter initialized', {
|
|
269
|
-
idType,
|
|
270
|
-
schema: Object.keys(schema),
|
|
271
|
-
})
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Get the schema for a model, handling plural/singular lookups.
|
|
276
|
-
* Better Auth queries with plural names when usePlural is true,
|
|
277
|
-
* but schema keys are singular.
|
|
278
|
-
*/
|
|
279
|
-
function getModelSchema(model: string) {
|
|
280
|
-
// First try direct lookup
|
|
281
|
-
if (schema[model]) return schema[model]
|
|
282
|
-
|
|
283
|
-
// Try singular form (strip trailing 's') for plural model names
|
|
284
|
-
const singular = model.endsWith('s') ? model.slice(0, -1) : model
|
|
285
|
-
if (schema[singular]) return schema[singular]
|
|
286
|
-
|
|
287
|
-
// Try without 'ies' → 'y' conversion (e.g., 'verifications' → 'verification')
|
|
288
|
-
// This handles edge cases but 'verifications' → 'verification' works with simple 's' strip
|
|
289
|
-
|
|
290
|
-
return undefined
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Transform a Better Auth field name to a Payload field name.
|
|
295
|
-
*
|
|
296
|
-
* For reference fields (those with `references` in schema), Payload collections
|
|
297
|
-
* use the field name without the `Id`/`_id` suffix (e.g., `userId` → `user`).
|
|
298
|
-
* This matches how betterAuthCollections() generates relationship fields.
|
|
299
|
-
*/
|
|
300
|
-
function getPayloadFieldName(model: string, field: string): string {
|
|
301
|
-
// First apply any custom field name mappings from BetterAuthOptions
|
|
302
|
-
const mappedField = getFieldName({ model, field })
|
|
303
|
-
|
|
304
|
-
// Check if this field is a reference field in the schema
|
|
305
|
-
const modelSchema = getModelSchema(model)
|
|
306
|
-
|
|
307
|
-
if (modelSchema?.fields?.[field]?.references) {
|
|
308
|
-
// Strip _id or Id suffix for reference fields
|
|
309
|
-
// This matches betterAuthCollections() which does: fieldName.replace(/(_id|Id)$/, '')
|
|
310
|
-
return mappedField.replace(/(_id|Id)$/, '')
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return mappedField
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Transform input data from Better Auth format to Payload format.
|
|
318
|
-
* Converts reference field names (e.g., `userId` → `user`) and
|
|
319
|
-
* converts reference field values to the correct ID type.
|
|
320
|
-
*/
|
|
321
|
-
function transformDataForPayload(
|
|
322
|
-
model: string,
|
|
323
|
-
data: Record<string, unknown>
|
|
324
|
-
): Record<string, unknown> {
|
|
325
|
-
const modelSchema = getModelSchema(model)
|
|
326
|
-
if (!modelSchema?.fields) return data
|
|
327
|
-
|
|
328
|
-
const transformed: Record<string, unknown> = {}
|
|
329
|
-
|
|
330
|
-
for (const [key, value] of Object.entries(data)) {
|
|
331
|
-
const payloadKey = getPayloadFieldName(model, key)
|
|
332
|
-
let transformedValue = value
|
|
333
|
-
|
|
334
|
-
// If this is a reference field, convert the ID to the correct type
|
|
335
|
-
const fieldDef = modelSchema.fields[key]
|
|
336
|
-
if (fieldDef?.references && value !== null && value !== undefined) {
|
|
337
|
-
// Convert reference ID to the correct type (number for SERIAL, string for UUID)
|
|
338
|
-
if (idType === 'number' && typeof value === 'string') {
|
|
339
|
-
const numValue = parseInt(value as string, 10)
|
|
340
|
-
if (!isNaN(numValue)) {
|
|
341
|
-
transformedValue = numValue
|
|
342
|
-
}
|
|
343
|
-
} else if (idType === 'text' && typeof value === 'number') {
|
|
344
|
-
transformedValue = String(value)
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
transformed[payloadKey] = transformedValue
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (enableDebugLogs) {
|
|
352
|
-
console.log('[payload-adapter] transformDataForPayload:', {
|
|
353
|
-
model,
|
|
354
|
-
inputKeys: Object.keys(data),
|
|
355
|
-
outputKeys: Object.keys(transformed),
|
|
356
|
-
transformedData: transformed,
|
|
357
|
-
})
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return transformed
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Transform output data from Payload format to Better Auth format.
|
|
365
|
-
* Converts reference field names back (e.g., `user` → `userId`).
|
|
366
|
-
*/
|
|
367
|
-
function transformDataFromPayload(
|
|
368
|
-
model: string,
|
|
369
|
-
data: Record<string, unknown>
|
|
370
|
-
): Record<string, unknown> {
|
|
371
|
-
const modelSchema = getModelSchema(model)
|
|
372
|
-
if (!modelSchema?.fields || !data) return data
|
|
373
|
-
|
|
374
|
-
const transformed: Record<string, unknown> = { ...data }
|
|
375
|
-
|
|
376
|
-
// For each field in the schema that has references,
|
|
377
|
-
// check if Payload returned the stripped name and map it back
|
|
378
|
-
for (const [fieldKey, fieldDef] of Object.entries(modelSchema.fields)) {
|
|
379
|
-
if ((fieldDef as { references?: unknown }).references) {
|
|
380
|
-
const payloadFieldName = fieldKey.replace(/(_id|Id)$/, '')
|
|
381
|
-
if (payloadFieldName in data && !(fieldKey in transformed)) {
|
|
382
|
-
transformed[fieldKey] = data[payloadFieldName]
|
|
383
|
-
// Keep both for compatibility - Better Auth expects userId
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Convert semantic ID fields to numbers when using serial IDs
|
|
389
|
-
// Heuristic: fields ending in 'Id' or '_id' containing numeric strings
|
|
390
|
-
// Modified by allowlist (add) and blocklist (exclude)
|
|
391
|
-
if (idType === 'number') {
|
|
392
|
-
for (const [key, value] of Object.entries(transformed)) {
|
|
393
|
-
// Skip if not a string or already processed as a reference
|
|
394
|
-
if (typeof value !== 'string') continue
|
|
395
|
-
|
|
396
|
-
// Check if field should be converted
|
|
397
|
-
const matchesHeuristic = /(?:Id|_id)$/.test(key)
|
|
398
|
-
const inAllowlist = idFieldsAllowlistSet.has(key)
|
|
399
|
-
const inBlocklist = idFieldsBlocklistSet.has(key)
|
|
400
|
-
|
|
401
|
-
if ((matchesHeuristic || inAllowlist) && !inBlocklist) {
|
|
402
|
-
// Only convert if it's a pure numeric string
|
|
403
|
-
if (/^\d+$/.test(value)) {
|
|
404
|
-
transformed[key] = parseInt(value, 10)
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return transformed
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Convert Better Auth where clause to Payload where clause.
|
|
415
|
-
* Handles field name transformations for reference fields.
|
|
416
|
-
*/
|
|
417
|
-
function convertWhereToPayload(
|
|
418
|
-
model: string,
|
|
419
|
-
where: Array<{
|
|
420
|
-
field: string
|
|
421
|
-
value: unknown
|
|
422
|
-
operator: string
|
|
423
|
-
connector?: string
|
|
424
|
-
}>
|
|
425
|
-
): PayloadWhere {
|
|
426
|
-
if (!where || where.length === 0) return {}
|
|
427
|
-
|
|
428
|
-
if (where.length === 1) {
|
|
429
|
-
const w = where[0]
|
|
430
|
-
return {
|
|
431
|
-
[getPayloadFieldName(model, w.field)]: convertOperator(w.operator, w.value),
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const andConditions = where.filter((w) => w.connector !== 'OR')
|
|
436
|
-
const orConditions = where.filter((w) => w.connector === 'OR')
|
|
437
|
-
|
|
438
|
-
const result: PayloadWhere = {}
|
|
439
|
-
|
|
440
|
-
if (andConditions.length > 0) {
|
|
441
|
-
result.and = andConditions.map((w) => ({
|
|
442
|
-
[getPayloadFieldName(model, w.field)]: convertOperator(w.operator, w.value),
|
|
443
|
-
}))
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (orConditions.length > 0) {
|
|
447
|
-
result.or = orConditions.map((w) => ({
|
|
448
|
-
[getPayloadFieldName(model, w.field)]: convertOperator(w.operator, w.value),
|
|
449
|
-
}))
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
return result
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Get Payload collection slug from model name
|
|
456
|
-
// Uses factory's getModelName which respects BetterAuthOptions.modelName config
|
|
457
|
-
const getCollection = (model: string): CollectionSlug => {
|
|
458
|
-
return getModelName(model) as CollectionSlug
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// The CustomAdapter interface uses generics (T) for return types.
|
|
462
|
-
// Payload returns concrete types (JsonObject & TypeWithID).
|
|
463
|
-
// We cast at the interface boundary - this is standard practice
|
|
464
|
-
// when implementing generic interfaces with concrete implementations.
|
|
465
|
-
// The official Better Auth adapters do the same (visible in compiled .mjs).
|
|
466
|
-
return {
|
|
467
|
-
create: async ({ model, data }) => {
|
|
468
|
-
const payload = await getPayload()
|
|
469
|
-
const collection = getCollection(model)
|
|
470
|
-
const payloadData = transformDataForPayload(model, data as Record<string, unknown>)
|
|
471
|
-
|
|
472
|
-
if (enableDebugLogs) {
|
|
473
|
-
debugLog('create', { collection, model, data, payloadData })
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
try {
|
|
477
|
-
const result = await payload.create({
|
|
478
|
-
collection,
|
|
479
|
-
data: payloadData,
|
|
480
|
-
depth: 0,
|
|
481
|
-
// Bypass access control - Better Auth handles its own auth
|
|
482
|
-
overrideAccess: true,
|
|
483
|
-
})
|
|
484
|
-
// Transform back and merge with input data for Better Auth
|
|
485
|
-
// Database result takes precedence (handles hooks that modify data like firstUserAdmin)
|
|
486
|
-
const transformed = transformDataFromPayload(model, result as Record<string, unknown>)
|
|
487
|
-
return { ...data, ...transformed } as typeof data
|
|
488
|
-
} catch (error) {
|
|
489
|
-
console.error('[payload-adapter] create failed:', {
|
|
490
|
-
collection,
|
|
491
|
-
model,
|
|
492
|
-
error: error instanceof Error ? error.message : error,
|
|
493
|
-
})
|
|
494
|
-
throw error
|
|
495
|
-
}
|
|
496
|
-
},
|
|
497
|
-
|
|
498
|
-
findOne: async ({ model, where, select, join }) => {
|
|
499
|
-
const payload = await getPayload()
|
|
500
|
-
const collection = getCollection(model)
|
|
501
|
-
|
|
502
|
-
if (enableDebugLogs) {
|
|
503
|
-
debugLog('findOne', { collection, model, where, join })
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
try {
|
|
507
|
-
// Optimize for single ID queries
|
|
508
|
-
const id = extractSingleId(where)
|
|
509
|
-
if (id !== null) {
|
|
510
|
-
try {
|
|
511
|
-
const result = await payload.findByID({
|
|
512
|
-
collection,
|
|
513
|
-
id: convertId(id),
|
|
514
|
-
depth: join ? 1 : 0,
|
|
515
|
-
overrideAccess: true,
|
|
516
|
-
})
|
|
517
|
-
return transformDataFromPayload(model, result as Record<string, unknown>)
|
|
518
|
-
} catch (error) {
|
|
519
|
-
if (
|
|
520
|
-
error instanceof Error &&
|
|
521
|
-
'status' in error &&
|
|
522
|
-
(error as Error & { status: number }).status === 404
|
|
523
|
-
) {
|
|
524
|
-
return null
|
|
525
|
-
}
|
|
526
|
-
throw error
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const payloadWhere = convertWhereToPayload(model, where)
|
|
531
|
-
const result = await payload.find({
|
|
532
|
-
collection,
|
|
533
|
-
where: payloadWhere,
|
|
534
|
-
limit: 1,
|
|
535
|
-
depth: join ? 1 : 0,
|
|
536
|
-
overrideAccess: true,
|
|
537
|
-
})
|
|
538
|
-
|
|
539
|
-
if (!result.docs[0]) return null
|
|
540
|
-
return transformDataFromPayload(model, result.docs[0] as Record<string, unknown>)
|
|
541
|
-
} catch (error) {
|
|
542
|
-
console.error('[payload-adapter] findOne failed:', {
|
|
543
|
-
model,
|
|
544
|
-
where,
|
|
545
|
-
error,
|
|
546
|
-
})
|
|
547
|
-
throw error
|
|
548
|
-
}
|
|
549
|
-
},
|
|
550
|
-
|
|
551
|
-
findMany: async ({ model, where, limit, offset, sortBy, join }) => {
|
|
552
|
-
const payload = await getPayload()
|
|
553
|
-
const collection = getCollection(model)
|
|
554
|
-
|
|
555
|
-
if (enableDebugLogs) {
|
|
556
|
-
debugLog('findMany', {
|
|
557
|
-
collection,
|
|
558
|
-
model,
|
|
559
|
-
where,
|
|
560
|
-
limit,
|
|
561
|
-
offset,
|
|
562
|
-
sortBy,
|
|
563
|
-
})
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const payloadWhere = where ? convertWhereToPayload(model, where) : {}
|
|
567
|
-
|
|
568
|
-
const result = await payload.find({
|
|
569
|
-
collection,
|
|
570
|
-
where: payloadWhere,
|
|
571
|
-
limit: limit ?? 100,
|
|
572
|
-
page: offset ? Math.floor(offset / (limit ?? 100)) + 1 : 1,
|
|
573
|
-
sort: sortBy
|
|
574
|
-
? `${sortBy.direction === 'desc' ? '-' : ''}${getPayloadFieldName(model, sortBy.field)}`
|
|
575
|
-
: undefined,
|
|
576
|
-
depth: join ? 1 : 0,
|
|
577
|
-
overrideAccess: true,
|
|
578
|
-
})
|
|
579
|
-
|
|
580
|
-
return result.docs.map((doc) =>
|
|
581
|
-
transformDataFromPayload(model, doc as Record<string, unknown>)
|
|
582
|
-
)
|
|
583
|
-
},
|
|
584
|
-
|
|
585
|
-
update: async ({ model, where, update: data }) => {
|
|
586
|
-
const payload = await getPayload()
|
|
587
|
-
const collection = getCollection(model)
|
|
588
|
-
const payloadData = transformDataForPayload(model, data as Record<string, unknown>)
|
|
589
|
-
|
|
590
|
-
if (enableDebugLogs) {
|
|
591
|
-
debugLog('update', { collection, model, where, data, payloadData })
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// Optimize for single ID queries
|
|
595
|
-
const id = extractSingleId(where)
|
|
596
|
-
if (id !== null) {
|
|
597
|
-
const result = await payload.update({
|
|
598
|
-
collection,
|
|
599
|
-
id: convertId(id),
|
|
600
|
-
data: payloadData,
|
|
601
|
-
depth: 0,
|
|
602
|
-
overrideAccess: true,
|
|
603
|
-
})
|
|
604
|
-
const transformed = transformDataFromPayload(model, result as Record<string, unknown>)
|
|
605
|
-
return { ...data, ...transformed } as typeof data
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
const payloadWhere = convertWhereToPayload(model, where)
|
|
609
|
-
const result = await payload.update({
|
|
610
|
-
collection,
|
|
611
|
-
where: payloadWhere,
|
|
612
|
-
data: payloadData,
|
|
613
|
-
depth: 0,
|
|
614
|
-
overrideAccess: true,
|
|
615
|
-
})
|
|
616
|
-
|
|
617
|
-
if (!result.docs[0]) return null
|
|
618
|
-
const transformed = transformDataFromPayload(model, result.docs[0] as Record<string, unknown>)
|
|
619
|
-
return { ...data, ...transformed } as typeof data
|
|
620
|
-
},
|
|
621
|
-
|
|
622
|
-
updateMany: async ({ model, where, update: data }) => {
|
|
623
|
-
const payload = await getPayload()
|
|
624
|
-
const collection = getCollection(model)
|
|
625
|
-
const payloadData = transformDataForPayload(model, data as Record<string, unknown>)
|
|
626
|
-
|
|
627
|
-
if (enableDebugLogs) {
|
|
628
|
-
debugLog('updateMany', { collection, model, where, data, payloadData })
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
const payloadWhere = convertWhereToPayload(model, where)
|
|
632
|
-
|
|
633
|
-
const result = await payload.update({
|
|
634
|
-
collection,
|
|
635
|
-
where: payloadWhere,
|
|
636
|
-
data: payloadData,
|
|
637
|
-
depth: 0,
|
|
638
|
-
overrideAccess: true,
|
|
639
|
-
})
|
|
640
|
-
|
|
641
|
-
return result.docs.length
|
|
642
|
-
},
|
|
643
|
-
|
|
644
|
-
delete: async ({ model, where }) => {
|
|
645
|
-
const payload = await getPayload()
|
|
646
|
-
const collection = getCollection(model)
|
|
647
|
-
|
|
648
|
-
if (enableDebugLogs) {
|
|
649
|
-
debugLog('delete', { collection, model, where })
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Optimize for single ID queries
|
|
653
|
-
const id = extractSingleId(where)
|
|
654
|
-
if (id !== null) {
|
|
655
|
-
await payload.delete({
|
|
656
|
-
collection,
|
|
657
|
-
id: convertId(id),
|
|
658
|
-
overrideAccess: true,
|
|
659
|
-
})
|
|
660
|
-
return
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
const payloadWhere = convertWhereToPayload(model, where)
|
|
664
|
-
await payload.delete({ collection, where: payloadWhere, overrideAccess: true })
|
|
665
|
-
},
|
|
666
|
-
|
|
667
|
-
deleteMany: async ({ model, where }) => {
|
|
668
|
-
const payload = await getPayload()
|
|
669
|
-
const collection = getCollection(model)
|
|
670
|
-
|
|
671
|
-
if (enableDebugLogs) {
|
|
672
|
-
debugLog('deleteMany', { collection, model, where })
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
const payloadWhere = convertWhereToPayload(model, where)
|
|
676
|
-
|
|
677
|
-
const result = await payload.delete({
|
|
678
|
-
collection,
|
|
679
|
-
where: payloadWhere,
|
|
680
|
-
overrideAccess: true,
|
|
681
|
-
})
|
|
682
|
-
|
|
683
|
-
return result.docs.length
|
|
684
|
-
},
|
|
685
|
-
|
|
686
|
-
count: async ({ model, where }) => {
|
|
687
|
-
const payload = await getPayload()
|
|
688
|
-
const collection = getCollection(model)
|
|
689
|
-
|
|
690
|
-
if (enableDebugLogs) {
|
|
691
|
-
debugLog('count', { collection, model, where })
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
const payloadWhere = where ? convertWhereToPayload(model, where) : {}
|
|
695
|
-
|
|
696
|
-
const result = await payload.count({
|
|
697
|
-
collection,
|
|
698
|
-
where: payloadWhere,
|
|
699
|
-
overrideAccess: true,
|
|
700
|
-
})
|
|
701
|
-
|
|
702
|
-
return result.totalDocs
|
|
703
|
-
},
|
|
704
|
-
} as CustomAdapter
|
|
705
|
-
},
|
|
706
|
-
})
|
|
707
|
-
|
|
708
|
-
return adapterFactory(options)
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
export type { Adapter, BetterAuthOptions }
|