payload-better-auth 1.0.10 → 1.1.6

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 (47) hide show
  1. package/README.md +111 -174
  2. package/dist/better-auth/databaseHooks.js +1 -1
  3. package/dist/better-auth/databaseHooks.js.map +1 -1
  4. package/dist/better-auth/plugin.d.ts +1 -1
  5. package/dist/better-auth/plugin.js +3 -3
  6. package/dist/better-auth/plugin.js.map +1 -1
  7. package/dist/better-auth/reconcile-queue.d.ts +1 -1
  8. package/dist/better-auth/reconcile-queue.js.map +1 -1
  9. package/dist/better-auth/sources.js +1 -1
  10. package/dist/better-auth/sources.js.map +1 -1
  11. package/dist/collections/Users/index.js +1 -1
  12. package/dist/collections/Users/index.js.map +1 -1
  13. package/dist/components/BetterAuthLoginServer.d.ts +19 -6
  14. package/dist/components/BetterAuthLoginServer.js +24 -8
  15. package/dist/components/BetterAuthLoginServer.js.map +1 -1
  16. package/dist/components/EmailPasswordFormClient.d.ts +1 -1
  17. package/dist/components/EmailPasswordFormClient.js.map +1 -1
  18. package/dist/exports/client.d.ts +1 -1
  19. package/dist/exports/client.js +1 -1
  20. package/dist/exports/client.js.map +1 -1
  21. package/dist/exports/rsc.d.ts +1 -1
  22. package/dist/exports/rsc.js +1 -1
  23. package/dist/exports/rsc.js.map +1 -1
  24. package/dist/index.d.ts +5 -4
  25. package/dist/index.js +5 -4
  26. package/dist/index.js.map +1 -1
  27. package/dist/payload/plugin.d.ts +21 -2
  28. package/dist/payload/plugin.js +29 -7
  29. package/dist/payload/plugin.js.map +1 -1
  30. package/dist/utils/payload-reconcile.js +2 -6
  31. package/dist/utils/payload-reconcile.js.map +1 -1
  32. package/package.json +137 -63
  33. package/src/better-auth/crypto-shared.ts +169 -0
  34. package/src/better-auth/databaseHooks.ts +30 -0
  35. package/src/better-auth/helpers.ts +3 -0
  36. package/src/better-auth/plugin.ts +214 -0
  37. package/src/better-auth/reconcile-queue.ts +401 -0
  38. package/src/better-auth/sources.ts +123 -0
  39. package/src/collections/Users/index.ts +148 -0
  40. package/src/components/BetterAuthLoginServer.tsx +154 -0
  41. package/src/components/EmailPasswordFormClient.tsx +204 -0
  42. package/src/components/VerifyEmailInfoViewClient.tsx +62 -0
  43. package/src/exports/client.ts +1 -0
  44. package/src/exports/rsc.ts +1 -0
  45. package/src/index.ts +9 -0
  46. package/src/payload/plugin.ts +163 -0
  47. package/src/utils/payload-reconcile.ts +50 -0
package/README.md CHANGED
@@ -1,225 +1,162 @@
1
- # Payload Plugin Template
1
+ # payload-better-auth
2
2
 
