@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.
Files changed (206) hide show
  1. package/dist/adapter/collections.d.ts +0 -1
  2. package/dist/adapter/collections.js +0 -2
  3. package/dist/adapter/index.d.ts +0 -1
  4. package/dist/adapter/index.js +0 -2
  5. package/dist/components/BeforeLogin.d.ts +0 -1
  6. package/dist/components/BeforeLogin.js +0 -2
  7. package/dist/components/LoginView.d.ts +0 -1
  8. package/dist/components/LoginView.js +0 -2
  9. package/dist/components/LoginViewWrapper.d.ts +0 -1
  10. package/dist/components/LoginViewWrapper.js +0 -2
  11. package/dist/components/LogoutButton.d.ts +0 -1
  12. package/dist/components/LogoutButton.js +0 -2
  13. package/dist/components/PasskeyRegisterButton.d.ts +0 -1
  14. package/dist/components/PasskeyRegisterButton.js +0 -2
  15. package/dist/components/PasskeySignInButton.d.ts +0 -1
  16. package/dist/components/PasskeySignInButton.js +0 -2
  17. package/dist/components/auth/ForgotPasswordView.d.ts +0 -1
  18. package/dist/components/auth/ForgotPasswordView.js +0 -2
  19. package/dist/components/auth/ResetPasswordView.d.ts +0 -1
  20. package/dist/components/auth/ResetPasswordView.js +0 -2
  21. package/dist/components/auth/index.d.ts +0 -1
  22. package/dist/components/auth/index.js +0 -2
  23. package/dist/components/management/ApiKeysManagementClient.d.ts +0 -1
  24. package/dist/components/management/ApiKeysManagementClient.js +0 -2
  25. package/dist/components/management/PasskeysManagementClient.d.ts +0 -1
  26. package/dist/components/management/PasskeysManagementClient.js +0 -2
  27. package/dist/components/management/SecurityNavLinks.d.ts +0 -1
  28. package/dist/components/management/SecurityNavLinks.js +0 -2
  29. package/dist/components/management/TwoFactorManagementClient.d.ts +0 -1
  30. package/dist/components/management/TwoFactorManagementClient.js +0 -2
  31. package/dist/components/management/index.d.ts +0 -1
  32. package/dist/components/management/index.js +0 -2
  33. package/dist/components/management/views/ApiKeysView.d.ts +0 -1
  34. package/dist/components/management/views/ApiKeysView.js +0 -2
  35. package/dist/components/management/views/PasskeysView.d.ts +0 -1
  36. package/dist/components/management/views/PasskeysView.js +0 -2
  37. package/dist/components/management/views/TwoFactorView.d.ts +0 -1
  38. package/dist/components/management/views/TwoFactorView.js +0 -2
  39. package/dist/components/management/views/index.d.ts +0 -1
  40. package/dist/components/management/views/index.js +0 -2
  41. package/dist/components/twoFactor/TwoFactorSetupView.d.ts +0 -1
  42. package/dist/components/twoFactor/TwoFactorSetupView.js +0 -2
  43. package/dist/components/twoFactor/TwoFactorVerifyView.d.ts +0 -1
  44. package/dist/components/twoFactor/TwoFactorVerifyView.js +0 -2
  45. package/dist/components/twoFactor/index.d.ts +0 -1
  46. package/dist/components/twoFactor/index.js +0 -2
  47. package/dist/exports/client.d.ts +0 -1
  48. package/dist/exports/client.js +0 -2
  49. package/dist/exports/components.d.ts +0 -1
  50. package/dist/exports/components.js +0 -2
  51. package/dist/exports/management.d.ts +0 -1
  52. package/dist/exports/management.js +0 -2
  53. package/dist/exports/rsc.d.ts +0 -1
  54. package/dist/exports/rsc.js +0 -2
  55. package/dist/generated-types.d.ts +0 -1
  56. package/dist/generated-types.js +0 -2
  57. package/dist/index.d.ts +0 -1
  58. package/dist/index.js +0 -2
  59. package/dist/plugin/index.d.ts +0 -1
  60. package/dist/plugin/index.js +0 -2
  61. package/dist/scripts/generate-types.d.ts +0 -1
  62. package/dist/scripts/generate-types.js +0 -2
  63. package/dist/types/apiKey.d.ts +0 -1
  64. package/dist/types/apiKey.js +0 -2
  65. package/dist/types/betterAuth.d.ts +0 -1
  66. package/dist/types/betterAuth.js +0 -2
  67. package/dist/utils/access.d.ts +0 -1
  68. package/dist/utils/access.js +0 -2
  69. package/dist/utils/apiKeyAccess.d.ts +0 -1
  70. package/dist/utils/apiKeyAccess.js +0 -2
  71. package/dist/utils/betterAuthDefaults.d.ts +0 -1
  72. package/dist/utils/betterAuthDefaults.js +0 -2
  73. package/dist/utils/detectAuthConfig.d.ts +0 -1
  74. package/dist/utils/detectAuthConfig.js +0 -2
  75. package/dist/utils/detectEnabledPlugins.d.ts +0 -1
  76. package/dist/utils/detectEnabledPlugins.js +0 -2
  77. package/dist/utils/firstUserAdmin.d.ts +0 -1
  78. package/dist/utils/firstUserAdmin.js +0 -2
  79. package/dist/utils/generateScopes.d.ts +0 -1
  80. package/dist/utils/generateScopes.js +0 -2
  81. package/dist/utils/session.d.ts +0 -1
  82. package/dist/utils/session.js +0 -2
  83. package/package.json +34 -91
  84. package/dist/adapter/collections.d.ts.map +0 -1
  85. package/dist/adapter/collections.js.map +0 -1
  86. package/dist/adapter/index.d.ts.map +0 -1
  87. package/dist/adapter/index.js.map +0 -1
  88. package/dist/components/BeforeLogin.d.ts.map +0 -1
  89. package/dist/components/BeforeLogin.js.map +0 -1
  90. package/dist/components/LoginView.d.ts.map +0 -1
  91. package/dist/components/LoginView.js.map +0 -1
  92. package/dist/components/LoginViewWrapper.d.ts.map +0 -1
  93. package/dist/components/LoginViewWrapper.js.map +0 -1
  94. package/dist/components/LogoutButton.d.ts.map +0 -1
  95. package/dist/components/LogoutButton.js.map +0 -1
  96. package/dist/components/PasskeyRegisterButton.d.ts.map +0 -1
  97. package/dist/components/PasskeyRegisterButton.js.map +0 -1
  98. package/dist/components/PasskeySignInButton.d.ts.map +0 -1
  99. package/dist/components/PasskeySignInButton.js.map +0 -1
  100. package/dist/components/auth/ForgotPasswordView.d.ts.map +0 -1
  101. package/dist/components/auth/ForgotPasswordView.js.map +0 -1
  102. package/dist/components/auth/ResetPasswordView.d.ts.map +0 -1
  103. package/dist/components/auth/ResetPasswordView.js.map +0 -1
  104. package/dist/components/auth/index.d.ts.map +0 -1
  105. package/dist/components/auth/index.js.map +0 -1
  106. package/dist/components/management/ApiKeysManagementClient.d.ts.map +0 -1
  107. package/dist/components/management/ApiKeysManagementClient.js.map +0 -1
  108. package/dist/components/management/PasskeysManagementClient.d.ts.map +0 -1
  109. package/dist/components/management/PasskeysManagementClient.js.map +0 -1
  110. package/dist/components/management/SecurityNavLinks.d.ts.map +0 -1
  111. package/dist/components/management/SecurityNavLinks.js.map +0 -1
  112. package/dist/components/management/TwoFactorManagementClient.d.ts.map +0 -1
  113. package/dist/components/management/TwoFactorManagementClient.js.map +0 -1
  114. package/dist/components/management/index.d.ts.map +0 -1
  115. package/dist/components/management/index.js.map +0 -1
  116. package/dist/components/management/views/ApiKeysView.d.ts.map +0 -1
  117. package/dist/components/management/views/ApiKeysView.js.map +0 -1
  118. package/dist/components/management/views/PasskeysView.d.ts.map +0 -1
  119. package/dist/components/management/views/PasskeysView.js.map +0 -1
  120. package/dist/components/management/views/TwoFactorView.d.ts.map +0 -1
  121. package/dist/components/management/views/TwoFactorView.js.map +0 -1
  122. package/dist/components/management/views/index.d.ts.map +0 -1
  123. package/dist/components/management/views/index.js.map +0 -1
  124. package/dist/components/twoFactor/TwoFactorSetupView.d.ts.map +0 -1
  125. package/dist/components/twoFactor/TwoFactorSetupView.js.map +0 -1
  126. package/dist/components/twoFactor/TwoFactorVerifyView.d.ts.map +0 -1
  127. package/dist/components/twoFactor/TwoFactorVerifyView.js.map +0 -1
  128. package/dist/components/twoFactor/index.d.ts.map +0 -1
  129. package/dist/components/twoFactor/index.js.map +0 -1
  130. package/dist/exports/client.d.ts.map +0 -1
  131. package/dist/exports/client.js.map +0 -1
  132. package/dist/exports/components.d.ts.map +0 -1
  133. package/dist/exports/components.js.map +0 -1
  134. package/dist/exports/management.d.ts.map +0 -1
  135. package/dist/exports/management.js.map +0 -1
  136. package/dist/exports/rsc.d.ts.map +0 -1
  137. package/dist/exports/rsc.js.map +0 -1
  138. package/dist/generated-types.d.ts.map +0 -1
  139. package/dist/generated-types.js.map +0 -1
  140. package/dist/index.d.ts.map +0 -1
  141. package/dist/index.js.map +0 -1
  142. package/dist/plugin/index.d.ts.map +0 -1
  143. package/dist/plugin/index.js.map +0 -1
  144. package/dist/scripts/generate-types.d.ts.map +0 -1
  145. package/dist/scripts/generate-types.js.map +0 -1
  146. package/dist/types/apiKey.d.ts.map +0 -1
  147. package/dist/types/apiKey.js.map +0 -1
  148. package/dist/types/betterAuth.d.ts.map +0 -1
  149. package/dist/types/betterAuth.js.map +0 -1
  150. package/dist/utils/access.d.ts.map +0 -1
  151. package/dist/utils/access.js.map +0 -1
  152. package/dist/utils/apiKeyAccess.d.ts.map +0 -1
  153. package/dist/utils/apiKeyAccess.js.map +0 -1
  154. package/dist/utils/betterAuthDefaults.d.ts.map +0 -1
  155. package/dist/utils/betterAuthDefaults.js.map +0 -1
  156. package/dist/utils/detectAuthConfig.d.ts.map +0 -1
  157. package/dist/utils/detectAuthConfig.js.map +0 -1
  158. package/dist/utils/detectEnabledPlugins.d.ts.map +0 -1
  159. package/dist/utils/detectEnabledPlugins.js.map +0 -1
  160. package/dist/utils/firstUserAdmin.d.ts.map +0 -1
  161. package/dist/utils/firstUserAdmin.js.map +0 -1
  162. package/dist/utils/generateScopes.d.ts.map +0 -1
  163. package/dist/utils/generateScopes.js.map +0 -1
  164. package/dist/utils/session.d.ts.map +0 -1
  165. package/dist/utils/session.js.map +0 -1
  166. package/src/adapter/collections.ts +0 -621
  167. package/src/adapter/index.ts +0 -712
  168. package/src/components/BeforeLogin.tsx +0 -39
  169. package/src/components/LoginView.tsx +0 -1516
  170. package/src/components/LoginViewWrapper.tsx +0 -35
  171. package/src/components/LogoutButton.tsx +0 -58
  172. package/src/components/PasskeyRegisterButton.tsx +0 -105
  173. package/src/components/PasskeySignInButton.tsx +0 -96
  174. package/src/components/auth/ForgotPasswordView.tsx +0 -274
  175. package/src/components/auth/ResetPasswordView.tsx +0 -331
  176. package/src/components/auth/index.ts +0 -8
  177. package/src/components/management/ApiKeysManagementClient.tsx +0 -988
  178. package/src/components/management/PasskeysManagementClient.tsx +0 -409
  179. package/src/components/management/SecurityNavLinks.tsx +0 -117
  180. package/src/components/management/TwoFactorManagementClient.tsx +0 -560
  181. package/src/components/management/index.ts +0 -20
  182. package/src/components/management/views/ApiKeysView.tsx +0 -57
  183. package/src/components/management/views/PasskeysView.tsx +0 -42
  184. package/src/components/management/views/TwoFactorView.tsx +0 -42
  185. package/src/components/management/views/index.ts +0 -10
  186. package/src/components/twoFactor/TwoFactorSetupView.tsx +0 -515
  187. package/src/components/twoFactor/TwoFactorVerifyView.tsx +0 -238
  188. package/src/components/twoFactor/index.ts +0 -8
  189. package/src/exports/client.ts +0 -77
  190. package/src/exports/components.ts +0 -30
  191. package/src/exports/management.ts +0 -25
  192. package/src/exports/rsc.ts +0 -11
  193. package/src/generated-types.ts +0 -269
  194. package/src/index.ts +0 -135
  195. package/src/plugin/index.ts +0 -834
  196. package/src/scripts/generate-types.ts +0 -269
  197. package/src/types/apiKey.ts +0 -63
  198. package/src/types/betterAuth.ts +0 -253
  199. package/src/utils/access.ts +0 -410
  200. package/src/utils/apiKeyAccess.ts +0 -443
  201. package/src/utils/betterAuthDefaults.ts +0 -102
  202. package/src/utils/detectAuthConfig.ts +0 -47
  203. package/src/utils/detectEnabledPlugins.ts +0 -69
  204. package/src/utils/firstUserAdmin.ts +0 -164
  205. package/src/utils/generateScopes.ts +0 -150
  206. package/src/utils/session.ts +0 -91
@@ -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 }