payload-better-auth 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ To build your own Payload plugin, all you need is:
13
13
 
14
14
  1. Install: `pnpm i`
15
15
  2. Generate import map for payload: `cd packages/plugins/dev && pnpx payload generate:importmap`
16
- 3. Initialize / migrate the better-auth database: `pnpx @better-auth/cli migrate --yes` (Also from `dev` directory)
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
17
  4. Run dev: `pnpm dev`
18
18
 
19
19
  ## Background
@@ -7,6 +7,7 @@ export declare const payloadBetterAuthPlugin: (opts: {
7
7
  overwrite?: boolean;
8
8
  user: CreateAdminsUser;
9
9
  }[];
10
+ enableLogging?: boolean;
10
11
  payloadConfig: Promise<SanitizedConfig>;
11
12
  token: string;
12
13
  } & InitOptions) => BetterAuthPlugin;
@@ -1,5 +1,6 @@
1
1
  // src/plugins/reconcile-queue-plugin.ts
2
- import { APIError, createAuthEndpoint } from 'better-auth/api';
2
+ import { APIError } from 'better-auth/api';
3
+ import { createAuthEndpoint, createAuthMiddleware } from 'better-auth/plugins';
3
4
  import { createDatabaseHooks } from './databaseHooks.js';
4
5
  import { Queue } from './reconcile-queue.js';
5
6
  import { createDeleteUserFromPayload, createListPayloadUsersPage, createSyncUserToPayload } from './sources.js';
@@ -89,6 +90,37 @@ export const payloadBetterAuthPlugin = (opts)=>{
89
90
  });
90
91
  })
91
92
  },
93
+ hooks: {
94
+ before: [
95
+ {
96
+ handler: createAuthMiddleware(async (ctx)=>{
97
+ const locale = ctx.getHeader('User-Locale');
98
+ return Promise.resolve({
99
+ context: {
100
+ ...ctx,
101
+ body: {
102
+ ...ctx.body,
103
+ locale: locale ?? undefined
104
+ }
105
+ }
106
+ });
107
+ }),
108
+ matcher: (context)=>{
109
+ return context.path === '/sign-up/email';
110
+ }
111
+ }
112
+ ]
113
+ },
114
+ schema: {
115
+ user: {
116
+ fields: {
117
+ locale: {
118
+ type: 'string',
119
+ required: false
120
+ }
121
+ }
122
+ }
123
+ },
92
124
  // TODO: the queue must be destroyed on better auth instance destruction, as it utilizes timers.
93
125
  async init ({ internalAdapter, password }) {
94
126
  if (opts.createAdmins) {
@@ -125,14 +157,16 @@ export const payloadBetterAuthPlugin = (opts)=>{
125
157
  }
126
158
  }));
127
159
  } catch (error) {
128
- defaultLog('Failed to create Admin user', error);
160
+ if (opts.enableLogging) {
161
+ defaultLog('Failed to create Admin user', error);
162
+ }
129
163
  }
130
164
  }
131
165
  const queue = new Queue({
132
166
  deleteUserFromPayload: createDeleteUserFromPayload(opts.payloadConfig),
133
167
  internalAdapter,
134
168
  listPayloadUsersPage: createListPayloadUsersPage(opts.payloadConfig),
135
- log: defaultLog,
169
+ log: opts.enableLogging ? defaultLog : undefined,
136
170
  syncUserToPayload: createSyncUserToPayload(opts.payloadConfig)
137
171
  }, opts);