3
- A template repo to create a [Payload CMS](https://payloadcms.com) plugin.
3
+ A Payload CMS plugin that integrates [Better Auth](https://better-auth.com) for seamless user authentication and management.
4
4
 
5
- Payload is built with a robust infrastructure intended to support Plugins with ease. This provides a simple, modular, and reusable way for developers to extend the core capabilities of Payload.
5
+ ## Features
6
6
 
7
- To build your own Payload plugin, all you need is:
7
+ - **Better Auth as Single Source of Truth** All user operations managed through Better Auth
8
+ - **Real-time Sync** — Automatic synchronization via database hooks
9
+ - **Background Reconciliation** — Periodic full sync ensures data consistency
10
+ - **Cryptographic Security** — Signed operations prevent unauthorized modifications
11
+ - **Custom Login UI** — Replaces Payload's default login with Better Auth authentication
8
12
 
9
- - An understanding of the basic Payload concepts
10
- - And some JavaScript/Typescript experience
13
+ ## Installation
11
14
 
12
- ## Getting started
13
-
14
- 1. Install: `pnpm i`
15
- 2. Generate import map for payload: `cd packages/plugins/dev && pnpx payload generate:importmap`
16
- 3. Initialize / migrate the better-auth database: `cd ../ && pnpx @better-auth/cli migrate --yes --config ./dev/lib/auth.ts` (Run from `dev` directory)
17
- 4. Run dev: `pnpm dev`
18
-
19
- ## Background
15
+ ```bash
16
+ pnpm add payload-better-auth better-auth
17
+ ```
20
18
 
21
- Here is a short recap on how to integrate plugins with Payload, to learn more visit the [plugin overview page](https://payloadcms.com/docs/plugins/overview).
19
+ **Requirements:** Node.js 18.20.2+, Better Auth 1.4.10+, Payload CMS 3.37.0+
22
20
 
23
- ### How to install a plugin
21
+ ## Quick Start
24
22
 
25
- To install any plugin, simply add it to your payload.config() in the Plugin array.
23
+ ### 1. Configure Better Auth
26
24
 
27
- ```ts
28
- import myPlugin from 'my-plugin'
25
+ ```typescript
26
+ // lib/auth.ts
27
+ import { betterAuth } from 'better-auth'
28
+ import { admin, apiKey } from 'better-auth/plugins'
29
+ import Database from 'better-sqlite3'
30
+ import { payloadBetterAuthPlugin } from 'payload-better-auth'
31
+ import buildConfig from './payload.config.js'
29
32
 
30
- export const config = buildConfig({
33
+ export const auth = betterAuth({
34
+ database: new Database(process.env.BETTER_AUTH_DB_PATH || './better-auth.db'),
35
+ secret: process.env.BETTER_AUTH_SECRET,
36
+ emailAndPassword: { enabled: true },
31
37
  plugins: [
32
- // You can pass options to the plugin
33
- myPlugin({
34
- enabled: true,
38
+ admin(),
39
+ apiKey(),
40
+ payloadBetterAuthPlugin({
41
+ payloadConfig: buildConfig,
42
+ token: process.env.RECONCILE_TOKEN,
35
43
  }),
36
44
  ],
37
45
  })
38
46
  ```
39
47
 
40
- ### Initialization
41
-
42
- The initialization process goes in the following order:
43
-
44
- 1. Incoming config is validated
45
- 2. **Plugins execute**
46
- 3. Default options are integrated
47
- 4. Sanitization cleans and validates data
48
- 5. Final config gets initialized
49
-
50
- ## Building the Plugin
51
-
52
- When you build a plugin, you are purely building a feature for your project and then abstracting it outside of the project.
53
-
54
- ### Template Files
55
-
56
- In the Payload [plugin template](https://github.com/payloadcms/payload/tree/main/templates/plugin), you will see a common file structure that is used across all plugins:
48
+ ### 2. Configure Payload
57
49
 
58
- 1. root folder
59
- 2. /src folder
60
- 3. /dev folder
50
+ ```typescript
51
+ // payload.config.ts
52
+ import { buildConfig } from 'payload'
53
+ import { betterAuthPlugin } from 'payload-better-auth'
54
+ import { auth } from './lib/auth.js'
61
55
 
62
- #### Root
63
-
64
- In the root folder, you will see various files that relate to the configuration of the plugin. We set up our environment in a similar manner in Payload core and across other projects, so hopefully these will look familiar:
65
-
66
- - **README**.md\* - This contains instructions on how to use the template. When you are ready, update this to contain instructions on how to use your Plugin.
67
- - **package**.json\* - Contains necessary scripts and dependencies. Overwrite the metadata in this file to describe your Plugin.
68
- - .**eslint**.config.js - Eslint configuration for reporting on problematic patterns.
69
- - .**gitignore** - List specific untracked files to omit from Git.
70
- - .**prettierrc**.json - Configuration for Prettier code formatting.
71
- - **tsconfig**.json - Configures the compiler options for TypeScript
72
- - .**swcrc** - Configuration for SWC, a fast compiler that transpiles and bundles TypeScript.
73
- - **vitest**.config.js - Config file for Vitest, defining how tests are run and how modules are resolved
74
-
75
- **IMPORTANT\***: You will need to modify these files.
76
-
77
- #### Dev
78
-
79
- In the dev folder, you’ll find a basic payload project, created with `npx create-payload-app` and the blank template.
80
-
81
- **IMPORTANT**: Make a copy of the `.env.example` file and rename it to `.env`. Update the `DATABASE_URI` to match the database you are using and your plugin name. Update `PAYLOAD_SECRET` to a unique string.
82
- **You will not be able to run `pnpm/yarn dev` until you have created this `.env` file.**
83
-
84
- `myPlugin` has already been added to the `payload.config()` file in this project.
85
-
86
- ```ts
87
- plugins: [
88
- myPlugin({
89
- collections: {
90
- posts: true,
91
- },
92
- }),
93
- ]
56
+ export default buildConfig({
57
+ plugins: [betterAuthPlugin({ betterAuth: auth })],
58
+ // ... rest of your config
59
+ })
94
60
  ```
95
61
 
96
- Later when you rename the plugin or add additional options, **make sure to update it here**.
62
+ ### 3. Set Environment Variables
97
63
 
98
- You may wish to add collections or expand the test project depending on the purpose of your plugin. Just make sure to keep this dev environment as simplified as possible - users should be able to install your plugin without additional configuration required.
64
+ ```bash
65
+ BETTER_AUTH_SECRET=your-secret-min-32-chars
66
+ BETTER_AUTH_DB_PATH=./better-auth.db
67
+ BA_TO_PAYLOAD_SECRET=your-sync-secret
68
+ RECONCILE_TOKEN=your-api-token
69
+ PAYLOAD_SECRET=your-payload-secret
70
+ DATABASE_URI=file:./payload.db
71
+ ```
99
72
 
100
- When you’re ready to start development, initiate the project with `pnpm/npm/yarn dev` and pull up [http://localhost:3000](http://localhost:3000) in your browser.
73
+ ## Documentation
101
74
 
102
- #### Src
75
+ For detailed configuration options, API endpoints, architecture details, and production considerations, see the **[MANUAL.md](./MANUAL.md)**.
103
76
 
104
- Now that we have our environment setup and we have a dev project ready to - it’s time to build the plugin!
77
+ ## Development
105
78
 
106
- **index.ts**
79
+ ### Getting Started
107
80
 
108
- The essence of a Payload plugin is simply to extend the payload config - and that is exactly what we are doing in this file.
81
+ ```bash
82
+ # Install dependencies
83
+ pnpm install
109
84
 
110
- ```ts
111
- export const myPlugin =
112
- (pluginOptions: MyPluginConfig) =>
113
- (config: Config): Config => {
114
- // do cool stuff with the config here
85
+ # Reset databases and run migrations
86
+ pnpm reset
115
87
 
116
- return config
117
- }
88
+ # Start development server
89
+ pnpm dev
118
90
  ```
119
91
 
120
- First, we receive the existing payload config along with any plugin options.
121
-
122
- From here, you can extend the config as you wish.
92
+ The dev server starts at [http://localhost:3000](http://localhost:3000) with a mail server at port 1080.
123
93
 
124
- Finally, you return the config and that is it!
94
+ ### Git Hooks
125
95
 
126
- ##### Spread Syntax
96
+ This project uses [Husky](https://typicode.github.io/husky/) for Git hooks:
127
97
 
128
- Spread syntax (or the spread operator) is a feature in JavaScript that uses the dot notation **(...)** to spread elements from arrays, strings, or objects into various contexts.
98
+ - **pre-commit**: Builds the plugin and stages `dist/`, blocks manual version changes
99
+ - **pre-push**: Runs lint, typecheck, and tests before pushing
100
+ - **commit-msg**: Validates commit messages follow [Conventional Commits](https://www.conventionalcommits.org/)
129
101
 
130
- We are going to use spread syntax to allow us to add data to existing arrays without losing the existing data. It is crucial to spread the existing data correctly – else this can cause adverse behavior and conflicts with Payload config and other plugins.
102
+ When you're happy with your changes, just commit the build is handled for you!
131
103
 
132
- Let’s say you want to build a plugin that adds a new collection:
104
+ ### Versioning & Releases
133
105
 
134
- ```ts
135
- config.collections = [
136
- ...(config.collections || []),
137
- // Add additional collections here
138
- ]
139
- ```
106
+ This project uses [semantic-release](https://semantic-release.gitbook.io/) for automated versioning. **Do not manually edit the `version` field in `package.json`** — it will be rejected by the pre-commit hook.
140
107
 
141
- First we spread the `config.collections` to ensure that we don’t lose the existing collections, then you can add any additional collections just as you would in a regular payload config.
108
+ Versions are determined automatically from your commit messages:
142
109
 
143
- This same logic is applied to other properties like admin, hooks, globals:
110
+ | Commit Type | Version Bump | Example |
111
+ |-------------|--------------|---------|
112
+ | `fix:` | Patch (1.0.0 → 1.0.1) | `fix: resolve login redirect bug` |
113
+ | `feat:` | Minor (1.0.0 → 1.1.0) | `feat: add OAuth provider support` |
114
+ | `feat!:` or `BREAKING CHANGE:` | Major (1.0.0 → 2.0.0) | `feat!: redesign auth API` |
144
115
 
145
- ```ts
146
- config.globals = [
147
- ...(config.globals || []),
148
- // Add additional globals here
149
- ]
150
-
151
- config.hooks = {
152
- ...(incomingConfig.hooks || {}),
153
- // Add additional hooks here
154
- }
155
- ```
116
+ When you push to `main`, the CI will automatically:
117
+ 1. Analyze commits since the last release
118
+ 2. Determine the next version
119
+ 3. Update `package.json` and `CHANGELOG.md`
120
+ 4. Create a Git tag and GitHub Release
156
121
 
157
- Some properties will be slightly different to extend, for instance the onInit property:
122
+ #### Installing Specific Versions
158
123
 
159
- ```ts
160
- import { onInitExtension } from './onInitExtension' // example file
124
+ ```bash
125
+ # Latest
126
+ pnpm add github:benjaminpreiss/payload-better-auth
161
127
 
162
- config.onInit = async (payload) => {
163
- if (incomingConfig.onInit) await incomingConfig.onInit(payload)
164
- // Add additional onInit code by defining an onInitExtension function
165
- onInitExtension(pluginOptions, payload)
166
- }
128
+ # Specific version
129
+ pnpm add github:benjaminpreiss/payload-better-auth#v1.2.0
167
130
  ```
168
131
 
169
- If you wish to add to the onInit, you must include the **async/await**. We don’t use spread syntax in this case, instead you must await the existing `onInit` before running additional functionality.
132
+ ### Available Scripts
170
133
 
171
- In the template, we have stubbed out some addition `onInit` actions that seeds in a document to the `plugin-collection`, you can use this as a base point to add more actions - and if not needed, feel free to delete it.
134
+ | Script | Description |
135
+ |--------|-------------|
136
+ | `pnpm dev` | Start dev server with mail server |
137
+ | `pnpm build` | Build the plugin |
138
+ | `pnpm reset` | Reset databases and run all migrations |
139
+ | `pnpm test` | Run all tests |
140
+ | `pnpm lint` | Run ESLint |
141
+ | `pnpm typecheck` | Run TypeScript type checking |
142
+ | `pnpm generate:types` | Generate Payload types |
172
143
 
173
- ##### Types.ts
144
+ ### Project Structure
174
145
 
175
- If your plugin has options, you should define and provide types for these options.
176
-
177
- ```ts
178
- export type MyPluginConfig = {
179
- /**
180
- * List of collections to add a custom field
181
- */
182
- collections?: Partial<Record<CollectionSlug, true>>
183
- /**
184
- * Disable the plugin
185
- */
186
- disabled?: boolean
187
- }
188
146
  ```
189
-
190
- If possible, include JSDoc comments to describe the options and their types. This allows a developer to see details about the options in their editor.
191
-
192
- ##### Testing
193
-
194
- Having a test suite for your plugin is essential to ensure quality and stability. **Vitest** is a fast, modern testing framework that works seamlessly with Vite and supports TypeScript out of the box.
195
-
196
- Vitest organizes tests into test suites and cases, similar to other testing frameworks. We recommend creating individual tests based on the expected behavior of your plugin from start to finish.
197
-
198
- Writing tests with Vitest is very straightforward, and you can learn more about how it works in the [Vitest documentation.](https://vitest.dev/)
199
-
200
- For this template, we stubbed out `int.spec.ts` in the `dev` folder where you can write your tests.
201
-
202
- ```ts
203
- describe('Plugin tests', () => {
204
- // Create tests to ensure expected behavior from the plugin
205
- it('some condition that must be met', () => {
206
- // Write your test logic here
207
- expect(...)
208
- })
209
- })
147
+ ├── src/ # Plugin source code
148
+ │ ├── better-auth/ # Better Auth integration
149
+ │ ├── collections/ # Payload collections
150
+ │ ├── components/ # React components
151
+ │ ├── payload/ # Payload plugin
152
+ │ └── exports/ # Client/RSC exports
153
+ ├── dev/ # Development environment
154
+ │ ├── app/ # Next.js app
155
+ │ ├── lib/ # Dev configuration
156
+ │ └── tests/ # Test files
157
+ └── dist/ # Built output
210
158
  ```
211
159
 
212
- ## Best practices
213
-
214
- With this tutorial and the plugin template, you should have everything you need to start building your own plugin.
215
- In addition to the setup, here are other best practices aim we follow:
216
-
217
- - **Providing an enable / disable option:** For a better user experience, provide a way to disable the plugin without uninstalling it. This is especially important if your plugin adds additional webpack aliases, this will allow you to still let the webpack run to prevent errors.
218
- - **Include tests in your GitHub CI workflow**: If you’ve configured tests for your package, integrate them into your workflow to run the tests each time you commit to the plugin repository. Learn more about [how to configure tests into your GitHub CI workflow.](https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs)
219
- - **Publish your finished plugin to NPM**: The best way to share and allow others to use your plugin once it is complete is to publish an NPM package. This process is straightforward and well documented, find out more [creating and publishing a NPM package here.](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/).
220
- - **Add payload-plugin topic tag**: Apply the tag **payload-plugin **to your GitHub repository. This will boost the visibility of your plugin and ensure it gets listed with [existing payload plugins](https://github.com/topics/payload-plugin).
221
- - **Use [Semantic Versioning](https://semver.org/) (SemVar)** - With the SemVar system you release version numbers that reflect the nature of changes (major, minor, patch). Ensure all major versions reference their Payload compatibility.
222
-
223
- # Questions
160
+ ## License
224
161
 
225
- Please contact [Payload](mailto:dev@payloadcms.com) with any questions about using this plugin template.
162
+ MIT
@@ -1,4 +1,4 @@
1
- import { createDeleteUserFromPayload, createSyncUserToPayload } from './sources.js';
1
+ import { createDeleteUserFromPayload, createSyncUserToPayload } from './sources';
2
2
  export function createDatabaseHooks({ config }) {
3
3
  const syncUserToPayload = createSyncUserToPayload(config);
4
4
  const deleteUserFromPayload = createDeleteUserFromPayload(config);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/better-auth/databaseHooks.ts"],"sourcesContent":["import type { betterAuth } from 'better-auth'\nimport type { SanitizedConfig } from 'payload'\n\nimport { createDeleteUserFromPayload, createSyncUserToPayload } from './sources.js'\n\nexport function createDatabaseHooks({\n config,\n}: {\n config: Promise<SanitizedConfig>\n}): Parameters<typeof betterAuth>['0']['databaseHooks'] {\n const syncUserToPayload = createSyncUserToPayload(config)\n const deleteUserFromPayload = createDeleteUserFromPayload(config)\n return {\n user: {\n create: {\n // After the BA user exists, sync to Payload. On failure, enqueue in memory.\n after: async (user) => {\n // push BA-induced ensure to the **front** of the queue\n await syncUserToPayload(user)\n },\n },\n // TODO: possibly offer \"update\"\n delete: {\n after: async (user) => {\n await deleteUserFromPayload(user.id)\n },\n },\n },\n }\n}\n"],"names":["createDeleteUserFromPayload","createSyncUserToPayload","createDatabaseHooks","config","syncUserToPayload","deleteUserFromPayload","user","create","after","delete","id"],"mappings":"AAGA,SAASA,2BAA2B,EAAEC,uBAAuB,QAAQ,eAAc;AAEnF,OAAO,SAASC,oBAAoB,EAClCC,MAAM,EAGP;IACC,MAAMC,oBAAoBH,wBAAwBE;IAClD,MAAME,wBAAwBL,4BAA4BG;IAC1D,OAAO;QACLG,MAAM;YACJC,QAAQ;gBACN,4EAA4E;gBAC5EC,OAAO,OAAOF;oBACZ,uDAAuD;oBACvD,MAAMF,kBAAkBE;gBAC1B;YACF;YACA,gCAAgC;YAChCG,QAAQ;gBACND,OAAO,OAAOF;oBACZ,MAAMD,sBAAsBC,KAAKI,EAAE;gBACrC;YACF;QACF;IACF;AACF"}
1
+ {"version":3,"sources":["../../src/better-auth/databaseHooks.ts"],"sourcesContent":["import type { betterAuth } from 'better-auth'\nimport type { SanitizedConfig } from 'payload'\n\nimport { createDeleteUserFromPayload, createSyncUserToPayload } from './sources'\n\nexport function createDatabaseHooks({\n config,\n}: {\n config: Promise<SanitizedConfig>\n}): Parameters<typeof betterAuth>['0']['databaseHooks'] {\n const syncUserToPayload = createSyncUserToPayload(config)\n const deleteUserFromPayload = createDeleteUserFromPayload(config)\n return {\n user: {\n create: {\n // After the BA user exists, sync to Payload. On failure, enqueue in memory.\n after: async (user) => {\n // push BA-induced ensure to the **front** of the queue\n await syncUserToPayload(user)\n },\n },\n // TODO: possibly offer \"update\"\n delete: {\n after: async (user) => {\n await deleteUserFromPayload(user.id)\n },\n },\n },\n }\n}\n"],"names":["createDeleteUserFromPayload","createSyncUserToPayload","createDatabaseHooks","config","syncUserToPayload","deleteUserFromPayload","user","create","after","delete","id"],"mappings":"AAGA,SAASA,2BAA2B,EAAEC,uBAAuB,QAAQ,YAAW;AAEhF,OAAO,SAASC,oBAAoB,EAClCC,MAAM,EAGP;IACC,MAAMC,oBAAoBH,wBAAwBE;IAClD,MAAME,wBAAwBL,4BAA4BG;IAC1D,OAAO;QACLG,MAAM;YACJC,QAAQ;gBACN,4EAA4E;gBAC5EC,OAAO,OAAOF;oBACZ,uDAAuD;oBACvD,MAAMF,kBAAkBE;gBAC1B;YACF;YACA,gCAAgC;YAChCG,QAAQ;gBACND,OAAO,OAAOF;oBACZ,MAAMD,sBAAsBC,KAAKI,EAAE;gBACrC;YACF;QACF;IACF;AACF"}
@@ -1,6 +1,6 @@
1
1
  import type { AuthContext, BetterAuthPlugin } from 'better-auth';
2
2
  import type { SanitizedConfig } from 'payload';
3
- import { type InitOptions } from './reconcile-queue.js';
3
+ import { type InitOptions } from './reconcile-queue';
4
4
  type CreateAdminsUser = Parameters<AuthContext['internalAdapter']['createUser']>['0'];
5
5
  export declare const payloadBetterAuthPlugin: (opts: {
6
6
  createAdmins?: {
@@ -1,9 +1,9 @@
1
1
  // src/plugins/reconcile-queue-plugin.ts
2
2
  import { APIError } from 'better-auth/api';
3
3
  import { createAuthEndpoint, createAuthMiddleware } from 'better-auth/plugins';
4
- import { createDatabaseHooks } from './databaseHooks.js';
5
- import { Queue } from './reconcile-queue.js';
6
- import { createDeleteUserFromPayload, createListPayloadUsersPage, createSyncUserToPayload } from './sources.js';
4
+ import { createDatabaseHooks } from './databaseHooks';
5
+ import { Queue } from './reconcile-queue';
6
+ import { createDeleteUserFromPayload, createListPayloadUsersPage, createSyncUserToPayload } from './sources';
7
7
  const defaultLog = (msg, extra)=>{
8
8
  console.log(`[reconcile] ${msg}`, extra ? JSON.stringify(extra, null, 2) : '');
9
9
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/better-auth/plugin.ts"],"sourcesContent":["// src/plugins/reconcile-queue-plugin.ts\nimport type { AuthContext, BetterAuthPlugin, DeepPartial } from 'better-auth'\nimport type { SanitizedConfig } from 'payload'\n\nimport { APIError } from 'better-auth/api'\nimport { createAuthEndpoint, createAuthMiddleware } from 'better-auth/plugins'\n\nimport type { AuthMethod } from './helpers.js'\n\nimport { createDatabaseHooks } from './databaseHooks.js'\nimport { type InitOptions, Queue } from './reconcile-queue.js'\nimport {\n type BAUser,\n createDeleteUserFromPayload,\n createListPayloadUsersPage,\n createSyncUserToPayload,\n} from './sources.js'\n\ntype PayloadSyncPluginContext = { payloadSyncPlugin: { queue: Queue } } & AuthContext\n\ntype CreateAdminsUser = Parameters<AuthContext['internalAdapter']['createUser']>['0']\n\nconst defaultLog = (msg: string, extra?: unknown) => {\n console.log(`[reconcile] ${msg}`, extra ? JSON.stringify(extra, null, 2) : '')\n}\n\nexport const payloadBetterAuthPlugin = (\n opts: {\n createAdmins?: { overwrite?: boolean; user: CreateAdminsUser }[]\n enableLogging?: boolean\n payloadConfig: Promise<SanitizedConfig>\n token: string // simple header token for admin endpoints,\n } & InitOptions,\n): BetterAuthPlugin => {\n return {\n id: 'reconcile-queue-plugin',\n endpoints: {\n run: createAuthEndpoint(\n '/reconcile/run',\n { method: 'POST' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n throw new APIError('UNAUTHORIZED', { message: 'invalid token' })\n }\n await (context as PayloadSyncPluginContext).payloadSyncPlugin.queue.seedFullReconcile()\n return json({ ok: true })\n },\n ),\n status: createAuthEndpoint(\n '/reconcile/status',\n { method: 'GET' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n return Promise.reject(\n new APIError('UNAUTHORIZED', { message: 'invalid token' }) as Error,\n )\n }\n return json((context as PayloadSyncPluginContext).payloadSyncPlugin.queue.status())\n },\n ),\n // convenience for tests/admin tools (optional)\n authMethods: createAuthEndpoint(\n '/auth/methods',\n { method: 'GET' },\n async ({ context, json }) => {\n const authMethods: AuthMethod[] = []\n // Check if emailAndPassword is enabled, or if present at all (not present defaults to false)\n if (context.options.emailAndPassword?.enabled) {\n authMethods.push({\n method: 'emailAndPassword',\n options: {\n minPasswordLength: context.options.emailAndPassword.minPasswordLength ?? 0,\n },\n })\n }\n if (context.options.plugins?.some((p) => p.id === 'magic-link')) {\n authMethods.push({ method: 'magicLink' })\n }\n\n return await json(authMethods)\n },\n ),\n deleteNow: createAuthEndpoint(\n '/reconcile/delete',\n { method: 'POST' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n throw new APIError('UNAUTHORIZED', { message: 'invalid token' })\n }\n const body = (await request?.json().catch(() => ({}))) as { baId?: string } | undefined\n const baId = body?.baId\n if (!baId) {\n throw new APIError('BAD_REQUEST', { message: 'missing baId' })\n }\n ;(context as PayloadSyncPluginContext).payloadSyncPlugin.queue.enqueueDelete(\n baId,\n true,\n 'user-operation',\n )\n return json({ ok: true })\n },\n ),\n ensureNow: createAuthEndpoint(\n '/reconcile/ensure',\n { method: 'POST' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n throw new APIError('UNAUTHORIZED', { message: 'invalid token' })\n }\n const body = (await request?.json().catch(() => ({}))) as { user?: BAUser } | undefined\n const user = body?.user\n if (!user?.id) {\n throw new APIError('BAD_REQUEST', { message: 'missing user' })\n }\n ;(context as PayloadSyncPluginContext).payloadSyncPlugin.queue.enqueueEnsure(\n user,\n true,\n 'user-operation',\n )\n return json({ ok: true })\n },\n ),\n },\n hooks: {\n before: [\n {\n handler: createAuthMiddleware(async (ctx) => {\n const locale = ctx.getHeader('User-Locale')\n return Promise.resolve({\n context: { ...ctx, body: { ...ctx.body, locale: locale ?? undefined } },\n })\n }),\n matcher: (context) => {\n return context.path === '/sign-up/email'\n },\n },\n ],\n },\n schema: {\n user: {\n fields: {\n locale: {\n type: 'string',\n required: false,\n },\n },\n },\n },\n // TODO: the queue must be destroyed on better auth instance destruction, as it utilizes timers.\n async init({ internalAdapter, password }) {\n if (opts.createAdmins) {\n try {\n await Promise.all(\n opts.createAdmins.map(async ({ overwrite, user }) => {\n const alreadyExistingUser = await internalAdapter.findUserByEmail(user.email)\n if (alreadyExistingUser) {\n if (overwrite) {\n // clear accounts\n await internalAdapter.deleteAccounts(alreadyExistingUser.user.id)\n const createdUser = await internalAdapter.updateUser(\n alreadyExistingUser.user.id,\n {\n ...user,\n role: 'admin',\n },\n )\n // assuming this creates an account?\n await internalAdapter.linkAccount({\n accountId: createdUser.id,\n password: await password.hash(user.password),\n providerId: 'credential',\n userId: createdUser.id,\n })\n }\n }\n // if the user doesnt exist there can't be an account\n else {\n const createdUser = await internalAdapter.createUser({ ...user, role: 'admin' })\n await internalAdapter.linkAccount({\n accountId: createdUser.id,\n password: await password.hash(user.password),\n providerId: 'credential',\n userId: createdUser.id,\n })\n }\n }),\n )\n } catch (error) {\n if (opts.enableLogging) {\n defaultLog('Failed to create Admin user', error)\n }\n }\n }\n\n const queue = new Queue(\n {\n deleteUserFromPayload: createDeleteUserFromPayload(opts.payloadConfig),\n internalAdapter,\n listPayloadUsersPage: createListPayloadUsersPage(opts.payloadConfig),\n log: opts.enableLogging ? defaultLog : undefined,\n syncUserToPayload: createSyncUserToPayload(opts.payloadConfig),\n },\n opts,\n )\n return {\n context: { payloadSyncPlugin: { queue } } as DeepPartial<Omit<AuthContext, 'options'>>,\n options: {\n databaseHooks: createDatabaseHooks({ config: opts.payloadConfig }),\n user: { deleteUser: { enabled: true } },\n },\n }\n },\n }\n}\n"],"names":["APIError","createAuthEndpoint","createAuthMiddleware","createDatabaseHooks","Queue","createDeleteUserFromPayload","createListPayloadUsersPage","createSyncUserToPayload","defaultLog","msg","extra","console","log","JSON","stringify","payloadBetterAuthPlugin","opts","id","endpoints","run","method","context","json","request","token","headers","get","message","payloadSyncPlugin","queue","seedFullReconcile","ok","status","Promise","reject","authMethods","options","emailAndPassword","enabled","push","minPasswordLength","plugins","some","p","deleteNow","body","catch","baId","enqueueDelete","ensureNow","user","enqueueEnsure","hooks","before","handler","ctx","locale","getHeader","resolve","undefined","matcher","path","schema","fields","type","required","init","internalAdapter","password","createAdmins","all","map","overwrite","alreadyExistingUser","findUserByEmail","email","deleteAccounts","createdUser","updateUser","role","linkAccount","accountId","hash","providerId","userId","createUser","error","enableLogging","deleteUserFromPayload","payloadConfig","listPayloadUsersPage","syncUserToPayload","databaseHooks","config","deleteUser"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/better-auth/plugin.ts"],"sourcesContent":["// src/plugins/reconcile-queue-plugin.ts\nimport type { AuthContext, BetterAuthPlugin, DeepPartial } from 'better-auth'\nimport type { SanitizedConfig } from 'payload'\n\nimport { APIError } from 'better-auth/api'\nimport { createAuthEndpoint, createAuthMiddleware } from 'better-auth/plugins'\n\nimport type { AuthMethod } from './helpers'\n\nimport { createDatabaseHooks } from './databaseHooks'\nimport { type InitOptions, Queue } from './reconcile-queue'\nimport {\n type BAUser,\n createDeleteUserFromPayload,\n createListPayloadUsersPage,\n createSyncUserToPayload,\n} from './sources'\n\ntype PayloadSyncPluginContext = { payloadSyncPlugin: { queue: Queue } } & AuthContext\n\ntype CreateAdminsUser = Parameters<AuthContext['internalAdapter']['createUser']>['0']\n\nconst defaultLog = (msg: string, extra?: unknown) => {\n console.log(`[reconcile] ${msg}`, extra ? JSON.stringify(extra, null, 2) : '')\n}\n\nexport const payloadBetterAuthPlugin = (\n opts: {\n createAdmins?: { overwrite?: boolean; user: CreateAdminsUser }[]\n enableLogging?: boolean\n payloadConfig: Promise<SanitizedConfig>\n token: string // simple header token for admin endpoints,\n } & InitOptions,\n): BetterAuthPlugin => {\n return {\n id: 'reconcile-queue-plugin',\n endpoints: {\n run: createAuthEndpoint(\n '/reconcile/run',\n { method: 'POST' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n throw new APIError('UNAUTHORIZED', { message: 'invalid token' })\n }\n await (context as PayloadSyncPluginContext).payloadSyncPlugin.queue.seedFullReconcile()\n return json({ ok: true })\n },\n ),\n status: createAuthEndpoint(\n '/reconcile/status',\n { method: 'GET' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n return Promise.reject(\n new APIError('UNAUTHORIZED', { message: 'invalid token' }) as Error,\n )\n }\n return json((context as PayloadSyncPluginContext).payloadSyncPlugin.queue.status())\n },\n ),\n // convenience for tests/admin tools (optional)\n authMethods: createAuthEndpoint(\n '/auth/methods',\n { method: 'GET' },\n async ({ context, json }) => {\n const authMethods: AuthMethod[] = []\n // Check if emailAndPassword is enabled, or if present at all (not present defaults to false)\n if (context.options.emailAndPassword?.enabled) {\n authMethods.push({\n method: 'emailAndPassword',\n options: {\n minPasswordLength: context.options.emailAndPassword.minPasswordLength ?? 0,\n },\n })\n }\n if (context.options.plugins?.some((p) => p.id === 'magic-link')) {\n authMethods.push({ method: 'magicLink' })\n }\n\n return await json(authMethods)\n },\n ),\n deleteNow: createAuthEndpoint(\n '/reconcile/delete',\n { method: 'POST' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n throw new APIError('UNAUTHORIZED', { message: 'invalid token' })\n }\n const body = (await request?.json().catch(() => ({}))) as { baId?: string } | undefined\n const baId = body?.baId\n if (!baId) {\n throw new APIError('BAD_REQUEST', { message: 'missing baId' })\n }\n ;(context as PayloadSyncPluginContext).payloadSyncPlugin.queue.enqueueDelete(\n baId,\n true,\n 'user-operation',\n )\n return json({ ok: true })\n },\n ),\n ensureNow: createAuthEndpoint(\n '/reconcile/ensure',\n { method: 'POST' },\n async ({ context, json, request }) => {\n if (opts.token && request?.headers.get('x-reconcile-token') !== opts.token) {\n throw new APIError('UNAUTHORIZED', { message: 'invalid token' })\n }\n const body = (await request?.json().catch(() => ({}))) as { user?: BAUser } | undefined\n const user = body?.user\n if (!user?.id) {\n throw new APIError('BAD_REQUEST', { message: 'missing user' })\n }\n ;(context as PayloadSyncPluginContext).payloadSyncPlugin.queue.enqueueEnsure(\n user,\n true,\n 'user-operation',\n )\n return json({ ok: true })\n },\n ),\n },\n hooks: {\n before: [\n {\n handler: createAuthMiddleware(async (ctx) => {\n const locale = ctx.getHeader('User-Locale')\n return Promise.resolve({\n context: { ...ctx, body: { ...ctx.body, locale: locale ?? undefined } },\n })\n }),\n matcher: (context) => {\n return context.path === '/sign-up/email'\n },\n },\n ],\n },\n schema: {\n user: {\n fields: {\n locale: {\n type: 'string',\n required: false,\n },\n },\n },\n },\n // TODO: the queue must be destroyed on better auth instance destruction, as it utilizes timers.\n async init({ internalAdapter, password }) {\n if (opts.createAdmins) {\n try {\n await Promise.all(\n opts.createAdmins.map(async ({ overwrite, user }) => {\n const alreadyExistingUser = await internalAdapter.findUserByEmail(user.email)\n if (alreadyExistingUser) {\n if (overwrite) {\n // clear accounts\n await internalAdapter.deleteAccounts(alreadyExistingUser.user.id)\n const createdUser = await internalAdapter.updateUser(\n alreadyExistingUser.user.id,\n {\n ...user,\n role: 'admin',\n },\n )\n // assuming this creates an account?\n await internalAdapter.linkAccount({\n accountId: createdUser.id,\n password: await password.hash(user.password),\n providerId: 'credential',\n userId: createdUser.id,\n })\n }\n }\n // if the user doesnt exist there can't be an account\n else {\n const createdUser = await internalAdapter.createUser({ ...user, role: 'admin' })\n await internalAdapter.linkAccount({\n accountId: createdUser.id,\n password: await password.hash(user.password),\n providerId: 'credential',\n userId: createdUser.id,\n })\n }\n }),\n )\n } catch (error) {\n if (opts.enableLogging) {\n defaultLog('Failed to create Admin user', error)\n }\n }\n }\n\n const queue = new Queue(\n {\n deleteUserFromPayload: createDeleteUserFromPayload(opts.payloadConfig),\n internalAdapter,\n listPayloadUsersPage: createListPayloadUsersPage(opts.payloadConfig),\n log: opts.enableLogging ? defaultLog : undefined,\n syncUserToPayload: createSyncUserToPayload(opts.payloadConfig),\n },\n opts,\n )\n return {\n context: { payloadSyncPlugin: { queue } } as DeepPartial<Omit<AuthContext, 'options'>>,\n options: {\n databaseHooks: createDatabaseHooks({ config: opts.payloadConfig }),\n user: { deleteUser: { enabled: true } },\n },\n }\n },\n }\n}\n"],"names":["APIError","createAuthEndpoint","createAuthMiddleware","createDatabaseHooks","Queue","createDeleteUserFromPayload","createListPayloadUsersPage","createSyncUserToPayload","defaultLog","msg","extra","console","log","JSON","stringify","payloadBetterAuthPlugin","opts","id","endpoints","run","method","context","json","request","token","headers","get","message","payloadSyncPlugin","queue","seedFullReconcile","ok","status","Promise","reject","authMethods","options","emailAndPassword","enabled","push","minPasswordLength","plugins","some","p","deleteNow","body","catch","baId","enqueueDelete","ensureNow","user","enqueueEnsure","hooks","before","handler","ctx","locale","getHeader","resolve","undefined","matcher","path","schema","fields","type","required","init","internalAdapter","password","createAdmins","all","map","overwrite","alreadyExistingUser","findUserByEmail","email","deleteAccounts","createdUser","updateUser","role","linkAccount","accountId","hash","providerId","userId","createUser","error","enableLogging","deleteUserFromPayload","payloadConfig","listPayloadUsersPage","syncUserToPayload","databaseHooks","config","deleteUser"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { AuthContext } from 'better-auth';
2
- import type { BAUser, PayloadUser } from './sources.js';
2
+ import type { BAUser, PayloadUser } from './sources';
3
3
  export interface QueueDeps {
4
4
  deleteUserFromPayload: (baId: string) => Promise<void>;
5
5
  internalAdapter: AuthContext['internalAdapter'];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/better-auth/reconcile-queue.ts"],"sourcesContent":["import type { AuthContext } from 'better-auth'\n\n// src/reconcile-queue.ts\nimport type { BAUser, PayloadUser } from './sources.js'\n\nexport interface QueueDeps {\n deleteUserFromPayload: (baId: string) => Promise<void> // delete by externalId; ignore missing\n internalAdapter: AuthContext['internalAdapter']\n\n // Paginated loaders (efficient processing)\n listPayloadUsersPage: (\n limit: number,\n page: number,\n ) => Promise<{ hasNextPage: boolean; total: number; users: PayloadUser[] }>\n // Logging\n log?: (msg: string, extra?: any) => void\n\n // Policy\n prunePayloadOrphans?: boolean // default: false\n\n // Idempotent effects (via Payload Local API)\n syncUserToPayload: (baUser: BAUser) => Promise<void> // upsert by externalId=baUser.id\n}\n\nexport type TaskSource = 'full-reconcile' | 'user-operation'\n\n// Bootstrap options interface\nexport interface InitOptions {\n forceReset?: boolean\n reconcileEveryMs?: number\n runOnBoot?: boolean\n tickMs?: number\n}\n\n// Simplified bootstrap state interface (removed processId)\ninterface BootstrapState {\n adminHeaders: Headers | null\n bootstrapPromise: null | Promise<void>\n isBootstrapped: boolean\n}\n\ntype Task =\n | {\n attempts: number\n baId: string\n baUser?: BAUser\n kind: 'ensure'\n nextAt: number\n reconcileId?: string\n source: TaskSource\n }\n | {\n attempts: number\n baId: string\n kind: 'delete'\n nextAt: number\n reconcileId?: string\n source: TaskSource\n }\n\nconst KEY = (t: Task) => `${t.kind}:${t.baId}`\n\nexport class Queue {\n // Bootstrap state stored directly on the queue instance\n private bootstrapState: BootstrapState = {\n adminHeaders: null,\n bootstrapPromise: null,\n isBootstrapped: false,\n }\n private deps!: QueueDeps\n private failed = 0\n private keys = new Map<string, Task>()\n private lastError: null | string = null\n private lastSeedAt: null | string = null\n private processed = 0\n\n private processing = false\n private q: Task[] = []\n private reconcileEveryMs = 30 * 60_000 // default 30 minutes\n private reconcileTimeout: NodeJS.Timeout | null = null\n private reconciling = false\n\n private tickTimer: NodeJS.Timeout | null = null\n\n constructor(deps: QueueDeps, opts: InitOptions = {}) {\n this.deps = deps\n const log = this.deps?.log ?? (() => {})\n // Start bootstrap process - but defer heavy operations\n log('Starting bootstrap process...')\n\n // Start timers but don't run reconcile immediately\n this.start({\n reconcileEveryMs: opts?.reconcileEveryMs ?? 30 * 60_000,\n tickMs: opts?.tickMs ?? 1000,\n })\n\n // Defer the initial reconcile to avoid circular dependency issues\n if (opts?.runOnBoot ?? true) {\n // Use setTimeout instead of queueMicrotask to give more time for initialization\n setTimeout(() => {\n this.seedFullReconcile().catch(\n (err) => this.deps.log && this.deps.log('[reconcile] seed failed', err),\n )\n }, 2000) // 2 second delay to allow Better Auth and Payload to fully initialize\n }\n\n log('Bootstrap process completed')\n }\n\n private bumpFront(task: Task) {\n this.q = [task, ...this.q.filter((t) => t !== task)]\n }\n\n /** Clear all full-reconcile tasks from the queue, preserving user-operation tasks */\n private clearFullReconcileTasks() {\n const log = this.deps?.log ?? (() => {})\n const beforeCount = this.q.length\n const fullReconcileCount = this.q.filter((t) => t.source === 'full-reconcile').length\n\n // Remove full-reconcile tasks from queue and keys map\n this.q = this.q.filter((task) => {\n if (task.source === 'full-reconcile') {\n this.keys.delete(KEY(task))\n return false\n }\n return true\n })\n\n const afterCount = this.q.length\n log('reconcile.clear-previous', {\n afterCount,\n beforeCount,\n clearedFullReconcile: fullReconcileCount,\n preservedUserOps: afterCount,\n })\n }\n\n // ——— Internals ———\n private enqueue(task: Task, priority: boolean) {\n const k = KEY(task)\n const existing = this.keys.get(k)\n if (existing) {\n if (task.kind === 'ensure' && existing.kind === 'ensure' && !existing.baUser && task.baUser) {\n existing.baUser = task.baUser\n }\n if (priority) {\n this.bumpFront(existing)\n }\n return\n }\n if (priority) {\n this.q.unshift(task)\n } else {\n this.q.push(task)\n }\n this.keys.set(k, task)\n }\n\n private async listBAUsersPage({ limit, offset }: { limit: number; offset: number }) {\n // sort by newest (used) first\n // when a delete is happening in the meantime, this will lead to some users not being listed (as the index changes)\n // TODO: fix this by maintaining a delete list.\n const total = await this.deps.internalAdapter.countTotalUsers()\n const users = await this.deps.internalAdapter.listUsers(limit, offset, {\n direction: 'desc',\n field: 'updatedAt',\n })\n return { total, users }\n }\n\n private async runTask(t: Task) {\n const log = this.deps?.log ?? (() => {})\n if (t.kind === 'ensure') {\n log('queue.ensure', { attempts: t.attempts, baId: t.baId })\n await this.deps.syncUserToPayload(t.baUser ?? { id: t.baId })\n return\n }\n // delete\n log('queue.delete', { attempts: t.attempts, baId: t.baId })\n await this.deps.deleteUserFromPayload(t.baId)\n }\n private scheduleNextReconcile() {\n if (this.reconcileTimeout) {\n clearTimeout(this.reconcileTimeout)\n }\n\n this.reconcileTimeout = setTimeout(async () => {\n if (!this.reconciling) {\n this.reconciling = true\n try {\n await this.seedFullReconcile()\n } catch (error) {\n // Error is already logged in seedFullReconcile\n } finally {\n this.reconciling = false\n // Schedule the next reconcile after this one completes\n this.scheduleNextReconcile()\n }\n }\n }, this.reconcileEveryMs)\n\n // Optional unref for Node.js environments to prevent keeping process alive\n if ('unref' in this.reconcileTimeout && typeof this.reconcileTimeout.unref === 'function') {\n this.reconcileTimeout.unref()\n }\n }\n\n /** Paginated approach: process users page by page to reduce memory usage */\n private async seedFullReconcilePaginated(reconcileId: string) {\n const log = this.deps?.log ?? (() => {})\n const pageSize = 500\n let baIdSet: null | Set<string> = null\n\n // If we need to prune orphans, we need to collect all BA user IDs\n if (this.deps.prunePayloadOrphans) {\n baIdSet = new Set<string>()\n let baOffset = 0\n let baTotal = 0\n\n do {\n const { total, users: baUsers } = await this.listBAUsersPage({\n limit: pageSize,\n offset: baOffset,\n })\n baTotal = total\n\n // Enqueue ensure tasks for this page with full-reconcile source\n for (const u of baUsers) {\n this.enqueueEnsure(u, false, 'full-reconcile', reconcileId)\n baIdSet.add(u.id)\n }\n\n baOffset += baUsers.length\n log('reconcile.seed.ba-page', { processed: baOffset, reconcileId, total: baTotal })\n } while (baOffset < baTotal)\n } else {\n // If not pruning, we can process BA users page by page without storing IDs\n let baOffset = 0\n let baTotal = 0\n\n do {\n // TODO: make sure that we dont go past the window through deletes happening\n // (As a user deletes, the total window size becomes smaller)\n const { total, users: baUsers } = await this.listBAUsersPage({\n limit: pageSize,\n offset: baOffset,\n })\n baTotal = total\n\n // Enqueue ensure tasks for this page with full-reconcile source\n for (const u of baUsers) {\n this.enqueueEnsure(u, false, 'full-reconcile', reconcileId)\n }\n\n baOffset += baUsers.length\n log('reconcile.seed.ba-page', { processed: baOffset, reconcileId, total: baTotal })\n } while (baOffset < baTotal)\n }\n\n // Process Payload users page by page for orphan pruning\n if (this.deps.prunePayloadOrphans && baIdSet) {\n let payloadPage = 1\n let hasNextPage = true\n\n while (hasNextPage) {\n const { hasNextPage: nextPage, users: pUsers } = await this.deps.listPayloadUsersPage(\n pageSize,\n payloadPage,\n )\n hasNextPage = nextPage\n\n for (const pu of pUsers) {\n const ext = pu.externalId?.toString()\n if (ext && !baIdSet.has(ext)) {\n this.enqueueDelete(ext, false, 'full-reconcile', reconcileId)\n }\n }\n\n payloadPage++\n log('reconcile.seed.payload-page', { page: payloadPage - 1, reconcileId })\n }\n }\n }\n\n private async tick() {\n if (this.processing) {\n return\n }\n const now = Date.now()\n const idx = this.q.findIndex((t) => t.nextAt <= now)\n if (idx === -1) {\n return\n }\n const task = this.q[idx]\n this.processing = true\n try {\n await this.runTask(task)\n this.q.splice(idx, 1)\n this.keys.delete(KEY(task))\n this.processed++\n } catch (e: any) {\n this.failed++\n this.lastError = e?.message ?? String(e)\n task.attempts += 1\n const delay =\n Math.min(60_000, Math.pow(2, task.attempts) * 1000) + Math.floor(Math.random() * 500)\n task.nextAt = now + delay\n } finally {\n this.processing = false\n }\n }\n\n enqueueDelete(\n baId: string,\n priority = false,\n source: TaskSource = 'user-operation',\n reconcileId?: string,\n ) {\n this.enqueue(\n { attempts: 0, baId, kind: 'delete', nextAt: Date.now(), reconcileId, source },\n priority,\n )\n }\n\n // ——— Public enqueue API ———\n enqueueEnsure(\n user: BAUser,\n priority = false,\n source: TaskSource = 'user-operation',\n reconcileId?: string,\n ) {\n this.enqueue(\n {\n attempts: 0,\n baId: user.id,\n baUser: user,\n kind: 'ensure',\n nextAt: Date.now(),\n reconcileId,\n source,\n },\n priority,\n )\n }\n\n // Get current instance info\n getInstanceInfo() {\n return {\n isBootstrapped: this.bootstrapState.isBootstrapped,\n }\n }\n\n /** Seed tasks by comparing users page by page (Better-Auth → Payload). */\n async seedFullReconcile() {\n const log = this.deps?.log ?? (() => {})\n this.lastSeedAt = new Date().toISOString()\n const reconcileId = `reconcile-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`\n\n log('reconcile.seed.start', { reconcileId })\n\n // Clear all previous full-reconcile tasks, but preserve user-operation tasks\n this.clearFullReconcileTasks()\n\n await this.seedFullReconcilePaginated(reconcileId)\n\n log('reconcile.seed.done', this.status())\n }\n\n start({ reconcileEveryMs = 30 * 60_000, tickMs = 1000 } = {}) {\n this.reconcileEveryMs = reconcileEveryMs\n\n if (!this.tickTimer) {\n this.tickTimer = setInterval(() => this.tick(), tickMs)\n // Optional unref for Node.js environments to prevent keeping process alive\n if ('unref' in this.tickTimer && typeof this.tickTimer.unref === 'function') {\n this.tickTimer.unref()\n }\n }\n\n // Schedule the first reconcile\n this.scheduleNextReconcile()\n }\n\n status() {\n const userOpCount = this.q.filter((t) => t.source === 'user-operation').length\n const fullReconcileCount = this.q.filter((t) => t.source === 'full-reconcile').length\n\n return {\n failed: this.failed,\n fullReconcileTasks: fullReconcileCount,\n lastError: this.lastError,\n lastSeedAt: this.lastSeedAt,\n processed: this.processed,\n processing: this.processing,\n queueSize: this.q.length,\n reconciling: this.reconciling,\n sampleKeys: Array.from(this.keys.keys()).slice(0, 50),\n userOperationTasks: userOpCount,\n }\n }\n}\n"],"names":["KEY","t","kind","baId","Queue","bootstrapState","adminHeaders","bootstrapPromise","isBootstrapped","deps","failed","keys","Map","lastError","lastSeedAt","processed","processing","q","reconcileEveryMs","reconcileTimeout","reconciling","tickTimer","opts","log","start","tickMs","runOnBoot","setTimeout","seedFullReconcile","catch","err","bumpFront","task","filter","clearFullReconcileTasks","beforeCount","length","fullReconcileCount","source","delete","afterCount","clearedFullReconcile","preservedUserOps","enqueue","priority","k","existing","get","baUser","unshift","push","set","listBAUsersPage","limit","offset","total","internalAdapter","countTotalUsers","users","listUsers","direction","field","runTask","attempts","syncUserToPayload","id","deleteUserFromPayload","scheduleNextReconcile","clearTimeout","error","unref","seedFullReconcilePaginated","reconcileId","pageSize","baIdSet","prunePayloadOrphans","Set","baOffset","baTotal","baUsers","u","enqueueEnsure","add","payloadPage","hasNextPage","nextPage","pUsers","listPayloadUsersPage","pu","ext","externalId","toString","has","enqueueDelete","page","tick","now","Date","idx","findIndex","nextAt","splice","e","message","String","delay","Math","min","pow","floor","random","user","getInstanceInfo","toISOString","substr","status","setInterval","userOpCount","fullReconcileTasks","queueSize","sampleKeys","Array","from","slice","userOperationTasks"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/better-auth/reconcile-queue.ts"],"sourcesContent":["import type { AuthContext } from 'better-auth'\n\n// src/reconcile-queue.ts\nimport type { BAUser, PayloadUser } from './sources'\n\nexport interface QueueDeps {\n deleteUserFromPayload: (baId: string) => Promise<void> // delete by externalId; ignore missing\n internalAdapter: AuthContext['internalAdapter']\n\n // Paginated loaders (efficient processing)\n listPayloadUsersPage: (\n limit: number,\n page: number,\n ) => Promise<{ hasNextPage: boolean; total: number; users: PayloadUser[] }>\n // Logging\n log?: (msg: string, extra?: any) => void\n\n // Policy\n prunePayloadOrphans?: boolean // default: false\n\n // Idempotent effects (via Payload Local API)\n syncUserToPayload: (baUser: BAUser) => Promise<void> // upsert by externalId=baUser.id\n}\n\nexport type TaskSource = 'full-reconcile' | 'user-operation'\n\n// Bootstrap options interface\nexport interface InitOptions {\n forceReset?: boolean\n reconcileEveryMs?: number\n runOnBoot?: boolean\n tickMs?: number\n}\n\n// Simplified bootstrap state interface (removed processId)\ninterface BootstrapState {\n adminHeaders: Headers | null\n bootstrapPromise: null | Promise<void>\n isBootstrapped: boolean\n}\n\ntype Task =\n | {\n attempts: number\n baId: string\n baUser?: BAUser\n kind: 'ensure'\n nextAt: number\n reconcileId?: string\n source: TaskSource\n }\n | {\n attempts: number\n baId: string\n kind: 'delete'\n nextAt: number\n reconcileId?: string\n source: TaskSource\n }\n\nconst KEY = (t: Task) => `${t.kind}:${t.baId}`\n\nexport class Queue {\n // Bootstrap state stored directly on the queue instance\n private bootstrapState: BootstrapState = {\n adminHeaders: null,\n bootstrapPromise: null,\n isBootstrapped: false,\n }\n private deps!: QueueDeps\n private failed = 0\n private keys = new Map<string, Task>()\n private lastError: null | string = null\n private lastSeedAt: null | string = null\n private processed = 0\n\n private processing = false\n private q: Task[] = []\n private reconcileEveryMs = 30 * 60_000 // default 30 minutes\n private reconcileTimeout: NodeJS.Timeout | null = null\n private reconciling = false\n\n private tickTimer: NodeJS.Timeout | null = null\n\n constructor(deps: QueueDeps, opts: InitOptions = {}) {\n this.deps = deps\n const log = this.deps?.log ?? (() => {})\n // Start bootstrap process - but defer heavy operations\n log('Starting bootstrap process...')\n\n // Start timers but don't run reconcile immediately\n this.start({\n reconcileEveryMs: opts?.reconcileEveryMs ?? 30 * 60_000,\n tickMs: opts?.tickMs ?? 1000,\n })\n\n // Defer the initial reconcile to avoid circular dependency issues\n if (opts?.runOnBoot ?? true) {\n // Use setTimeout instead of queueMicrotask to give more time for initialization\n setTimeout(() => {\n this.seedFullReconcile().catch(\n (err) => this.deps.log && this.deps.log('[reconcile] seed failed', err),\n )\n }, 2000) // 2 second delay to allow Better Auth and Payload to fully initialize\n }\n\n log('Bootstrap process completed')\n }\n\n private bumpFront(task: Task) {\n this.q = [task, ...this.q.filter((t) => t !== task)]\n }\n\n /** Clear all full-reconcile tasks from the queue, preserving user-operation tasks */\n private clearFullReconcileTasks() {\n const log = this.deps?.log ?? (() => {})\n const beforeCount = this.q.length\n const fullReconcileCount = this.q.filter((t) => t.source === 'full-reconcile').length\n\n // Remove full-reconcile tasks from queue and keys map\n this.q = this.q.filter((task) => {\n if (task.source === 'full-reconcile') {\n this.keys.delete(KEY(task))\n return false\n }\n return true\n })\n\n const afterCount = this.q.length\n log('reconcile.clear-previous', {\n afterCount,\n beforeCount,\n clearedFullReconcile: fullReconcileCount,\n preservedUserOps: afterCount,\n })\n }\n\n // ——— Internals ———\n private enqueue(task: Task, priority: boolean) {\n const k = KEY(task)\n const existing = this.keys.get(k)\n if (existing) {\n if (task.kind === 'ensure' && existing.kind === 'ensure' && !existing.baUser && task.baUser) {\n existing.baUser = task.baUser\n }\n if (priority) {\n this.bumpFront(existing)\n }\n return\n }\n if (priority) {\n this.q.unshift(task)\n } else {\n this.q.push(task)\n }\n this.keys.set(k, task)\n }\n\n private async listBAUsersPage({ limit, offset }: { limit: number; offset: number }) {\n // sort by newest (used) first\n // when a delete is happening in the meantime, this will lead to some users not being listed (as the index changes)\n // TODO: fix this by maintaining a delete list.\n const total = await this.deps.internalAdapter.countTotalUsers()\n const users = await this.deps.internalAdapter.listUsers(limit, offset, {\n direction: 'desc',\n field: 'updatedAt',\n })\n return { total, users }\n }\n\n private async runTask(t: Task) {\n const log = this.deps?.log ?? (() => {})\n if (t.kind === 'ensure') {\n log('queue.ensure', { attempts: t.attempts, baId: t.baId })\n await this.deps.syncUserToPayload(t.baUser ?? { id: t.baId })\n return\n }\n // delete\n log('queue.delete', { attempts: t.attempts, baId: t.baId })\n await this.deps.deleteUserFromPayload(t.baId)\n }\n private scheduleNextReconcile() {\n if (this.reconcileTimeout) {\n clearTimeout(this.reconcileTimeout)\n }\n\n this.reconcileTimeout = setTimeout(async () => {\n if (!this.reconciling) {\n this.reconciling = true\n try {\n await this.seedFullReconcile()\n } catch (error) {\n // Error is already logged in seedFullReconcile\n } finally {\n this.reconciling = false\n // Schedule the next reconcile after this one completes\n this.scheduleNextReconcile()\n }\n }\n }, this.reconcileEveryMs)\n\n // Optional unref for Node.js environments to prevent keeping process alive\n if ('unref' in this.reconcileTimeout && typeof this.reconcileTimeout.unref === 'function') {\n this.reconcileTimeout.unref()\n }\n }\n\n /** Paginated approach: process users page by page to reduce memory usage */\n private async seedFullReconcilePaginated(reconcileId: string) {\n const log = this.deps?.log ?? (() => {})\n const pageSize = 500\n let baIdSet: null | Set<string> = null\n\n // If we need to prune orphans, we need to collect all BA user IDs\n if (this.deps.prunePayloadOrphans) {\n baIdSet = new Set<string>()\n let baOffset = 0\n let baTotal = 0\n\n do {\n const { total, users: baUsers } = await this.listBAUsersPage({\n limit: pageSize,\n offset: baOffset,\n })\n baTotal = total\n\n // Enqueue ensure tasks for this page with full-reconcile source\n for (const u of baUsers) {\n this.enqueueEnsure(u, false, 'full-reconcile', reconcileId)\n baIdSet.add(u.id)\n }\n\n baOffset += baUsers.length\n log('reconcile.seed.ba-page', { processed: baOffset, reconcileId, total: baTotal })\n } while (baOffset < baTotal)\n } else {\n // If not pruning, we can process BA users page by page without storing IDs\n let baOffset = 0\n let baTotal = 0\n\n do {\n // TODO: make sure that we dont go past the window through deletes happening\n // (As a user deletes, the total window size becomes smaller)\n const { total, users: baUsers } = await this.listBAUsersPage({\n limit: pageSize,\n offset: baOffset,\n })\n baTotal = total\n\n // Enqueue ensure tasks for this page with full-reconcile source\n for (const u of baUsers) {\n this.enqueueEnsure(u, false, 'full-reconcile', reconcileId)\n }\n\n baOffset += baUsers.length\n log('reconcile.seed.ba-page', { processed: baOffset, reconcileId, total: baTotal })\n } while (baOffset < baTotal)\n }\n\n // Process Payload users page by page for orphan pruning\n if (this.deps.prunePayloadOrphans && baIdSet) {\n let payloadPage = 1\n let hasNextPage = true\n\n while (hasNextPage) {\n const { hasNextPage: nextPage, users: pUsers } = await this.deps.listPayloadUsersPage(\n pageSize,\n payloadPage,\n )\n hasNextPage = nextPage\n\n for (const pu of pUsers) {\n const ext = pu.externalId?.toString()\n if (ext && !baIdSet.has(ext)) {\n this.enqueueDelete(ext, false, 'full-reconcile', reconcileId)\n }\n }\n\n payloadPage++\n log('reconcile.seed.payload-page', { page: payloadPage - 1, reconcileId })\n }\n }\n }\n\n private async tick() {\n if (this.processing) {\n return\n }\n const now = Date.now()\n const idx = this.q.findIndex((t) => t.nextAt <= now)\n if (idx === -1) {\n return\n }\n const task = this.q[idx]\n this.processing = true\n try {\n await this.runTask(task)\n this.q.splice(idx, 1)\n this.keys.delete(KEY(task))\n this.processed++\n } catch (e: any) {\n this.failed++\n this.lastError = e?.message ?? String(e)\n task.attempts += 1\n const delay =\n Math.min(60_000, Math.pow(2, task.attempts) * 1000) + Math.floor(Math.random() * 500)\n task.nextAt = now + delay\n } finally {\n this.processing = false\n }\n }\n\n enqueueDelete(\n baId: string,\n priority = false,\n source: TaskSource = 'user-operation',\n reconcileId?: string,\n ) {\n this.enqueue(\n { attempts: 0, baId, kind: 'delete', nextAt: Date.now(), reconcileId, source },\n priority,\n )\n }\n\n // ——— Public enqueue API ———\n enqueueEnsure(\n user: BAUser,\n priority = false,\n source: TaskSource = 'user-operation',\n reconcileId?: string,\n ) {\n this.enqueue(\n {\n attempts: 0,\n baId: user.id,\n baUser: user,\n kind: 'ensure',\n nextAt: Date.now(),\n reconcileId,\n source,\n },\n priority,\n )\n }\n\n // Get current instance info\n getInstanceInfo() {\n return {\n isBootstrapped: this.bootstrapState.isBootstrapped,\n }\n }\n\n /** Seed tasks by comparing users page by page (Better-Auth → Payload). */\n async seedFullReconcile() {\n const log = this.deps?.log ?? (() => {})\n this.lastSeedAt = new Date().toISOString()\n const reconcileId = `reconcile-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`\n\n log('reconcile.seed.start', { reconcileId })\n\n // Clear all previous full-reconcile tasks, but preserve user-operation tasks\n this.clearFullReconcileTasks()\n\n await this.seedFullReconcilePaginated(reconcileId)\n\n log('reconcile.seed.done', this.status())\n }\n\n start({ reconcileEveryMs = 30 * 60_000, tickMs = 1000 } = {}) {\n this.reconcileEveryMs = reconcileEveryMs\n\n if (!this.tickTimer) {\n this.tickTimer = setInterval(() => this.tick(), tickMs)\n // Optional unref for Node.js environments to prevent keeping process alive\n if ('unref' in this.tickTimer && typeof this.tickTimer.unref === 'function') {\n this.tickTimer.unref()\n }\n }\n\n // Schedule the first reconcile\n this.scheduleNextReconcile()\n }\n\n status() {\n const userOpCount = this.q.filter((t) => t.source === 'user-operation').length\n const fullReconcileCount = this.q.filter((t) => t.source === 'full-reconcile').length\n\n return {\n failed: this.failed,\n fullReconcileTasks: fullReconcileCount,\n lastError: this.lastError,\n lastSeedAt: this.lastSeedAt,\n processed: this.processed,\n processing: this.processing,\n queueSize: this.q.length,\n reconciling: this.reconciling,\n sampleKeys: Array.from(this.keys.keys()).slice(0, 50),\n userOperationTasks: userOpCount,\n }\n }\n}\n"],"names":["KEY","t","kind","baId","Queue","bootstrapState","adminHeaders","bootstrapPromise","isBootstrapped","deps","failed","keys","Map","lastError","lastSeedAt","processed","processing","q","reconcileEveryMs","reconcileTimeout","reconciling","tickTimer","opts","log","start","tickMs","runOnBoot","setTimeout","seedFullReconcile","catch","err","bumpFront","task","filter","clearFullReconcileTasks","beforeCount","length","fullReconcileCount","source","delete","afterCount","clearedFullReconcile","preservedUserOps","enqueue","priority","k","existing","get","baUser","unshift","push","set","listBAUsersPage","limit","offset","total","internalAdapter","countTotalUsers","users","listUsers","direction","field","runTask","attempts","syncUserToPayload","id","deleteUserFromPayload","scheduleNextReconcile","clearTimeout","error","unref","seedFullReconcilePaginated","reconcileId","pageSize","baIdSet","prunePayloadOrphans","Set","baOffset","baTotal","baUsers","u","enqueueEnsure","add","payloadPage","hasNextPage","nextPage","pUsers","listPayloadUsersPage","pu","ext","externalId","toString","has","enqueueDelete","page","tick","now","Date","idx","findIndex","nextAt","splice","e","message","String","delay","Math","min","pow","floor","random","user","getInstanceInfo","toISOString","substr","status","setInterval","userOpCount","fullReconcileTasks","queueSize","sampleKeys","Array","from","slice","userOperationTasks"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  // src/sources.ts
2
2
  import { getPayload } from 'payload';
3
- import { signCanonical } from './crypto-shared.js';
3
+ import { signCanonical } from './crypto-shared';
4
4
  const INTERNAL_SECRET = process.env.BA_TO_PAYLOAD_SECRET;
5
5
  /** Create a function to load Payload users page by page via Local API. */ export function createListPayloadUsersPage(config) {
6
6
  return async function listPayloadUsersPage(limit, page) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/better-auth/sources.ts"],"sourcesContent":["// src/sources.ts\nimport { getPayload, type SanitizedConfig } from 'payload'\n\nimport { signCanonical } from './crypto-shared.js'\n\nconst INTERNAL_SECRET = process.env.BA_TO_PAYLOAD_SECRET!\n\nexport type BAUser = { [k: string]: any; email?: null | string; id: string }\nexport type PayloadUser = { externalId?: null | string; id: number | string }\n\n// Better Auth user type for sync operations\nexport interface BetterAuthUser {\n [k: string]: any\n email?: null | string\n id: string\n name?: null | string\n}\n\n/** Create a function to load Payload users page by page via Local API. */\nexport function createListPayloadUsersPage(config: Promise<SanitizedConfig>) {\n return async function listPayloadUsersPage(\n limit: number,\n page: number,\n ): Promise<{ hasNextPage: boolean; total: number; users: PayloadUser[] }> {\n const payload = await getPayload({ config })\n const res = await payload.find({\n collection: 'users',\n depth: 0,\n limit,\n overrideAccess: true,\n page,\n })\n return {\n hasNextPage: res.hasNextPage || false,\n total: res.totalDocs || 0,\n users: res.docs.map((d: any) => ({\n id: d.id,\n externalId: d.externalId,\n })),\n }\n }\n}\n\n// Better-auth is the single source of truth and manages users through database hooks\n// These functions provide bidirectional validation and sync capabilities\n/**\n * Sync user from better-auth to Payload\n * This is called from the better-auth hooks\n * Creates a Payload user with externalId, which prevents reverse sync\n */\n\n/**\n * Create a function to sync user from better-auth to Payload\n * This is called from the better-auth hooks\n * Creates a Payload user with externalId, which prevents reverse sync\n */\nexport function createSyncUserToPayload(config: Promise<SanitizedConfig>) {\n return async function syncUserToPayload(betterAuthUser: BetterAuthUser) {\n const payload = await getPayload({ config })\n\n // idempotency check (keep as-is)\n const existing = await payload.find({\n collection: 'users',\n limit: 1,\n where: { externalId: { equals: betterAuthUser.id } },\n })\n if (existing.docs.length) {\n return\n }\n\n const baBody = { op: 'create', userId: betterAuthUser.id } // keep body minimal & stable\n const baSig = signCanonical(baBody, INTERNAL_SECRET)\n\n await payload.create({\n collection: 'users',\n context: { baBody, baSig },\n data: {\n name: betterAuthUser.name ?? '',\n externalId: betterAuthUser.id,\n },\n overrideAccess: false,\n })\n }\n}\n\n// Create a function to delete user from Payload\nexport function createDeleteUserFromPayload(config: Promise<SanitizedConfig>) {\n return async function deleteUserFromPayload(betterAuthUserId: string) {\n const payload = await getPayload({ config })\n\n const existing = await payload.find({\n collection: 'users',\n limit: 1,\n where: { externalId: { equals: betterAuthUserId } },\n })\n if (!existing.docs.length) {\n return\n }\n\n const baBody = { op: 'delete', userId: betterAuthUserId }\n const baSig = signCanonical(baBody, INTERNAL_SECRET)\n\n await payload.delete({\n id: existing.docs[0].id,\n collection: 'users',\n context: { baBody, baSig },\n overrideAccess: false,\n })\n }\n}\n\n// ——— Optional: link an existing Payload user (id-matched) to BA id\nexport function createAttachExternalIdInPayload(config: Promise<SanitizedConfig>) {\n return async function attachExternalIdInPayload(payloadUserId: number | string, baId: string) {\n const payload = await getPayload({ config })\n await payload.update({\n id: payloadUserId,\n collection: 'users',\n data: { externalId: baId },\n overrideAccess: true,\n })\n }\n}\n"],"names":["getPayload","signCanonical","INTERNAL_SECRET","process","env","BA_TO_PAYLOAD_SECRET","createListPayloadUsersPage","config","listPayloadUsersPage","limit","page","payload","res","find","collection","depth","overrideAccess","hasNextPage","total","totalDocs","users","docs","map","d","id","externalId","createSyncUserToPayload","syncUserToPayload","betterAuthUser","existing","where","equals","length","baBody","op","userId","baSig","create","context","data","name","createDeleteUserFromPayload","deleteUserFromPayload","betterAuthUserId","delete","createAttachExternalIdInPayload","attachExternalIdInPayload","payloadUserId","baId","update"],"mappings":"AAAA,iBAAiB;AACjB,SAASA,UAAU,QAA8B,UAAS;AAE1D,SAASC,aAAa,QAAQ,qBAAoB;AAElD,MAAMC,kBAAkBC,QAAQC,GAAG,CAACC,oBAAoB;AAaxD,wEAAwE,GACxE,OAAO,SAASC,2BAA2BC,MAAgC;IACzE,OAAO,eAAeC,qBACpBC,KAAa,EACbC,IAAY;QAEZ,MAAMC,UAAU,MAAMX,WAAW;YAAEO;QAAO;QAC1C,MAAMK,MAAM,MAAMD,QAAQE,IAAI,CAAC;YAC7BC,YAAY;YACZC,OAAO;YACPN;YACAO,gBAAgB;YAChBN;QACF;QACA,OAAO;YACLO,aAAaL,IAAIK,WAAW,IAAI;YAChCC,OAAON,IAAIO,SAAS,IAAI;YACxBC,OAAOR,IAAIS,IAAI,CAACC,GAAG,CAAC,CAACC,IAAY,CAAA;oBAC/BC,IAAID,EAAEC,EAAE;oBACRC,YAAYF,EAAEE,UAAU;gBAC1B,CAAA;QACF;IACF;AACF;AAEA,qFAAqF;AACrF,yEAAyE;AACzE;;;;CAIC,GAED;;;;CAIC,GACD,OAAO,SAASC,wBAAwBnB,MAAgC;IACtE,OAAO,eAAeoB,kBAAkBC,cAA8B;QACpE,MAAMjB,UAAU,MAAMX,WAAW;YAAEO;QAAO;QAE1C,iCAAiC;QACjC,MAAMsB,WAAW,MAAMlB,QAAQE,IAAI,CAAC;YAClCC,YAAY;YACZL,OAAO;YACPqB,OAAO;gBAAEL,YAAY;oBAAEM,QAAQH,eAAeJ,EAAE;gBAAC;YAAE;QACrD;QACA,IAAIK,SAASR,IAAI,CAACW,MAAM,EAAE;YACxB;QACF;QAEA,MAAMC,SAAS;YAAEC,IAAI;YAAUC,QAAQP,eAAeJ,EAAE;QAAC,EAAE,6BAA6B;;QACxF,MAAMY,QAAQnC,cAAcgC,QAAQ/B;QAEpC,MAAMS,QAAQ0B,MAAM,CAAC;YACnBvB,YAAY;YACZwB,SAAS;gBAAEL;gBAAQG;YAAM;YACzBG,MAAM;gBACJC,MAAMZ,eAAeY,IAAI,IAAI;gBAC7Bf,YAAYG,eAAeJ,EAAE;YAC/B;YACAR,gBAAgB;QAClB;IACF;AACF;AAEA,gDAAgD;AAChD,OAAO,SAASyB,4BAA4BlC,MAAgC;IAC1E,OAAO,eAAemC,sBAAsBC,gBAAwB;QAClE,MAAMhC,UAAU,MAAMX,WAAW;YAAEO;QAAO;QAE1C,MAAMsB,WAAW,MAAMlB,QAAQE,IAAI,CAAC;YAClCC,YAAY;YACZL,OAAO;YACPqB,OAAO;gBAAEL,YAAY;oBAAEM,QAAQY;gBAAiB;YAAE;QACpD;QACA,IAAI,CAACd,SAASR,IAAI,CAACW,MAAM,EAAE;YACzB;QACF;QAEA,MAAMC,SAAS;YAAEC,IAAI;YAAUC,QAAQQ;QAAiB;QACxD,MAAMP,QAAQnC,cAAcgC,QAAQ/B;QAEpC,MAAMS,QAAQiC,MAAM,CAAC;YACnBpB,IAAIK,SAASR,IAAI,CAAC,EAAE,CAACG,EAAE;YACvBV,YAAY;YACZwB,SAAS;gBAAEL;gBAAQG;YAAM;YACzBpB,gBAAgB;QAClB;IACF;AACF;AAEA,oEAAoE;AACpE,OAAO,SAAS6B,gCAAgCtC,MAAgC;IAC9E,OAAO,eAAeuC,0BAA0BC,aAA8B,EAAEC,IAAY;QAC1F,MAAMrC,UAAU,MAAMX,WAAW;YAAEO;QAAO;QAC1C,MAAMI,QAAQsC,MAAM,CAAC;YACnBzB,IAAIuB;YACJjC,YAAY;YACZyB,MAAM;gBAAEd,YAAYuB;YAAK;YACzBhC,gBAAgB;QAClB;IACF;AACF"}
1
+ {"version":3,"sources":["../../src/better-auth/sources.ts"],"sourcesContent":["// src/sources.ts\nimport { getPayload, type SanitizedConfig } from 'payload'\n\nimport { signCanonical } from './crypto-shared'\n\nconst INTERNAL_SECRET = process.env.BA_TO_PAYLOAD_SECRET!\n\nexport type BAUser = { [k: string]: any; email?: null | string; id: string }\nexport type PayloadUser = { externalId?: null | string; id: number | string }\n\n// Better Auth user type for sync operations\nexport interface BetterAuthUser {\n [k: string]: any\n email?: null | string\n id: string\n name?: null | string\n}\n\n/** Create a function to load Payload users page by page via Local API. */\nexport function createListPayloadUsersPage(config: Promise<SanitizedConfig>) {\n return async function listPayloadUsersPage(\n limit: number,\n page: number,\n ): Promise<{ hasNextPage: boolean; total: number; users: PayloadUser[] }> {\n const payload = await getPayload({ config })\n const res = await payload.find({\n collection: 'users',\n depth: 0,\n limit,\n overrideAccess: true,\n page,\n })\n return {\n hasNextPage: res.hasNextPage || false,\n total: res.totalDocs || 0,\n users: res.docs.map((d: any) => ({\n id: d.id,\n externalId: d.externalId,\n })),\n }\n }\n}\n\n// Better-auth is the single source of truth and manages users through database hooks\n// These functions provide bidirectional validation and sync capabilities\n/**\n * Sync user from better-auth to Payload\n * This is called from the better-auth hooks\n * Creates a Payload user with externalId, which prevents reverse sync\n */\n\n/**\n * Create a function to sync user from better-auth to Payload\n * This is called from the better-auth hooks\n * Creates a Payload user with externalId, which prevents reverse sync\n */\nexport function createSyncUserToPayload(config: Promise<SanitizedConfig>) {\n return async function syncUserToPayload(betterAuthUser: BetterAuthUser) {\n const payload = await getPayload({ config })\n\n // idempotency check (keep as-is)\n const existing = await payload.find({\n collection: 'users',\n limit: 1,\n where: { externalId: { equals: betterAuthUser.id } },\n })\n if (existing.docs.length) {\n return\n }\n\n const baBody = { op: 'create', userId: betterAuthUser.id } // keep body minimal & stable\n const baSig = signCanonical(baBody, INTERNAL_SECRET)\n\n await payload.create({\n collection: 'users',\n context: { baBody, baSig },\n data: {\n name: betterAuthUser.name ?? '',\n externalId: betterAuthUser.id,\n },\n overrideAccess: false,\n })\n }\n}\n\n// Create a function to delete user from Payload\nexport function createDeleteUserFromPayload(config: Promise<SanitizedConfig>) {\n return async function deleteUserFromPayload(betterAuthUserId: string) {\n const payload = await getPayload({ config })\n\n const existing = await payload.find({\n collection: 'users',\n limit: 1,\n where: { externalId: { equals: betterAuthUserId } },\n })\n if (!existing.docs.length) {\n return\n }\n\n const baBody = { op: 'delete', userId: betterAuthUserId }\n const baSig = signCanonical(baBody, INTERNAL_SECRET)\n\n await payload.delete({\n id: existing.docs[0].id,\n collection: 'users',\n context: { baBody, baSig },\n overrideAccess: false,\n })\n }\n}\n\n// ——— Optional: link an existing Payload user (id-matched) to BA id\nexport function createAttachExternalIdInPayload(config: Promise<SanitizedConfig>) {\n return async function attachExternalIdInPayload(payloadUserId: number | string, baId: string) {\n const payload = await getPayload({ config })\n await payload.update({\n id: payloadUserId,\n collection: 'users',\n data: { externalId: baId },\n overrideAccess: true,\n })\n }\n}\n"],"names":["getPayload","signCanonical","INTERNAL_SECRET","process","env","BA_TO_PAYLOAD_SECRET","createListPayloadUsersPage","config","listPayloadUsersPage","limit","page","payload","res","find","collection","depth","overrideAccess","hasNextPage","total","totalDocs","users","docs","map","d","id","externalId","createSyncUserToPayload","syncUserToPayload","betterAuthUser","existing","where","equals","length","baBody","op","userId","baSig","create","context","data","name","createDeleteUserFromPayload","deleteUserFromPayload","betterAuthUserId","delete","createAttachExternalIdInPayload","attachExternalIdInPayload","payloadUserId","baId","update"],"mappings":"AAAA,iBAAiB;AACjB,SAASA,UAAU,QAA8B,UAAS;AAE1D,SAASC,aAAa,QAAQ,kBAAiB;AAE/C,MAAMC,kBAAkBC,QAAQC,GAAG,CAACC,oBAAoB;AAaxD,wEAAwE,GACxE,OAAO,SAASC,2BAA2BC,MAAgC;IACzE,OAAO,eAAeC,qBACpBC,KAAa,EACbC,IAAY;QAEZ,MAAMC,UAAU,MAAMX,WAAW;YAAEO;QAAO;QAC1C,MAAMK,MAAM,MAAMD,QAAQE,IAAI,CAAC;YAC7BC,YAAY;YACZC,OAAO;YACPN;YACAO,gBAAgB;YAChBN;QACF;QACA,OAAO;YACLO,aAAaL,IAAIK,WAAW,IAAI;YAChCC,OAAON,IAAIO,SAAS,IAAI;YACxBC,OAAOR,IAAIS,IAAI,CAACC,GAAG,CAAC,CAACC,IAAY,CAAA;oBAC/BC,IAAID,EAAEC,EAAE;oBACRC,YAAYF,EAAEE,UAAU;gBAC1B,CAAA;QACF;IACF;AACF;AAEA,qFAAqF;AACrF,yEAAyE;AACzE;;;;CAIC,GAED;;;;CAIC,GACD,OAAO,SAASC,wBAAwBnB,MAAgC;IACtE,OAAO,eAAeoB,kBAAkBC,cAA8B;QACpE,MAAMjB,UAAU,MAAMX,WAAW;YAAEO;QAAO;QAE1C,iCAAiC;QACjC,MAAMsB,WAAW,MAAMlB,QAAQE,IAAI,CAAC;YAClCC,YAAY;YACZL,OAAO;YACPqB,OAAO;gBAAEL,YAAY;oBAAEM,QAAQH,eAAeJ,EAAE;gBAAC;YAAE;QACrD;QACA,IAAIK,SAASR,IAAI,CAACW,MAAM,EAAE;YACxB;QACF;QAEA,MAAMC,SAAS;YAAEC,IAAI;YAAUC,QAAQP,eAAeJ,EAAE;QAAC,EAAE,6BAA6B;;QACxF,MAAMY,QAAQnC,cAAcgC,QAAQ/B;QAEpC,MAAMS,QAAQ0B,MAAM,CAAC;YACnBvB,YAAY;YACZwB,SAAS;gBAAEL;gBAAQG;YAAM;YACzBG,MAAM;gBACJC,MAAMZ,eAAeY,IAAI,IAAI;gBAC7Bf,YAAYG,eAAeJ,EAAE;YAC/B;YACAR,gBAAgB;QAClB;IACF;AACF;AAEA,gDAAgD;AAChD,OAAO,SAASyB,4BAA4BlC,MAAgC;IAC1E,OAAO,eAAemC,sBAAsBC,gBAAwB;QAClE,MAAMhC,UAAU,MAAMX,WAAW;YAAEO;QAAO;QAE1C,MAAMsB,WAAW,MAAMlB,QAAQE,IAAI,CAAC;YAClCC,YAAY;YACZL,OAAO;YACPqB,OAAO;gBAAEL,YAAY;oBAAEM,QAAQY;gBAAiB;YAAE;QACpD;QACA,IAAI,CAACd,SAASR,IAAI,CAACW,MAAM,EAAE;YACzB;QACF;QAEA,MAAMC,SAAS;YAAEC,IAAI;YAAUC,QAAQQ;QAAiB;QACxD,MAAMP,QAAQnC,cAAcgC,QAAQ/B;QAEpC,MAAMS,QAAQiC,MAAM,CAAC;YACnBpB,IAAIK,SAASR,IAAI,CAAC,EAAE,CAACG,EAAE;YACvBV,YAAY;YACZwB,SAAS;gBAAEL;gBAAQG;YAAM;YACzBpB,gBAAgB;QAClB;IACF;AACF;AAEA,oEAAoE;AACpE,OAAO,SAAS6B,gCAAgCtC,MAAgC;IAC9E,OAAO,eAAeuC,0BAA0BC,aAA8B,EAAEC,IAAY;QAC1F,MAAMrC,UAAU,MAAMX,WAAW;YAAEO;QAAO;QAC1C,MAAMI,QAAQsC,MAAM,CAAC;YACnBzB,IAAIuB;YACJjC,YAAY;YACZyB,MAAM;gBAAEd,YAAYuB;YAAK;YACzBhC,gBAAgB;QAClB;IACF;AACF"}