payload-better-auth 1.0.9 → 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.
- package/README.md +111 -174
- package/dist/better-auth/databaseHooks.js +1 -1
- package/dist/better-auth/databaseHooks.js.map +1 -1
- package/dist/better-auth/plugin.d.ts +1 -1
- package/dist/better-auth/plugin.js +3 -3
- package/dist/better-auth/plugin.js.map +1 -1
- package/dist/better-auth/reconcile-queue.d.ts +1 -1
- package/dist/better-auth/reconcile-queue.js.map +1 -1
- package/dist/better-auth/sources.js +1 -1
- package/dist/better-auth/sources.js.map +1 -1
- package/dist/collections/Users/index.js +1 -1
- package/dist/collections/Users/index.js.map +1 -1
- package/dist/components/BetterAuthLoginServer.d.ts +19 -6
- package/dist/components/BetterAuthLoginServer.js +24 -8
- package/dist/components/BetterAuthLoginServer.js.map +1 -1
- package/dist/components/EmailPasswordFormClient.d.ts +1 -1
- package/dist/components/EmailPasswordFormClient.js.map +1 -1
- package/dist/exports/client.d.ts +1 -1
- package/dist/exports/client.js +1 -1
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/rsc.d.ts +1 -1
- package/dist/exports/rsc.js +1 -1
- package/dist/exports/rsc.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/payload/plugin.d.ts +21 -2
- package/dist/payload/plugin.js +34 -16
- package/dist/payload/plugin.js.map +1 -1
- package/dist/utils/payload-reconcile.js +2 -6
- package/dist/utils/payload-reconcile.js.map +1 -1
- package/package.json +137 -63
- package/src/better-auth/crypto-shared.ts +169 -0
- package/src/better-auth/databaseHooks.ts +30 -0
- package/src/better-auth/helpers.ts +3 -0
- package/src/better-auth/plugin.ts +214 -0
- package/src/better-auth/reconcile-queue.ts +401 -0
- package/src/better-auth/sources.ts +123 -0
- package/src/collections/Users/index.ts +148 -0
- package/src/components/BetterAuthLoginServer.tsx +154 -0
- package/src/components/EmailPasswordFormClient.tsx +204 -0
- package/src/components/VerifyEmailInfoViewClient.tsx +62 -0
- package/src/exports/client.ts +1 -0
- package/src/exports/rsc.ts +1 -0
- package/src/index.ts +9 -0
- package/src/payload/plugin.ts +163 -0
- package/src/utils/payload-reconcile.ts +50 -0
package/README.md
CHANGED
|
@@ -1,225 +1,162 @@
|
|
|
1
|
-
#
|
|
1
|
+
# payload-better-auth
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A Payload CMS plugin that integrates [Better Auth](https://better-auth.com) for seamless user authentication and management.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
- And some JavaScript/Typescript experience
|
|
13
|
+
## Installation
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
19
|
+
**Requirements:** Node.js 18.20.2+, Better Auth 1.4.10+, Payload CMS 3.37.0+
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
## Quick Start
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
### 1. Configure Better Auth
|
|
26
24
|
|
|
27
|
-
```
|
|
28
|
-
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
62
|
+
### 3. Set Environment Variables
|
|
97
63
|
|
|
98
|
-
|
|
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
|
-
|
|
73
|
+
## Documentation
|
|
101
74
|
|
|
102
|
-
|
|
75
|
+
For detailed configuration options, API endpoints, architecture details, and production considerations, see the **[MANUAL.md](./MANUAL.md)**.
|
|
103
76
|
|
|
104
|
-
|
|
77
|
+
## Development
|
|
105
78
|
|
|
106
|
-
|
|
79
|
+
### Getting Started
|
|
107
80
|
|
|
108
|
-
|
|
81
|
+
```bash
|
|
82
|
+
# Install dependencies
|
|
83
|
+
pnpm install
|
|
109
84
|
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
117
|
-
|
|
88
|
+
# Start development server
|
|
89
|
+
pnpm dev
|
|
118
90
|
```
|
|
119
91
|
|
|
120
|
-
|
|
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
|
-
|
|
94
|
+
### Git Hooks
|
|
125
95
|
|
|
126
|
-
|
|
96
|
+
This project uses [Husky](https://typicode.github.io/husky/) for Git hooks:
|
|
127
97
|
|
|
128
|
-
|
|
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
|
-
|
|
102
|
+
When you're happy with your changes, just commit — the build is handled for you!
|
|
131
103
|
|
|
132
|
-
|
|
104
|
+
### Versioning & Releases
|
|
133
105
|
|
|
134
|
-
|
|
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
|
-
|
|
108
|
+
Versions are determined automatically from your commit messages:
|
|
142
109
|
|
|
143
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
122
|
+
#### Installing Specific Versions
|
|
158
123
|
|
|
159
|
-
```
|
|
160
|
-
|
|
124
|
+
```bash
|
|
125
|
+
# Latest
|
|
126
|
+
pnpm add github:benjaminpreiss/payload-better-auth
|
|
161
127
|
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
132
|
+
### Available Scripts
|
|
170
133
|
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
162
|
+
MIT
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createDeleteUserFromPayload, createSyncUserToPayload } from './sources
|
|
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
|
|
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
|
|
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
|
|
5
|
-
import { Queue } from './reconcile-queue
|
|
6
|
-
import { createDeleteUserFromPayload, createListPayloadUsersPage, createSyncUserToPayload } from './sources
|
|
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
|
|
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
|
|
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
|
|
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"}
|