138
172
  return {
@@ -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, createAuthEndpoint } from 'better-auth/api'\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?: any) => {\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 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: string[] = []\n\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('emailAndPassword')\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 // 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 defaultLog('Failed to create Admin user', error)\n }\n }\n\n const queue = new Queue(\n {\n deleteUserFromPayload: createDeleteUserFromPayload(opts.payloadConfig),\n internalAdapter,\n listPayloadUsersPage: createListPayloadUsersPage(opts.payloadConfig),\n log: defaultLog,\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","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","deleteNow","body","catch","baId","enqueueDelete","ensureNow","user","enqueueEnsure","init","internalAdapter","password","createAdmins","all","map","overwrite","alreadyExistingUser","findUserByEmail","email","deleteAccounts","createdUser","updateUser","role","linkAccount","accountId","hash","providerId","userId","createUser","error","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 { 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: string[] = []\n\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('emailAndPassword')\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","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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"}
@@ -33,7 +33,7 @@ export class Queue {
33
33
  if (opts?.runOnBoot ?? true) {
34
34
  // Use setTimeout instead of queueMicrotask to give more time for initialization
35
35
  setTimeout(()=>{
36
- this.seedFullReconcile().catch((err)=>console.error('[reconcile] seed failed', err));
36
+ this.seedFullReconcile().catch((err)=>this.deps.log && this.deps.log('[reconcile] seed failed', err));
37
37
  }, 2000); // 2 second delay to allow Better Auth and Payload to fully initialize
38
38
  }
39
39
  log('Bootstrap process completed');
@@ -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((err) => console.error('[reconcile] seed failed', err))\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","console","error","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","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.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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"}
@@ -13,6 +13,7 @@
13
13
  const reconcileUrl = `${betterAuthUrl}/api/auth/reconcile/run`;
14
14
  payload.logger.info('Triggering full reconcile from Payload onInit...');
15
15
  const response = await fetch(reconcileUrl, {
16
+ body: JSON.stringify({}),
16
17
  headers: {
17
18
  'Content-Type': 'application/json',
18
19
  'x-reconcile-token': reconcileToken
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/payload-reconcile.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\n/**\n * Triggers a full reconcile operation via the Better Auth reconcile API\n * This is typically called during Payload initialization to ensure data consistency\n */\nexport async function triggerFullReconcile(payload: Payload): Promise<void> {\n try {\n const reconcileToken = process.env.RECONCILE_TOKEN\n if (!reconcileToken) {\n payload.logger.warn('RECONCILE_TOKEN not set, skipping onInit reconcile trigger')\n return\n }\n\n // Determine the better-auth server URL\n const betterAuthUrl = process.env.BETTER_AUTH_URL || 'http://localhost:3000'\n const reconcileUrl = `${betterAuthUrl}/api/auth/reconcile/run`\n\n payload.logger.info('Triggering full reconcile from Payload onInit...')\n\n const response = await fetch(reconcileUrl, {\n headers: {\n 'Content-Type': 'application/json',\n 'x-reconcile-token': reconcileToken,\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new Error(`Reconcile request failed: ${response.status} ${response.statusText}`)\n }\n\n const result = await response.json()\n payload.logger.info('Full reconcile triggered successfully from Payload onInit', { result })\n } catch (error) {\n payload.logger.error('Failed to trigger full reconcile from Payload onInit', { error })\n // Don't throw - we don't want to prevent Payload from starting if reconcile fails\n }\n}\n"],"names":["triggerFullReconcile","payload","reconcileToken","process","env","RECONCILE_TOKEN","logger","warn","betterAuthUrl","BETTER_AUTH_URL","reconcileUrl","info","response","fetch","headers","method","ok","Error","status","statusText","result","json","error"],"mappings":"AAEA;;;CAGC,GACD,OAAO,eAAeA,qBAAqBC,OAAgB;IACzD,IAAI;QACF,MAAMC,iBAAiBC,QAAQC,GAAG,CAACC,eAAe;QAClD,IAAI,CAACH,gBAAgB;YACnBD,QAAQK,MAAM,CAACC,IAAI,CAAC;YACpB;QACF;QAEA,uCAAuC;QACvC,MAAMC,gBAAgBL,QAAQC,GAAG,CAACK,eAAe,IAAI;QACrD,MAAMC,eAAe,GAAGF,cAAc,uBAAuB,CAAC;QAE9DP,QAAQK,MAAM,CAACK,IAAI,CAAC;QAEpB,MAAMC,WAAW,MAAMC,MAAMH,cAAc;YACzCI,SAAS;gBACP,gBAAgB;gBAChB,qBAAqBZ;YACvB;YACAa,QAAQ;QACV;QAEA,IAAI,CAACH,SAASI,EAAE,EAAE;YAChB,MAAM,IAAIC,MAAM,CAAC,0BAA0B,EAAEL,SAASM,MAAM,CAAC,CAAC,EAAEN,SAASO,UAAU,EAAE;QACvF;QAEA,MAAMC,SAAS,MAAMR,SAASS,IAAI;QAClCpB,QAAQK,MAAM,CAACK,IAAI,CAAC,6DAA6D;YAAES;QAAO;IAC5F,EAAE,OAAOE,OAAO;QACdrB,QAAQK,MAAM,CAACgB,KAAK,CAAC,wDAAwD;YAAEA;QAAM;IACrF,kFAAkF;IACpF;AACF"}
1
+ {"version":3,"sources":["../../src/utils/payload-reconcile.ts"],"sourcesContent":["import type { Payload } from 'payload'\n\n/**\n * Triggers a full reconcile operation via the Better Auth reconcile API\n * This is typically called during Payload initialization to ensure data consistency\n */\nexport async function triggerFullReconcile(payload: Payload): Promise<void> {\n try {\n const reconcileToken = process.env.RECONCILE_TOKEN\n if (!reconcileToken) {\n payload.logger.warn('RECONCILE_TOKEN not set, skipping onInit reconcile trigger')\n return\n }\n\n // Determine the better-auth server URL\n const betterAuthUrl = process.env.BETTER_AUTH_URL || 'http://localhost:3000'\n const reconcileUrl = `${betterAuthUrl}/api/auth/reconcile/run`\n\n payload.logger.info('Triggering full reconcile from Payload onInit...')\n\n const response = await fetch(reconcileUrl, {\n body: JSON.stringify({}),\n headers: {\n 'Content-Type': 'application/json',\n 'x-reconcile-token': reconcileToken,\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new Error(`Reconcile request failed: ${response.status} ${response.statusText}`)\n }\n\n const result = await response.json()\n payload.logger.info('Full reconcile triggered successfully from Payload onInit', { result })\n } catch (error) {\n payload.logger.error('Failed to trigger full reconcile from Payload onInit', { error })\n // Don't throw - we don't want to prevent Payload from starting if reconcile fails\n }\n}\n"],"names":["triggerFullReconcile","payload","reconcileToken","process","env","RECONCILE_TOKEN","logger","warn","betterAuthUrl","BETTER_AUTH_URL","reconcileUrl","info","response","fetch","body","JSON","stringify","headers","method","ok","Error","status","statusText","result","json","error"],"mappings":"AAEA;;;CAGC,GACD,OAAO,eAAeA,qBAAqBC,OAAgB;IACzD,IAAI;QACF,MAAMC,iBAAiBC,QAAQC,GAAG,CAACC,eAAe;QAClD,IAAI,CAACH,gBAAgB;YACnBD,QAAQK,MAAM,CAACC,IAAI,CAAC;YACpB;QACF;QAEA,uCAAuC;QACvC,MAAMC,gBAAgBL,QAAQC,GAAG,CAACK,eAAe,IAAI;QACrD,MAAMC,eAAe,GAAGF,cAAc,uBAAuB,CAAC;QAE9DP,QAAQK,MAAM,CAACK,IAAI,CAAC;QAEpB,MAAMC,WAAW,MAAMC,MAAMH,cAAc;YACzCI,MAAMC,KAAKC,SAAS,CAAC,CAAC;YACtBC,SAAS;gBACP,gBAAgB;gBAChB,qBAAqBf;YACvB;YACAgB,QAAQ;QACV;QAEA,IAAI,CAACN,SAASO,EAAE,EAAE;YAChB,MAAM,IAAIC,MAAM,CAAC,0BAA0B,EAAER,SAASS,MAAM,CAAC,CAAC,EAAET,SAASU,UAAU,EAAE;QACvF;QAEA,MAAMC,SAAS,MAAMX,SAASY,IAAI;QAClCvB,QAAQK,MAAM,CAACK,IAAI,CAAC,6DAA6D;YAAEY;QAAO;IAC5F,EAAE,OAAOE,OAAO;QACdxB,QAAQK,MAAM,CAACmB,KAAK,CAAC,wDAAwD;YAAEA;QAAM;IACrF,kFAAkF;IACpF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-better-auth",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A blank template to get started with Payload 3.0",
5
5
  "license": "MIT",
6
6
  "type": "module",