better-auth-lead 0.4.3 → 0.5.0

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
@@ -56,6 +56,8 @@ const authClient = createAuthClient({
56
56
 
57
57
  ### Subscribe
58
58
 
59
+ Provide an `email` to subscribe an anonymous lead:
60
+
59
61
  ```ts
60
62
  // POST /lead/subscribe
61
63
  const { data, error } = await authClient.lead.subscribe({
@@ -67,6 +69,19 @@ const { data, error } = await authClient.lead.subscribe({
67
69
  });
68
70
  ```
69
71
 
72
+ Or omit `email` to subscribe the currently authenticated user. The lead is associated to the session user's `id` (a valid session cookie is required):
73
+
74
+ ```ts
75
+ // POST /lead/subscribe
76
+ const { data, error } = await authClient.lead.subscribe({
77
+ metadata: {
78
+ preferences: 'engineering',
79
+ },
80
+ });
81
+ ```
82
+
83
+ If neither `email` nor an active session is provided, the endpoint responds with `400 Bad Request` (`EMAIL_OR_SESSION_REQUIRED`).
84
+
70
85
  ### Verify
71
86
 
72
87
  ```ts
@@ -80,7 +95,7 @@ await authClient.lead.verify({
80
95
 
81
96
  ### Unsubscribe
82
97
 
83
- The unsubscribe endpoint is designed for [RFC 8058](https://www.rfc-editor.org/rfc/rfc8058) one-click unsubscribe. The signed `token` is embedded in the `unsubscribeUrl` provided to `sendVerificationEmail` and should be used in `List-Unsubscribe` email headers — email clients (Gmail, Apple Mail, Yahoo Mail) will POST to this URL automatically when the user clicks "Unsubscribe".
98
+ The unsubscribe endpoint is designed for [RFC 8058](https://www.rfc-editor.org/rfc/rfc8058) one-click unsubscribe. The signed `token` is embedded in the `unsubscribeUrl` provided to `sendConfirmationEmail` and should be used in `List-Unsubscribe` email headers — email clients (Gmail, Apple Mail, Yahoo Mail) will POST to this URL automatically when the user clicks "Unsubscribe".
84
99
 
85
100
  ```ts
86
101
  // POST /lead/unsubscribe?token=<signed-token>
@@ -89,8 +104,17 @@ const { data, error } = await authClient.lead.unsubscribe({
89
104
  });
90
105
  ```
91
106
 
107
+ For an authenticated user (e.g. from a "Manage preferences" page in your app), use the session-based endpoint. It requires a valid session and deletes the lead associated with the session user's `id`:
108
+
109
+ ```ts
110
+ // POST /lead/unsubscribe-session
111
+ const { data, error } = await authClient.lead.unsubscribeSession();
112
+ ```
113
+
92
114
  ### Resend
93
115
 
116
+ Resend the confirmation email by `email`:
117
+
94
118
  ```ts
95
119
  // POST /lead/resend
96
120
  const { data, error } = await authClient.lead.resend({
@@ -98,25 +122,82 @@ const { data, error } = await authClient.lead.resend({
98
122
  });
99
123
  ```
100
124
 
125
+ Or omit `email` to resend for the currently authenticated user (lead is looked up by the session user's `id`):
126
+
127
+ ```ts
128
+ // POST /lead/resend
129
+ const { data, error } = await authClient.lead.resend();
130
+ ```
131
+
132
+ If neither `email` nor an active session is provided, the endpoint responds with `400 Bad Request` (`EMAIL_OR_SESSION_REQUIRED`).
133
+
101
134
  ### Update
102
135
 
136
+ Update the metadata of the lead associated with the currently authenticated user. Requires a valid session — the lead is looked up by the session user's `id`:
137
+
103
138
  ```ts
104
139
  // POST /lead/update
105
140
  const { data, error } = await authClient.lead.update({
106
- id: 'lead-id',
107
141
  metadata: {
108
142
  preferences: 'ai',
109
143
  },
110
144
  });
111
145
  ```
112
146
 
113
- ### Email Verification
147
+ If no session is present the endpoint responds with `401 Unauthorized`.
148
+
149
+ ### List (admin)
150
+
151
+ Optional admin endpoint to list all leads. Requires the better-auth [`admin`](https://www.better-auth.com/docs/plugins/admin) plugin to be registered, and must be opted in via `admin.enabled`:
152
+
153
+ ```ts
154
+ // server/auth.ts
155
+ import { betterAuth } from 'better-auth';
156
+ import { admin } from 'better-auth/plugins';
157
+ import { lead } from 'better-auth-lead';
158
+
159
+ export const auth = betterAuth({
160
+ plugins: [
161
+ admin(),
162
+ lead({
163
+ admin: {
164
+ enabled: true,
165
+ // Optional. Roles allowed to call /lead/list. Default: ['admin'].
166
+ // Checked against session.user.role (admin plugin supports
167
+ // comma-separated roles).
168
+ roles: ['admin', 'editor'],
169
+ },
170
+ }),
171
+ ],
172
+ });
173
+ ```
174
+
175
+ ```ts
176
+ // GET /lead/list?limit=100&offset=0
177
+ const { data, error } = await authClient.lead.list({
178
+ query: {
179
+ limit: 100, // optional, default 100, max 1000
180
+ offset: 0, // optional, default 0
181
+ },
182
+ });
183
+ ```
184
+
185
+ The response includes the page of `leads`, the `total` number of leads in the database, and the resolved `limit` and `offset` for client-side pagination.
186
+
187
+ Responses:
114
188
 
115
- To enable email verification, you need to pass a function that sends a verification email with a link. The `sendVerificationEmail` takes a data object with the following properties:
189
+ - `404 Not Found` (`ADMIN_PLUGIN_REQUIRED`) if the admin plugin is not registered.
190
+ - `403 Forbidden` (`FORBIDDEN`) if the session user's role is not in `admin.roles`.
191
+ - `401 Unauthorized` if no session is present.
192
+
193
+ ### Email Confirmation
194
+
195
+ To enable double opt-in email confirmation, pass a `sendConfirmationEmail` function. It receives a data object with:
116
196
 
117
197
  - `lead`: The lead object.
118
- - `url`: The URL to send to the user which contains the token.
119
- - `token`: A verification token used to complete the email verification.
198
+ - `email`: The lead's email address.
199
+ - `url`: The URL containing the confirmation token to send to the user.
200
+ - `token`: The confirmation token used to complete the verification.
120
201
  - `unsubscribeUrl`: The endpoint URL for one-click unsubscribe (RFC 8058). Use this in `List-Unsubscribe` email headers.
121
202
 
122
203
  and a `request` object as the second parameter.
@@ -130,22 +211,22 @@ import { sendEmail } from './email'; // your email sending function
130
211
  export const auth = betterAuth({
131
212
  plugins: [
132
213
  lead({
133
- sendVerificationEmail: async ({ lead, url, token, unsubscribeUrl }) => {
134
- const { verificationEmailSentAt } = lead;
214
+ sendConfirmationEmail: async ({ lead, email, url, token, unsubscribeUrl }) => {
215
+ const { confirmationSentAt } = lead;
135
216
  if (
136
- verificationEmailSentAt &&
137
- Date.now() - verificationEmailSentAt.getTime() < 60 * 1000 // 1 minute
217
+ confirmationSentAt &&
218
+ Date.now() - confirmationSentAt.getTime() < 60 * 1000 // 1 minute
138
219
  ) {
139
220
  console.log(
140
- `Skipping sending verification email to ${lead.email} because a recent email was already sent.`,
221
+ `Skipping sending confirmation email to ${email} because a recent email was already sent.`,
141
222
  );
142
223
  return false;
143
224
  }
144
225
 
145
226
  void sendEmail({
146
- to: lead.email,
147
- subject: 'Newsletter: Verify your email address',
148
- text: `Click the link to verify your email: ${url}`,
227
+ to: email,
228
+ subject: 'Newsletter: Confirm your subscription',
229
+ text: `Click the link to confirm your subscription: ${url}`,
149
230
  // One-click unsubscribe headers (RFC 8058)
150
231
  // Supported by Gmail, Apple Mail, and Yahoo Mail.
151
232
  headers: {
@@ -156,9 +237,9 @@ export const auth = betterAuth({
156
237
 
157
238
  return true;
158
239
  },
159
- onEmailVerified: async ({ lead }) => {
160
- // do something when a lead's email is verified
161
- console.log(`Lead ${lead.email} has been verified!`);
240
+ onConfirmed: async ({ lead }) => {
241
+ // do something when a lead confirms their subscription
242
+ console.log(`Lead ${lead} has confirmed their subscription!`);
162
243
  },
163
244
  }),
164
245
  ],
@@ -167,7 +248,7 @@ export const auth = betterAuth({
167
248
 
168
249
  > Avoid awaiting the email sending to prevent timing attacks.
169
250
 
170
- Additionally, you can provide an `onEmailVerified` callback to execute logic after a lead's email is verified.
251
+ Additionally, you can provide an `onConfirmed` callback to execute logic after a lead confirms their subscription.
171
252
 
172
253
  ### Metadata Validation
173
254
 
@@ -218,6 +299,11 @@ await authClient.lead.subscribe({
218
299
  email: 'user@example.com',
219
300
  metadata: { preferences: 'engineering' },
220
301
  });
302
+
303
+ // or for the currently authenticated user (omit email)
304
+ await authClient.lead.subscribe({
305
+ metadata: { preferences: 'engineering' },
306
+ });
221
307
  ```
222
308
 
223
309
  ## Schema
@@ -226,29 +312,33 @@ await authClient.lead.subscribe({
226
312
 
227
313
  Table name: `lead`
228
314
 
229
- |  Field |  Type |  Key |  Description |
230
- | ----------------------- | ------- | ------ | ------------------------------------------------- |
231
- | id | string | pk | Unique identifier for each lead |
232
- | email | string | unique | Email address of the lead |
233
- | emailVerified | boolean | | Whether the email is verified |
234
- | verificationEmailSentAt | Date | ? | Timestamp of when the verification email was sent |
235
- | metadata | json | ? | Additional data about the lead |
236
- | createdAt | date | | Timestamp of lead creation |
237
- | updatedAt | date | | Timestamp of last update |
315
+ | Field | Type | Key | Description |
316
+ | ------------------ | ------- | ------ | ------------------------------------------------- |
317
+ | id | string | pk | Unique identifier for each lead |
318
+ | email | string? | unique | Email address of the lead (optional) |
319
+ | userId | string? | unique | ID of an associated better-auth user (optional) |
320
+ | confirmed | boolean | | Whether the lead has confirmed their subscription |
321
+ | confirmationSentAt | Date | ? | Timestamp of when the confirmation email was sent |
322
+ | metadata | json | ? | Additional data about the lead |
323
+ | createdAt | date | | Timestamp of lead creation |
324
+ | updatedAt | date | | Timestamp of last update |
238
325
 
239
326
  #### Prisma
240
327
 
241
328
  ```prisma
242
329
  model Lead {
243
- id String @id
244
- createdAt DateTime @default(now())
245
- updatedAt DateTime @updatedAt
246
- email String
247
- emailVerified Boolean @default(false)
248
- verificationEmailSentAt DateTime?
249
- metadata String?
330
+ id String @id
331
+ createdAt DateTime @default(now())
332
+ updatedAt DateTime @updatedAt
333
+ email String?
334
+ userId String?
335
+ confirmed Boolean @default(false)
336
+ confirmationSentAt DateTime?
337
+ metadata String?
338
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
250
339
 
251
340
  @@unique([email])
341
+ @@unique([userId])
252
342
  @@map("lead")
253
343
  }
254
344
  ```
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as LeadOptions, t as lead } from "./index-P6m89iLp.mjs";
1
+ import { r as LeadOptions, t as lead } from "./index-DOhc8vla.mjs";
2
2
  import { StandardSchemaV1 } from "better-auth";
3
3
 
4
4
  //#region src/client.d.ts
@@ -19,16 +19,25 @@ declare const lead$1: {
19
19
  };
20
20
  email: {
21
21
  type: "string";
22
- required: true;
22
+ required: false;
23
23
  unique: true;
24
24
  };
25
- emailVerified: {
25
+ userId: {
26
+ type: "string";
27
+ required: false;
28
+ unique: true;
29
+ references: {
30
+ model: string;
31
+ field: string;
32
+ };
33
+ };
34
+ confirmed: {
26
35
  type: "boolean";
27
36
  defaultValue: false;
28
37
  required: true;
29
38
  input: false;
30
39
  };
31
- verificationEmailSentAt: {
40
+ confirmationSentAt: {
32
41
  type: "date";
33
42
  required: false;
34
43
  input: false;
@@ -48,30 +57,32 @@ interface LeadOptions {
48
57
  * @param data the data object
49
58
  * @param request the request object
50
59
  */
51
- sendVerificationEmail?: (
60
+ sendConfirmationEmail?: (
52
61
  /**
53
- * @param lead the lead to send the verification email to
54
- * @param url the verification url
55
- * @param token the verification token
62
+ * @param lead the lead to send the confirmation email to
63
+ * @param email the email address to send the confirmation to
64
+ * @param url the confirmation url
65
+ * @param token the confirmation token
56
66
  * @param unsubscribeUrl the one-click unsubscribe URL (RFC 8058) to include in List-Unsubscribe headers
57
67
  */
58
68
 
59
69
  data: {
60
70
  lead: Lead;
71
+ email: string;
61
72
  url: string;
62
73
  token: string;
63
74
  unsubscribeUrl: string;
64
75
  }, request?: Request) => Promise<boolean>;
65
- onEmailVerified?: (
76
+ onConfirmed?: (
66
77
  /**
67
- * @param lead the lead that was verified
78
+ * @param lead the lead that confirmed their subscription
68
79
  */
69
80
 
70
81
  data: {
71
82
  lead: Lead;
72
83
  }, request?: Request) => Promise<void>;
73
84
  /**
74
- * Number of seconds the verification token is
85
+ * Number of seconds the confirmation token is
75
86
  * valid for.
76
87
  * @default 3600 seconds (1 hour)
77
88
  */
@@ -105,6 +116,25 @@ interface LeadOptions {
105
116
  metadata?: {
106
117
  validationSchema?: StandardSchemaV1;
107
118
  };
119
+ /**
120
+ * Admin-only endpoints. Requires the better-auth `admin` plugin to be
121
+ * registered. When enabled, exposes `GET /lead/list` which returns all
122
+ * leads to users whose role matches `admin.roles`.
123
+ */
124
+ admin?: {
125
+ /**
126
+ * Enable admin endpoints (e.g. `/lead/list`).
127
+ * @default false
128
+ */
129
+ enabled?: boolean;
130
+ /**
131
+ * Roles allowed to call admin endpoints. The check is performed against
132
+ * `session.user.role` (added by the admin plugin), which may contain a
133
+ * comma-separated list of roles.
134
+ * @default ['admin']
135
+ */
136
+ roles?: string[];
137
+ };
108
138
  }
109
139
  interface Lead {
110
140
  /**
@@ -113,12 +143,17 @@ interface Lead {
113
143
  id: string;
114
144
  createdAt: Date;
115
145
  updatedAt: Date;
116
- email: string;
117
- emailVerified: boolean;
118
- verificationEmailSentAt: Date | null;
146
+ email: string | null;
147
+ userId: string | null;
148
+ confirmed: boolean;
149
+ confirmationSentAt: Date | null;
119
150
  metadata?: string;
120
151
  }
121
- type LeadPayload = Omit<Lead, 'id' | 'createdAt' | 'updatedAt' | 'emailVerified' | 'verificationEmailSentAt'>;
152
+ type LeadPayload = {
153
+ email?: string | null;
154
+ userId?: string | null;
155
+ metadata?: string;
156
+ };
122
157
  //#endregion
123
158
  //#region src/index.d.ts
124
159
  declare const lead: <O extends LeadOptions>(options?: O) => {
@@ -141,16 +176,25 @@ declare const lead: <O extends LeadOptions>(options?: O) => {
141
176
  };
142
177
  email: {
143
178
  type: "string";
144
- required: true;
179
+ required: false;
145
180
  unique: true;
146
181
  };
147
- emailVerified: {
182
+ userId: {
183
+ type: "string";
184
+ required: false;
185
+ unique: true;
186
+ references: {
187
+ model: string;
188
+ field: string;
189
+ };
190
+ };
191
+ confirmed: {
148
192
  type: "boolean";
149
193
  defaultValue: false;
150
194
  required: true;
151
195
  input: false;
152
196
  };
153
- verificationEmailSentAt: {
197
+ confirmationSentAt: {
154
198
  type: "date";
155
199
  required: false;
156
200
  input: false;
@@ -163,16 +207,51 @@ declare const lead: <O extends LeadOptions>(options?: O) => {
163
207
  };
164
208
  };
165
209
  endpoints: {
210
+ list?: import("better-auth").StrictEndpoint<"/lead/list", {
211
+ method: "GET";
212
+ query: import("zod").ZodObject<{
213
+ limit: import("zod").ZodOptional<import("zod").ZodCoercedNumber<unknown>>;
214
+ offset: import("zod").ZodOptional<import("zod").ZodCoercedNumber<unknown>>;
215
+ }, import("better-auth").$strip>;
216
+ use: ((inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
217
+ session: {
218
+ session: Record<string, any> & {
219
+ id: string;
220
+ createdAt: Date;
221
+ updatedAt: Date;
222
+ userId: string;
223
+ expiresAt: Date;
224
+ token: string;
225
+ ipAddress?: string | null | undefined;
226
+ userAgent?: string | null | undefined;
227
+ };
228
+ user: Record<string, any> & {
229
+ id: string;
230
+ createdAt: Date;
231
+ updatedAt: Date;
232
+ email: string;
233
+ emailVerified: boolean;
234
+ name: string;
235
+ image?: string | null | undefined;
236
+ };
237
+ };
238
+ }>)[];
239
+ }, {
240
+ leads: Lead[];
241
+ total: number;
242
+ limit: number;
243
+ offset: number;
244
+ }> | undefined;
166
245
  subscribe: import("better-auth").StrictEndpoint<"/lead/subscribe", {
167
246
  method: "POST";
168
247
  body: import("zod").ZodObject<{
169
- email: import("zod").ZodString;
248
+ email: import("zod").ZodOptional<import("zod").ZodString>;
170
249
  metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
171
250
  }, import("better-auth").$strip>;
172
251
  metadata: {
173
252
  $Infer: {
174
253
  body: {
175
- email: string;
254
+ email?: string;
176
255
  metadata?: (O extends {
177
256
  metadata: {
178
257
  validationSchema: import("better-auth").StandardSchemaV1<unknown, infer Out>;
@@ -203,10 +282,38 @@ declare const lead: <O extends LeadOptions>(options?: O) => {
203
282
  }, {
204
283
  status: boolean;
205
284
  }>;
285
+ unsubscribeSession: import("better-auth").StrictEndpoint<"/lead/unsubscribe-session", {
286
+ method: "POST";
287
+ use: ((inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
288
+ session: {
289
+ session: Record<string, any> & {
290
+ id: string;
291
+ createdAt: Date;
292
+ updatedAt: Date;
293
+ userId: string;
294
+ expiresAt: Date;
295
+ token: string;
296
+ ipAddress?: string | null | undefined;
297
+ userAgent?: string | null | undefined;
298
+ };
299
+ user: Record<string, any> & {
300
+ id: string;
301
+ createdAt: Date;
302
+ updatedAt: Date;
303
+ email: string;
304
+ emailVerified: boolean;
305
+ name: string;
306
+ image?: string | null | undefined;
307
+ };
308
+ };
309
+ }>)[];
310
+ }, {
311
+ status: boolean;
312
+ }>;
206
313
  resend: import("better-auth").StrictEndpoint<"/lead/resend", {
207
314
  method: "POST";
208
315
  body: import("zod").ZodObject<{
209
- email: import("zod").ZodString;
316
+ email: import("zod").ZodOptional<import("zod").ZodString>;
210
317
  }, import("better-auth").$strip>;
211
318
  }, {
212
319
  status: boolean;
@@ -214,13 +321,34 @@ declare const lead: <O extends LeadOptions>(options?: O) => {
214
321
  update: import("better-auth").StrictEndpoint<"/lead/update", {
215
322
  method: "POST";
216
323
  body: import("zod").ZodObject<{
217
- id: import("zod").ZodString;
218
324
  metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
219
325
  }, import("better-auth").$strip>;
326
+ use: ((inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<{
327
+ session: {
328
+ session: Record<string, any> & {
329
+ id: string;
330
+ createdAt: Date;
331
+ updatedAt: Date;
332
+ userId: string;
333
+ expiresAt: Date;
334
+ token: string;
335
+ ipAddress?: string | null | undefined;
336
+ userAgent?: string | null | undefined;
337
+ };
338
+ user: Record<string, any> & {
339
+ id: string;
340
+ createdAt: Date;
341
+ updatedAt: Date;
342
+ email: string;
343
+ emailVerified: boolean;
344
+ name: string;
345
+ image?: string | null | undefined;
346
+ };
347
+ };
348
+ }>)[];
220
349
  metadata: {
221
350
  $Infer: {
222
351
  body: {
223
- id: string;
224
352
  metadata?: (O extends {
225
353
  metadata: {
226
354
  validationSchema: import("better-auth").StandardSchemaV1<unknown, infer Out>;
@@ -240,12 +368,15 @@ declare const lead: <O extends LeadOptions>(options?: O) => {
240
368
  max: number;
241
369
  }[];
242
370
  $ERROR_CODES: {
371
+ FORBIDDEN: import("better-auth").RawError<"FORBIDDEN">;
243
372
  INVALID_EMAIL: import("better-auth").RawError<"INVALID_EMAIL">;
244
373
  INVALID_TOKEN: import("better-auth").RawError<"INVALID_TOKEN">;
245
374
  TOKEN_EXPIRED: import("better-auth").RawError<"TOKEN_EXPIRED">;
246
375
  INVALID_METADATA: import("better-auth").RawError<"INVALID_METADATA">;
376
+ EMAIL_OR_SESSION_REQUIRED: import("better-auth").RawError<"EMAIL_OR_SESSION_REQUIRED">;
377
+ ADMIN_PLUGIN_REQUIRED: import("better-auth").RawError<"ADMIN_PLUGIN_REQUIRED">;
247
378
  };
248
379
  };
249
380
  //#endregion
250
381
  export { LeadPayload as i, Lead as n, LeadOptions as r, lead as t };
251
- //# sourceMappingURL=index-P6m89iLp.d.mts.map
382
+ //# sourceMappingURL=index-DOhc8vla.d.mts.map
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as LeadPayload, n as Lead, r as LeadOptions, t as lead } from "./index-P6m89iLp.mjs";
1
+ import { i as LeadPayload, n as Lead, r as LeadOptions, t as lead } from "./index-DOhc8vla.mjs";
2
2
  export { type Lead, type LeadOptions, type LeadPayload, lead };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BASE_ERROR_CODES, defineErrorCodes } from "better-auth";
2
- import { APIError, createAuthEndpoint, createEmailVerificationToken } from "better-auth/api";
2
+ import { APIError, createAuthEndpoint, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
3
3
  import { SignJWT, jwtVerify } from "jose";
4
4
  import { JWTExpired } from "jose/errors";
5
5
  import * as z from "zod";
@@ -9,12 +9,15 @@ const LEAD_ERROR_CODES = defineErrorCodes({
9
9
  INVALID_EMAIL: "Invalid email",
10
10
  INVALID_TOKEN: "Invalid token",
11
11
  TOKEN_EXPIRED: "Token expired",
12
- INVALID_METADATA: "Invalid metadata"
12
+ INVALID_METADATA: "Invalid metadata",
13
+ EMAIL_OR_SESSION_REQUIRED: "Email or session is required",
14
+ ADMIN_PLUGIN_REQUIRED: "Admin plugin is required",
15
+ FORBIDDEN: "Forbidden"
13
16
  });
14
17
  //#endregion
15
18
  //#region src/routes.ts
16
19
  const subscribeSchema = z.object({
17
- email: z.string().meta({ description: "Email address of the lead" }),
20
+ email: z.string().optional().meta({ description: "Email address of the lead" }),
18
21
  metadata: z.record(z.string(), z.any()).optional().meta({ description: "Additional metadata to store with the lead" })
19
22
  });
20
23
  const subscribe = (options) => createAuthEndpoint("/lead/subscribe", {
@@ -22,52 +25,76 @@ const subscribe = (options) => createAuthEndpoint("/lead/subscribe", {
22
25
  body: subscribeSchema,
23
26
  metadata: { $Infer: { body: {} } }
24
27
  }, async (ctx) => {
25
- const { email } = ctx.body;
26
- if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
28
+ const email = ctx.body.email;
27
29
  const metadata = validateMetadata(options, ctx.body.metadata, ctx.context.logger);
28
- const normalizedEmail = email.toLowerCase();
30
+ let identifierType;
31
+ let leadIdentifier;
32
+ let leadEmail;
33
+ let createData;
34
+ if (email) {
35
+ if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
36
+ leadIdentifier = email.toLowerCase();
37
+ identifierType = "email";
38
+ leadEmail = leadIdentifier;
39
+ createData = {
40
+ email: leadIdentifier,
41
+ metadata: metadata ? JSON.stringify(metadata) : void 0
42
+ };
43
+ } else {
44
+ const session = await getSessionFromCtx(ctx);
45
+ if (!session) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.EMAIL_OR_SESSION_REQUIRED);
46
+ leadIdentifier = session.user.id;
47
+ identifierType = "user";
48
+ leadEmail = session.user.email;
49
+ createData = {
50
+ userId: leadIdentifier,
51
+ metadata: metadata ? JSON.stringify(metadata) : void 0
52
+ };
53
+ }
54
+ const whereField = identifierType === "email" ? "email" : "userId";
29
55
  let lead = await ctx.context.adapter.findOne({
30
56
  model: "lead",
31
57
  where: [{
32
- field: "email",
33
- value: normalizedEmail
58
+ field: whereField,
59
+ value: leadIdentifier
34
60
  }]
35
61
  });
36
62
  if (!lead) try {
37
63
  lead = await ctx.context.adapter.create({
38
64
  model: "lead",
39
- data: {
40
- email: normalizedEmail,
41
- metadata: metadata ? JSON.stringify(metadata) : void 0
42
- }
65
+ data: createData
43
66
  });
44
67
  } catch (e) {
45
68
  ctx.context.logger.info("Error creating lead");
46
69
  lead = await ctx.context.adapter.findOne({
47
70
  model: "lead",
48
71
  where: [{
49
- field: "email",
50
- value: normalizedEmail
72
+ field: whereField,
73
+ value: leadIdentifier
51
74
  }]
52
75
  });
53
76
  }
54
- if (options.sendVerificationEmail && lead && !lead.emailVerified) {
55
- const token = await createEmailVerificationToken(ctx.context.secret, normalizedEmail, void 0, options.expiresIn ?? 3600);
77
+ if (options.sendConfirmationEmail && lead && !lead.confirmed) {
78
+ const token = await createConfirmationToken(ctx.context.secret, {
79
+ identifier: leadIdentifier,
80
+ type: identifierType
81
+ }, options.expiresIn ?? 3600);
56
82
  const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;
57
83
  const unsubscribeToken = await createUnsubscribeToken(ctx.context.secret, lead.id, options.unsubscribeExpiresIn);
58
84
  const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;
59
- if (await options.sendVerificationEmail({
85
+ if (await options.sendConfirmationEmail({
60
86
  lead,
87
+ email: leadEmail,
61
88
  url,
62
89
  token,
63
90
  unsubscribeUrl
64
91
  }, ctx.request)) await ctx.context.adapter.update({
65
92
  model: "lead",
66
93
  where: [{
67
- field: "email",
68
- value: normalizedEmail
94
+ field: whereField,
95
+ value: leadIdentifier
69
96
  }],
70
- update: { verificationEmailSentAt: /* @__PURE__ */ new Date() }
97
+ update: { confirmationSentAt: /* @__PURE__ */ new Date() }
71
98
  });
72
99
  }
73
100
  return ctx.json({ status: true });
@@ -85,26 +112,30 @@ const verify = (options) => createAuthEndpoint("/lead/verify", {
85
112
  if (e instanceof JWTExpired) throw APIError.from("UNAUTHORIZED", LEAD_ERROR_CODES.TOKEN_EXPIRED);
86
113
  throw APIError.from("UNAUTHORIZED", LEAD_ERROR_CODES.INVALID_TOKEN);
87
114
  }
88
- const parsed = subscribeSchema.parse(jwt.payload);
115
+ const parsed = z.object({
116
+ identifier: z.string(),
117
+ type: z.enum(["email", "user"])
118
+ }).parse(jwt.payload);
119
+ const whereField = parsed.type === "user" ? "userId" : "email";
89
120
  let lead = await ctx.context.adapter.findOne({
90
121
  model: "lead",
91
122
  where: [{
92
- field: "email",
93
- value: parsed.email
123
+ field: whereField,
124
+ value: parsed.identifier
94
125
  }]
95
126
  });
96
127
  if (!lead) return ctx.json({ status: true });
97
- if (lead.emailVerified) return ctx.json({ status: true });
128
+ if (lead.confirmed) return ctx.json({ status: true });
98
129
  lead = await ctx.context.adapter.update({
99
130
  model: "lead",
100
131
  where: [{
101
- field: "email",
102
- value: parsed.email
132
+ field: whereField,
133
+ value: parsed.identifier
103
134
  }],
104
- update: { emailVerified: true }
135
+ update: { confirmed: true }
105
136
  });
106
137
  if (!lead) return ctx.json({ status: true });
107
- if (options.onEmailVerified) await ctx.context.runInBackgroundOrAwait(options.onEmailVerified({ lead }, ctx.request));
138
+ if (options.onConfirmed) await ctx.context.runInBackgroundOrAwait(options.onConfirmed({ lead }, ctx.request));
108
139
  return ctx.json({ status: true });
109
140
  });
110
141
  const unsubscribeQuerySchema = z.object({ token: z.string().meta({ description: "Signed unsubscribe token" }) });
@@ -137,71 +168,137 @@ const unsubscribe = (options) => createAuthEndpoint("/lead/unsubscribe", {
137
168
  });
138
169
  return ctx.json({ status: true });
139
170
  });
140
- const resendSchema = z.object({ email: z.string().meta({ description: "Email address to resend the verification email to" }) });
171
+ const unsubscribeSession = (_options) => createAuthEndpoint("/lead/unsubscribe-session", {
172
+ method: "POST",
173
+ use: [sessionMiddleware]
174
+ }, async (ctx) => {
175
+ const userId = ctx.context.session.user.id;
176
+ if (!await ctx.context.adapter.findOne({
177
+ model: "lead",
178
+ where: [{
179
+ field: "userId",
180
+ value: userId
181
+ }]
182
+ })) return ctx.json({ status: true });
183
+ await ctx.context.adapter.delete({
184
+ model: "lead",
185
+ where: [{
186
+ field: "userId",
187
+ value: userId
188
+ }]
189
+ });
190
+ return ctx.json({ status: true });
191
+ });
192
+ const resendSchema = z.object({ email: z.string().optional().meta({ description: "Email address to resend the verification email to" }) });
141
193
  const resend = (options) => createAuthEndpoint("/lead/resend", {
142
194
  method: "POST",
143
195
  body: resendSchema
144
196
  }, async (ctx) => {
145
- const { email } = ctx.body;
146
- if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
147
- const normalizedEmail = email.toLowerCase();
197
+ const email = ctx.body.email;
198
+ let identifierType;
199
+ let leadIdentifier;
200
+ let leadEmail;
201
+ if (email) {
202
+ if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.INVALID_EMAIL);
203
+ leadIdentifier = email.toLowerCase();
204
+ identifierType = "email";
205
+ leadEmail = leadIdentifier;
206
+ } else {
207
+ const session = await getSessionFromCtx(ctx);
208
+ if (!session) throw APIError.from("BAD_REQUEST", LEAD_ERROR_CODES.EMAIL_OR_SESSION_REQUIRED);
209
+ leadIdentifier = session.user.id;
210
+ identifierType = "user";
211
+ leadEmail = session.user.email;
212
+ }
213
+ const whereField = identifierType === "email" ? "email" : "userId";
148
214
  const lead = await ctx.context.adapter.findOne({
149
215
  model: "lead",
150
216
  where: [{
151
- field: "email",
152
- value: normalizedEmail
217
+ field: whereField,
218
+ value: leadIdentifier
153
219
  }]
154
220
  });
155
221
  if (!lead) return ctx.json({ status: true });
156
- if (options.sendVerificationEmail && lead && !lead.emailVerified) {
157
- const token = await createEmailVerificationToken(ctx.context.secret, normalizedEmail, void 0, options.expiresIn ?? 3600);
222
+ if (options.sendConfirmationEmail && !lead.confirmed) {
223
+ const token = await createConfirmationToken(ctx.context.secret, {
224
+ identifier: leadIdentifier,
225
+ type: identifierType
226
+ }, options.expiresIn ?? 3600);
158
227
  const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;
159
228
  const unsubscribeToken = await createUnsubscribeToken(ctx.context.secret, lead.id, options.unsubscribeExpiresIn);
160
229
  const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;
161
- if (await options.sendVerificationEmail({
230
+ if (await options.sendConfirmationEmail({
162
231
  lead,
232
+ email: leadEmail,
163
233
  url,
164
234
  token,
165
235
  unsubscribeUrl
166
236
  }, ctx.request)) await ctx.context.adapter.update({
167
237
  model: "lead",
168
238
  where: [{
169
- field: "email",
170
- value: normalizedEmail
239
+ field: whereField,
240
+ value: leadIdentifier
171
241
  }],
172
- update: { verificationEmailSentAt: /* @__PURE__ */ new Date() }
242
+ update: { confirmationSentAt: /* @__PURE__ */ new Date() }
173
243
  });
174
244
  }
175
245
  return ctx.json({ status: true });
176
246
  });
177
- const updateSchema = z.object({
178
- id: z.string().meta({ description: "The id of the lead to update" }),
179
- metadata: z.record(z.string(), z.any()).optional().meta({ description: "Additional metadata to store with the lead" })
180
- });
247
+ const updateSchema = z.object({ metadata: z.record(z.string(), z.any()).optional().meta({ description: "Additional metadata to store with the lead" }) });
181
248
  const update = (options) => createAuthEndpoint("/lead/update", {
182
249
  method: "POST",
183
250
  body: updateSchema,
251
+ use: [sessionMiddleware],
184
252
  metadata: { $Infer: { body: {} } }
185
253
  }, async (ctx) => {
186
- const { id } = ctx.body;
254
+ const userId = ctx.context.session.user.id;
187
255
  if (!await ctx.context.adapter.findOne({
188
256
  model: "lead",
189
257
  where: [{
190
- field: "id",
191
- value: id
258
+ field: "userId",
259
+ value: userId
192
260
  }]
193
261
  })) return ctx.json({ status: true });
194
262
  const metadata = validateMetadata(options, ctx.body.metadata, ctx.context.logger);
195
263
  await ctx.context.adapter.update({
196
264
  model: "lead",
197
265
  where: [{
198
- field: "id",
199
- value: id
266
+ field: "userId",
267
+ value: userId
200
268
  }],
201
269
  update: { metadata: metadata ? JSON.stringify(metadata) : void 0 }
202
270
  });
203
271
  return ctx.json({ status: true });
204
272
  });
273
+ const listQuerySchema = z.object({
274
+ limit: z.coerce.number().meta({ description: "The number of lead to return" }).optional(),
275
+ offset: z.coerce.number().meta({ description: "The offset to start from" }).optional()
276
+ });
277
+ const list = (options) => createAuthEndpoint("/lead/list", {
278
+ method: "GET",
279
+ query: listQuerySchema,
280
+ use: [sessionMiddleware]
281
+ }, async (ctx) => {
282
+ if (!ctx.context.hasPlugin("admin")) throw APIError.from("NOT_FOUND", LEAD_ERROR_CODES.ADMIN_PLUGIN_REQUIRED);
283
+ const allowedRoles = options.admin?.roles ?? ["admin"];
284
+ if (!(ctx.context.session.user.role ?? "").split(",").map((r) => r.trim()).filter(Boolean).some((r) => allowedRoles.includes(r))) throw APIError.from("FORBIDDEN", LEAD_ERROR_CODES.FORBIDDEN);
285
+ const limit = ctx.query.limit ?? 100;
286
+ const offset = ctx.query.offset ?? 0;
287
+ const [leads, total] = await Promise.all([ctx.context.adapter.findMany({
288
+ model: "lead",
289
+ limit,
290
+ offset
291
+ }), ctx.context.adapter.count({ model: "lead" })]);
292
+ return ctx.json({
293
+ leads,
294
+ total,
295
+ limit,
296
+ offset
297
+ });
298
+ });
299
+ async function createConfirmationToken(secret, payload, expiresIn) {
300
+ return new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(Math.floor(Date.now() / 1e3) + expiresIn).sign(new TextEncoder().encode(secret));
301
+ }
205
302
  async function createUnsubscribeToken(secret, leadId, expiresIn) {
206
303
  const jwt = new SignJWT({ id: leadId }).setProtectedHeader({ alg: "HS256" }).setIssuedAt();
207
304
  if (expiresIn !== void 0) jwt.setExpirationTime(Math.floor(Date.now() / 1e3) + expiresIn);
@@ -235,16 +332,25 @@ const lead$1 = { lead: { fields: {
235
332
  },
236
333
  email: {
237
334
  type: "string",
238
- required: true,
335
+ required: false,
239
336
  unique: true
240
337
  },
241
- emailVerified: {
338
+ userId: {
339
+ type: "string",
340
+ required: false,
341
+ unique: true,
342
+ references: {
343
+ model: "user",
344
+ field: "id"
345
+ }
346
+ },
347
+ confirmed: {
242
348
  type: "boolean",
243
349
  defaultValue: false,
244
350
  required: true,
245
351
  input: false
246
352
  },
247
- verificationEmailSentAt: {
353
+ confirmationSentAt: {
248
354
  type: "date",
249
355
  required: false,
250
356
  input: false
@@ -260,16 +366,19 @@ const getSchema = (options) => {
260
366
  //#endregion
261
367
  //#region src/index.ts
262
368
  const lead = (options = {}) => {
369
+ const endpoints = {
370
+ subscribe: subscribe(options),
371
+ verify: verify(options),
372
+ unsubscribe: unsubscribe(options),
373
+ unsubscribeSession: unsubscribeSession(options),
374
+ resend: resend(options),
375
+ update: update(options),
376
+ ...options.admin?.enabled ? { list: list(options) } : {}
377
+ };
263
378
  return {
264
379
  id: "lead",
265
380
  schema: getSchema(options),
266
- endpoints: {
267
- subscribe: subscribe(options),
268
- verify: verify(options),
269
- unsubscribe: unsubscribe(options),
270
- resend: resend(options),
271
- update: update(options)
272
- },
381
+ endpoints,
273
382
  options,
274
383
  rateLimit: [{
275
384
  pathMatcher: (path) => ["/lead/subscribe", "/lead/resend"].includes(path),
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["lead"],"sources":["../src/error-codes.ts","../src/routes.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import { defineErrorCodes } from 'better-auth';\n\nexport const LEAD_ERROR_CODES = defineErrorCodes({\n INVALID_EMAIL: 'Invalid email',\n INVALID_TOKEN: 'Invalid token',\n TOKEN_EXPIRED: 'Token expired',\n INVALID_METADATA: 'Invalid metadata',\n});\n","import { BASE_ERROR_CODES, type InternalLogger, type StandardSchemaV1 } from 'better-auth';\nimport { APIError, createAuthEndpoint, createEmailVerificationToken } from 'better-auth/api';\nimport { SignJWT, jwtVerify } from 'jose';\nimport type { JWTPayload, JWTVerifyResult } from 'jose';\nimport { JWTExpired } from 'jose/errors';\nimport * as z from 'zod';\n\nimport { LEAD_ERROR_CODES } from './error-codes';\nimport type { Lead, LeadOptions, LeadPayload } from './type';\n\ntype InferMetadata<O extends LeadOptions> = O extends {\n metadata: { validationSchema: StandardSchemaV1<unknown, infer Out> };\n}\n ? Out\n : Record<string, any>;\n\nconst subscribeSchema = z.object({\n email: z.string().meta({\n description: 'Email address of the lead',\n }),\n metadata: z.record(z.string(), z.any()).optional().meta({\n description: 'Additional metadata to store with the lead',\n }),\n});\n\nexport const subscribe = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/subscribe',\n {\n method: 'POST',\n body: subscribeSchema,\n metadata: {\n $Infer: {\n body: {} as {\n email: string;\n metadata?: InferMetadata<O>;\n },\n },\n },\n },\n async (ctx) => {\n const { email } = ctx.body;\n\n const isValidEmail = z.email().safeParse(email);\n if (!isValidEmail.success) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_EMAIL);\n }\n\n const metadata = validateMetadata(\n options,\n ctx.body.metadata as Record<string, any> | undefined,\n ctx.context.logger,\n );\n\n const normalizedEmail = email.toLowerCase();\n\n let lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'email',\n value: normalizedEmail,\n },\n ],\n });\n\n if (!lead) {\n try {\n lead = await ctx.context.adapter.create<LeadPayload, Lead>({\n model: 'lead',\n data: {\n email: normalizedEmail,\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n },\n });\n } catch (e) {\n ctx.context.logger.info('Error creating lead');\n lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'email',\n value: normalizedEmail,\n },\n ],\n });\n }\n }\n\n if (options.sendVerificationEmail && lead && !lead.emailVerified) {\n const token = await createEmailVerificationToken(\n ctx.context.secret,\n normalizedEmail,\n undefined,\n options.expiresIn ?? 3600,\n );\n const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;\n const unsubscribeToken = await createUnsubscribeToken(\n ctx.context.secret,\n lead.id,\n options.unsubscribeExpiresIn,\n );\n const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;\n\n const sent = await options.sendVerificationEmail(\n {\n lead,\n url,\n token,\n unsubscribeUrl,\n },\n ctx.request,\n );\n\n if (sent) {\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: 'email', value: normalizedEmail }],\n update: { verificationEmailSentAt: new Date() },\n });\n }\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst verifySchema = z.object({\n token: z.string().meta({\n description: 'The token to verify the email',\n }),\n});\n\nexport const verify = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/verify',\n {\n method: 'GET',\n query: verifySchema,\n },\n async (ctx) => {\n const { token } = ctx.query;\n\n let jwt: JWTVerifyResult<JWTPayload>;\n try {\n jwt = await jwtVerify(token, new TextEncoder().encode(ctx.context.secret), {\n algorithms: ['HS256'],\n });\n } catch (e) {\n if (e instanceof JWTExpired) {\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.TOKEN_EXPIRED);\n }\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.INVALID_TOKEN);\n }\n\n const parsed = subscribeSchema.parse(jwt.payload);\n\n let lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'email',\n value: parsed.email,\n },\n ],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (lead.emailVerified) {\n return ctx.json({\n status: true,\n });\n }\n\n lead = await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [\n {\n field: 'email',\n value: parsed.email,\n },\n ],\n update: {\n emailVerified: true,\n },\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (options.onEmailVerified) {\n await ctx.context.runInBackgroundOrAwait(options.onEmailVerified({ lead }, ctx.request));\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst unsubscribeQuerySchema = z.object({\n token: z.string().meta({\n description: 'Signed unsubscribe token',\n }),\n});\n\nexport const unsubscribe = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/unsubscribe',\n {\n method: 'POST',\n query: unsubscribeQuerySchema,\n metadata: {\n // Empty array overrides the router-level JSON-only restriction,\n // allowing POST with no body (required for RFC 8058 one-click unsubscribe).\n allowedMediaTypes: [],\n },\n },\n async (ctx) => {\n let payload: JWTPayload;\n try {\n const result = await jwtVerify(\n ctx.query.token,\n new TextEncoder().encode(ctx.context.secret),\n { algorithms: ['HS256'] },\n );\n payload = result.payload;\n } catch (e) {\n if (e instanceof JWTExpired) {\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.TOKEN_EXPIRED);\n }\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.INVALID_TOKEN);\n }\n\n const id = payload['id'] as string;\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n await ctx.context.adapter.delete({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n });\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst resendSchema = z.object({\n email: z.string().meta({\n description: 'Email address to resend the verification email to',\n }),\n});\n\nexport const resend = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/resend',\n {\n method: 'POST',\n body: resendSchema,\n },\n async (ctx) => {\n const { email } = ctx.body;\n\n const isValidEmail = z.email().safeParse(email);\n if (!isValidEmail.success) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_EMAIL);\n }\n\n const normalizedEmail = email.toLowerCase();\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'email',\n value: normalizedEmail,\n },\n ],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (options.sendVerificationEmail && lead && !lead.emailVerified) {\n const token = await createEmailVerificationToken(\n ctx.context.secret,\n normalizedEmail,\n undefined,\n options.expiresIn ?? 3600,\n );\n const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;\n const unsubscribeToken = await createUnsubscribeToken(\n ctx.context.secret,\n lead.id,\n options.unsubscribeExpiresIn,\n );\n const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;\n\n const sent = await options.sendVerificationEmail(\n {\n lead,\n url,\n token,\n unsubscribeUrl,\n },\n ctx.request,\n );\n\n if (sent) {\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: 'email', value: normalizedEmail }],\n update: { verificationEmailSentAt: new Date() },\n });\n }\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst updateSchema = z.object({\n id: z.string().meta({\n description: 'The id of the lead to update',\n }),\n metadata: z.record(z.string(), z.any()).optional().meta({\n description: 'Additional metadata to store with the lead',\n }),\n});\n\nexport const update = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/update',\n {\n method: 'POST',\n body: updateSchema,\n metadata: {\n $Infer: {\n body: {} as {\n id: string;\n metadata?: InferMetadata<O>;\n },\n },\n },\n },\n async (ctx) => {\n const { id } = ctx.body;\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n const metadata = validateMetadata(\n options,\n ctx.body.metadata as Record<string, any> | undefined,\n ctx.context.logger,\n );\n\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n update: {\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n },\n });\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nasync function createUnsubscribeToken(secret: string, leadId: string, expiresIn?: number) {\n const jwt = new SignJWT({ id: leadId }).setProtectedHeader({ alg: 'HS256' }).setIssuedAt();\n if (expiresIn !== undefined) {\n jwt.setExpirationTime(Math.floor(Date.now() / 1000) + expiresIn);\n }\n return jwt.sign(new TextEncoder().encode(secret));\n}\n\nfunction validateMetadata(\n options: LeadOptions,\n metadata: Record<string, any> | undefined,\n logger: InternalLogger,\n) {\n if (!metadata || !options.metadata?.validationSchema) {\n return metadata;\n }\n const validationResult = options.metadata.validationSchema['~standard'].validate(metadata);\n\n if (validationResult instanceof Promise) {\n throw APIError.from('INTERNAL_SERVER_ERROR', BASE_ERROR_CODES.ASYNC_VALIDATION_NOT_SUPPORTED);\n }\n\n if (validationResult.issues) {\n logger.error('Invalid metadata', validationResult.issues);\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_METADATA);\n }\n\n return validationResult.value as Record<string, any>;\n}\n","import { type BetterAuthPluginDBSchema } from 'better-auth';\nimport { mergeSchema } from 'better-auth/db';\n\nimport type { LeadOptions } from './type';\n\nexport const lead = {\n lead: {\n fields: {\n createdAt: {\n type: 'date',\n defaultValue: () => new Date(),\n required: true,\n input: false,\n },\n updatedAt: {\n type: 'date',\n defaultValue: () => new Date(),\n onUpdate: () => new Date(),\n required: true,\n input: false,\n },\n email: {\n type: 'string',\n required: true,\n unique: true,\n },\n emailVerified: {\n type: 'boolean',\n defaultValue: false,\n required: true,\n input: false,\n },\n verificationEmailSentAt: {\n type: 'date',\n required: false,\n input: false,\n },\n metadata: {\n type: 'string',\n required: false,\n },\n },\n },\n} satisfies BetterAuthPluginDBSchema;\n\nexport const getSchema = <O extends LeadOptions>(options: O) => {\n return mergeSchema(lead, options.schema);\n};\n","import type { BetterAuthPlugin } from 'better-auth';\n\nimport { LEAD_ERROR_CODES } from './error-codes';\nimport { resend, subscribe, unsubscribe, update, verify } from './routes';\nimport { getSchema } from './schema';\nimport type { LeadOptions } from './type';\n\nexport const lead = <O extends LeadOptions>(options: O = {} as O) => {\n return {\n id: 'lead',\n schema: getSchema(options),\n endpoints: {\n subscribe: subscribe(options),\n verify: verify(options),\n unsubscribe: unsubscribe(options),\n resend: resend(options),\n update: update(options),\n },\n options: options as NoInfer<O>,\n rateLimit: [\n {\n pathMatcher: (path) => ['/lead/subscribe', '/lead/resend'].includes(path),\n window: options.rateLimit?.window ?? 10,\n max: options.rateLimit?.max ?? 3,\n },\n ],\n $ERROR_CODES: LEAD_ERROR_CODES,\n } satisfies BetterAuthPlugin;\n};\n\nexport type * from './type';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["lead"],"sources":["../src/error-codes.ts","../src/routes.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import { defineErrorCodes } from 'better-auth';\n\nexport const LEAD_ERROR_CODES = defineErrorCodes({\n INVALID_EMAIL: 'Invalid email',\n INVALID_TOKEN: 'Invalid token',\n TOKEN_EXPIRED: 'Token expired',\n INVALID_METADATA: 'Invalid metadata',\n EMAIL_OR_SESSION_REQUIRED: 'Email or session is required',\n ADMIN_PLUGIN_REQUIRED: 'Admin plugin is required',\n FORBIDDEN: 'Forbidden',\n});\n","import { BASE_ERROR_CODES, type InternalLogger, type StandardSchemaV1 } from 'better-auth';\nimport {\n APIError,\n createAuthEndpoint,\n getSessionFromCtx,\n sessionMiddleware,\n} from 'better-auth/api';\nimport { SignJWT, jwtVerify } from 'jose';\nimport type { JWTPayload, JWTVerifyResult } from 'jose';\nimport { JWTExpired } from 'jose/errors';\nimport * as z from 'zod';\n\nimport { LEAD_ERROR_CODES } from './error-codes';\nimport type { Lead, LeadOptions, LeadPayload } from './type';\n\ntype InferMetadata<O extends LeadOptions> = O extends {\n metadata: { validationSchema: StandardSchemaV1<unknown, infer Out> };\n}\n ? Out\n : Record<string, any>;\n\ntype IdentifierType = 'email' | 'user';\n\nconst subscribeSchema = z.object({\n email: z.string().optional().meta({\n description: 'Email address of the lead',\n }),\n metadata: z.record(z.string(), z.any()).optional().meta({\n description: 'Additional metadata to store with the lead',\n }),\n});\n\nexport const subscribe = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/subscribe',\n {\n method: 'POST',\n body: subscribeSchema,\n metadata: {\n $Infer: {\n body: {} as {\n email?: string;\n metadata?: InferMetadata<O>;\n },\n },\n },\n },\n async (ctx) => {\n const email = ctx.body.email;\n\n const metadata = validateMetadata(\n options,\n ctx.body.metadata as Record<string, any> | undefined,\n ctx.context.logger,\n );\n\n let identifierType: IdentifierType;\n let leadIdentifier: string;\n let leadEmail: string;\n let createData: LeadPayload;\n\n if (email) {\n const isValidEmail = z.email().safeParse(email);\n if (!isValidEmail.success) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_EMAIL);\n }\n\n leadIdentifier = email.toLowerCase();\n identifierType = 'email';\n leadEmail = leadIdentifier;\n createData = {\n email: leadIdentifier,\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n };\n } else {\n const session = await getSessionFromCtx(ctx);\n\n if (!session) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.EMAIL_OR_SESSION_REQUIRED);\n }\n\n leadIdentifier = session.user.id;\n identifierType = 'user';\n leadEmail = session.user.email;\n createData = {\n userId: leadIdentifier,\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n };\n }\n\n const whereField = identifierType === 'email' ? 'email' : 'userId';\n\n let lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: leadIdentifier }],\n });\n\n if (!lead) {\n try {\n lead = await ctx.context.adapter.create<LeadPayload, Lead>({\n model: 'lead',\n data: createData,\n });\n } catch (e) {\n ctx.context.logger.info('Error creating lead');\n lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: leadIdentifier }],\n });\n }\n }\n\n if (options.sendConfirmationEmail && lead && !lead.confirmed) {\n const token = await createConfirmationToken(\n ctx.context.secret,\n { identifier: leadIdentifier, type: identifierType },\n options.expiresIn ?? 3600,\n );\n const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;\n const unsubscribeToken = await createUnsubscribeToken(\n ctx.context.secret,\n lead.id,\n options.unsubscribeExpiresIn,\n );\n const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;\n\n const sent = await options.sendConfirmationEmail(\n {\n lead,\n email: leadEmail,\n url,\n token,\n unsubscribeUrl,\n },\n ctx.request,\n );\n\n if (sent) {\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: leadIdentifier }],\n update: { confirmationSentAt: new Date() },\n });\n }\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst verifySchema = z.object({\n token: z.string().meta({\n description: 'The token to verify the email',\n }),\n});\n\nexport const verify = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/verify',\n {\n method: 'GET',\n query: verifySchema,\n },\n async (ctx) => {\n const { token } = ctx.query;\n\n let jwt: JWTVerifyResult<JWTPayload>;\n try {\n jwt = await jwtVerify(token, new TextEncoder().encode(ctx.context.secret), {\n algorithms: ['HS256'],\n });\n } catch (e) {\n if (e instanceof JWTExpired) {\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.TOKEN_EXPIRED);\n }\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.INVALID_TOKEN);\n }\n\n const confirmationPayloadSchema = z.object({\n identifier: z.string(),\n type: z.enum(['email', 'user']),\n });\n const parsed = confirmationPayloadSchema.parse(jwt.payload);\n\n const whereField = parsed.type === 'user' ? 'userId' : 'email';\n\n let lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: parsed.identifier }],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (lead.confirmed) {\n return ctx.json({\n status: true,\n });\n }\n\n lead = await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: parsed.identifier }],\n update: {\n confirmed: true,\n },\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (options.onConfirmed) {\n await ctx.context.runInBackgroundOrAwait(options.onConfirmed({ lead }, ctx.request));\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst unsubscribeQuerySchema = z.object({\n token: z.string().meta({\n description: 'Signed unsubscribe token',\n }),\n});\n\nexport const unsubscribe = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/unsubscribe',\n {\n method: 'POST',\n query: unsubscribeQuerySchema,\n metadata: {\n // Empty array overrides the router-level JSON-only restriction,\n // allowing POST with no body (required for RFC 8058 one-click unsubscribe).\n allowedMediaTypes: [],\n },\n },\n async (ctx) => {\n let payload: JWTPayload;\n try {\n const result = await jwtVerify(\n ctx.query.token,\n new TextEncoder().encode(ctx.context.secret),\n { algorithms: ['HS256'] },\n );\n payload = result.payload;\n } catch (e) {\n if (e instanceof JWTExpired) {\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.TOKEN_EXPIRED);\n }\n throw APIError.from('UNAUTHORIZED', LEAD_ERROR_CODES.INVALID_TOKEN);\n }\n\n const id = payload['id'] as string;\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n await ctx.context.adapter.delete({\n model: 'lead',\n where: [\n {\n field: 'id',\n value: id,\n },\n ],\n });\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nexport const unsubscribeSession = <O extends LeadOptions>(_options: O) =>\n createAuthEndpoint(\n '/lead/unsubscribe-session',\n {\n method: 'POST',\n use: [sessionMiddleware],\n },\n async (ctx) => {\n const userId = ctx.context.session.user.id;\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: 'userId', value: userId }],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n await ctx.context.adapter.delete({\n model: 'lead',\n where: [{ field: 'userId', value: userId }],\n });\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst resendSchema = z.object({\n email: z.string().optional().meta({\n description: 'Email address to resend the verification email to',\n }),\n});\n\nexport const resend = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/resend',\n {\n method: 'POST',\n body: resendSchema,\n },\n async (ctx) => {\n const email = ctx.body.email;\n\n let identifierType: IdentifierType;\n let leadIdentifier: string;\n let leadEmail: string;\n\n if (email) {\n const isValidEmail = z.email().safeParse(email);\n if (!isValidEmail.success) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_EMAIL);\n }\n\n leadIdentifier = email.toLowerCase();\n identifierType = 'email';\n leadEmail = leadIdentifier;\n } else {\n const session = await getSessionFromCtx(ctx);\n\n if (!session) {\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.EMAIL_OR_SESSION_REQUIRED);\n }\n leadIdentifier = session.user.id;\n identifierType = 'user';\n leadEmail = session.user.email;\n }\n\n const whereField = identifierType === 'email' ? 'email' : 'userId';\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: leadIdentifier }],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n if (options.sendConfirmationEmail && !lead.confirmed) {\n const token = await createConfirmationToken(\n ctx.context.secret,\n { identifier: leadIdentifier, type: identifierType },\n options.expiresIn ?? 3600,\n );\n const url = `${ctx.context.baseURL}/lead/verify?token=${token}`;\n const unsubscribeToken = await createUnsubscribeToken(\n ctx.context.secret,\n lead.id,\n options.unsubscribeExpiresIn,\n );\n const unsubscribeUrl = `${ctx.context.baseURL}/lead/unsubscribe?token=${unsubscribeToken}`;\n\n const sent = await options.sendConfirmationEmail(\n {\n lead,\n email: leadEmail,\n url,\n token,\n unsubscribeUrl,\n },\n ctx.request,\n );\n\n if (sent) {\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: whereField, value: leadIdentifier }],\n update: { confirmationSentAt: new Date() },\n });\n }\n }\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst updateSchema = z.object({\n metadata: z.record(z.string(), z.any()).optional().meta({\n description: 'Additional metadata to store with the lead',\n }),\n});\n\nexport const update = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/update',\n {\n method: 'POST',\n body: updateSchema,\n use: [sessionMiddleware],\n metadata: {\n $Infer: {\n body: {} as {\n metadata?: InferMetadata<O>;\n },\n },\n },\n },\n async (ctx) => {\n const userId = ctx.context.session.user.id;\n\n const lead = await ctx.context.adapter.findOne<Lead>({\n model: 'lead',\n where: [{ field: 'userId', value: userId }],\n });\n\n if (!lead) {\n return ctx.json({\n status: true,\n });\n }\n\n const metadata = validateMetadata(\n options,\n ctx.body.metadata as Record<string, any> | undefined,\n ctx.context.logger,\n );\n\n await ctx.context.adapter.update<Lead>({\n model: 'lead',\n where: [{ field: 'userId', value: userId }],\n update: {\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n },\n });\n\n return ctx.json({\n status: true,\n });\n },\n );\n\nconst listQuerySchema = z.object({\n limit: z.coerce\n .number()\n .meta({\n description: 'The number of lead to return',\n })\n .optional(),\n offset: z.coerce\n .number()\n .meta({\n description: 'The offset to start from',\n })\n .optional(),\n});\n\nexport const list = <O extends LeadOptions>(options: O) =>\n createAuthEndpoint(\n '/lead/list',\n {\n method: 'GET',\n query: listQuerySchema,\n use: [sessionMiddleware],\n },\n async (ctx) => {\n if (!ctx.context.hasPlugin('admin')) {\n throw APIError.from('NOT_FOUND', LEAD_ERROR_CODES.ADMIN_PLUGIN_REQUIRED);\n }\n\n const allowedRoles = options.admin?.roles ?? ['admin'];\n const userRole = (ctx.context.session.user as { role?: string }).role ?? '';\n const userRoles = userRole\n .split(',')\n .map((r) => r.trim())\n .filter(Boolean);\n const hasRole = userRoles.some((r) => allowedRoles.includes(r));\n\n if (!hasRole) {\n throw APIError.from('FORBIDDEN', LEAD_ERROR_CODES.FORBIDDEN);\n }\n\n const limit = ctx.query.limit ?? 100;\n const offset = ctx.query.offset ?? 0;\n\n const [leads, total] = await Promise.all([\n ctx.context.adapter.findMany<Lead>({\n model: 'lead',\n limit,\n offset,\n }),\n ctx.context.adapter.count({ model: 'lead' }),\n ]);\n\n return ctx.json({ leads, total, limit, offset });\n },\n );\n\nasync function createConfirmationToken(\n secret: string,\n payload: { identifier: string; type: IdentifierType },\n expiresIn: number,\n) {\n return new SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(Math.floor(Date.now() / 1000) + expiresIn)\n .sign(new TextEncoder().encode(secret));\n}\n\nasync function createUnsubscribeToken(secret: string, leadId: string, expiresIn?: number) {\n const jwt = new SignJWT({ id: leadId }).setProtectedHeader({ alg: 'HS256' }).setIssuedAt();\n if (expiresIn !== undefined) {\n jwt.setExpirationTime(Math.floor(Date.now() / 1000) + expiresIn);\n }\n return jwt.sign(new TextEncoder().encode(secret));\n}\n\nfunction validateMetadata(\n options: LeadOptions,\n metadata: Record<string, any> | undefined,\n logger: InternalLogger,\n) {\n if (!metadata || !options.metadata?.validationSchema) {\n return metadata;\n }\n const validationResult = options.metadata.validationSchema['~standard'].validate(metadata);\n\n if (validationResult instanceof Promise) {\n throw APIError.from('INTERNAL_SERVER_ERROR', BASE_ERROR_CODES.ASYNC_VALIDATION_NOT_SUPPORTED);\n }\n\n if (validationResult.issues) {\n logger.error('Invalid metadata', validationResult.issues);\n throw APIError.from('BAD_REQUEST', LEAD_ERROR_CODES.INVALID_METADATA);\n }\n\n return validationResult.value as Record<string, any>;\n}\n","import { type BetterAuthPluginDBSchema } from 'better-auth';\nimport { mergeSchema } from 'better-auth/db';\n\nimport type { LeadOptions } from './type';\n\nexport const lead = {\n lead: {\n fields: {\n createdAt: {\n type: 'date',\n defaultValue: () => new Date(),\n required: true,\n input: false,\n },\n updatedAt: {\n type: 'date',\n defaultValue: () => new Date(),\n onUpdate: () => new Date(),\n required: true,\n input: false,\n },\n email: {\n type: 'string',\n required: false,\n unique: true,\n },\n userId: {\n type: 'string',\n required: false,\n unique: true,\n references: {\n model: 'user',\n field: 'id',\n },\n },\n confirmed: {\n type: 'boolean',\n defaultValue: false,\n required: true,\n input: false,\n },\n confirmationSentAt: {\n type: 'date',\n required: false,\n input: false,\n },\n metadata: {\n type: 'string',\n required: false,\n },\n },\n },\n} satisfies BetterAuthPluginDBSchema;\n\nexport const getSchema = <O extends LeadOptions>(options: O) => {\n return mergeSchema(lead, options.schema);\n};\n","import type { BetterAuthPlugin } from 'better-auth';\n\nimport { LEAD_ERROR_CODES } from './error-codes';\nimport { list, resend, subscribe, unsubscribe, unsubscribeSession, update, verify } from './routes';\nimport { getSchema } from './schema';\nimport type { LeadOptions } from './type';\n\nexport const lead = <O extends LeadOptions>(options: O = {} as O) => {\n const endpoints = {\n subscribe: subscribe(options),\n verify: verify(options),\n unsubscribe: unsubscribe(options),\n unsubscribeSession: unsubscribeSession(options),\n resend: resend(options),\n update: update(options),\n ...(options.admin?.enabled ? { list: list(options) } : {}),\n };\n\n return {\n id: 'lead',\n schema: getSchema(options),\n endpoints,\n options: options as NoInfer<O>,\n rateLimit: [\n {\n pathMatcher: (path) => ['/lead/subscribe', '/lead/resend'].includes(path),\n window: options.rateLimit?.window ?? 10,\n max: options.rateLimit?.max ?? 3,\n },\n ],\n $ERROR_CODES: LEAD_ERROR_CODES,\n } satisfies BetterAuthPlugin;\n};\n\nexport type * from './type';\n"],"mappings":";;;;;;;AAEA,MAAa,mBAAmB,iBAAiB;CAC/C,eAAe;CACf,eAAe;CACf,eAAe;CACf,kBAAkB;CAClB,2BAA2B;CAC3B,uBAAuB;CACvB,WAAW;AACb,CAAC;;;ACaD,MAAM,kBAAkB,EAAE,OAAO;CAC/B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAChC,aAAa,4BACf,CAAC;CACD,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,EACtD,aAAa,6CACf,CAAC;AACH,CAAC;AAED,MAAa,aAAoC,YAC/C,mBACE,mBACA;CACE,QAAQ;CACR,MAAM;CACN,UAAU,EACR,QAAQ,EACN,MAAM,CAAC,EAIT,EACF;AACF,GACA,OAAO,QAAQ;CACb,MAAM,QAAQ,IAAI,KAAK;CAEvB,MAAM,WAAW,iBACf,SACA,IAAI,KAAK,UACT,IAAI,QAAQ,MACd;CAEA,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,IAAI,OAAO;EAET,IAAI,CADiB,EAAE,MAAM,EAAE,UAAU,KACzB,EAAE,SAChB,MAAM,SAAS,KAAK,eAAe,iBAAiB,aAAa;EAGnE,iBAAiB,MAAM,YAAY;EACnC,iBAAiB;EACjB,YAAY;EACZ,aAAa;GACX,OAAO;GACP,UAAU,WAAW,KAAK,UAAU,QAAQ,IAAI,KAAA;EAClD;CACF,OAAO;EACL,MAAM,UAAU,MAAM,kBAAkB,GAAG;EAE3C,IAAI,CAAC,SACH,MAAM,SAAS,KAAK,eAAe,iBAAiB,yBAAyB;EAG/E,iBAAiB,QAAQ,KAAK;EAC9B,iBAAiB;EACjB,YAAY,QAAQ,KAAK;EACzB,aAAa;GACX,QAAQ;GACR,UAAU,WAAW,KAAK,UAAU,QAAQ,IAAI,KAAA;EAClD;CACF;CAEA,MAAM,aAAa,mBAAmB,UAAU,UAAU;CAE1D,IAAI,OAAO,MAAM,IAAI,QAAQ,QAAQ,QAAc;EACjD,OAAO;EACP,OAAO,CAAC;GAAE,OAAO;GAAY,OAAO;EAAe,CAAC;CACtD,CAAC;CAED,IAAI,CAAC,MACH,IAAI;EACF,OAAO,MAAM,IAAI,QAAQ,QAAQ,OAA0B;GACzD,OAAO;GACP,MAAM;EACR,CAAC;CACH,SAAS,GAAG;EACV,IAAI,QAAQ,OAAO,KAAK,qBAAqB;EAC7C,OAAO,MAAM,IAAI,QAAQ,QAAQ,QAAc;GAC7C,OAAO;GACP,OAAO,CAAC;IAAE,OAAO;IAAY,OAAO;GAAe,CAAC;EACtD,CAAC;CACH;CAGF,IAAI,QAAQ,yBAAyB,QAAQ,CAAC,KAAK,WAAW;EAC5D,MAAM,QAAQ,MAAM,wBAClB,IAAI,QAAQ,QACZ;GAAE,YAAY;GAAgB,MAAM;EAAe,GACnD,QAAQ,aAAa,IACvB;EACA,MAAM,MAAM,GAAG,IAAI,QAAQ,QAAQ,qBAAqB;EACxD,MAAM,mBAAmB,MAAM,uBAC7B,IAAI,QAAQ,QACZ,KAAK,IACL,QAAQ,oBACV;EACA,MAAM,iBAAiB,GAAG,IAAI,QAAQ,QAAQ,0BAA0B;EAaxE,IAAI,MAXe,QAAQ,sBACzB;GACE;GACA,OAAO;GACP;GACA;GACA;EACF,GACA,IAAI,OACN,GAGE,MAAM,IAAI,QAAQ,QAAQ,OAAa;GACrC,OAAO;GACP,OAAO,CAAC;IAAE,OAAO;IAAY,OAAO;GAAe,CAAC;GACpD,QAAQ,EAAE,oCAAoB,IAAI,KAAK,EAAE;EAC3C,CAAC;CAEL;CAEA,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;AACH,CACF;AAEF,MAAM,eAAe,EAAE,OAAO,EAC5B,OAAO,EAAE,OAAO,EAAE,KAAK,EACrB,aAAa,gCACf,CAAC,EACH,CAAC;AAED,MAAa,UAAiC,YAC5C,mBACE,gBACA;CACE,QAAQ;CACR,OAAO;AACT,GACA,OAAO,QAAQ;CACb,MAAM,EAAE,UAAU,IAAI;CAEtB,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,UAAU,OAAO,IAAI,YAAY,EAAE,OAAO,IAAI,QAAQ,MAAM,GAAG,EACzE,YAAY,CAAC,OAAO,EACtB,CAAC;CACH,SAAS,GAAG;EACV,IAAI,aAAa,YACf,MAAM,SAAS,KAAK,gBAAgB,iBAAiB,aAAa;EAEpE,MAAM,SAAS,KAAK,gBAAgB,iBAAiB,aAAa;CACpE;CAMA,MAAM,SAJ4B,EAAE,OAAO;EACzC,YAAY,EAAE,OAAO;EACrB,MAAM,EAAE,KAAK,CAAC,SAAS,MAAM,CAAC;CAChC,CACuC,EAAE,MAAM,IAAI,OAAO;CAE1D,MAAM,aAAa,OAAO,SAAS,SAAS,WAAW;CAEvD,IAAI,OAAO,MAAM,IAAI,QAAQ,QAAQ,QAAc;EACjD,OAAO;EACP,OAAO,CAAC;GAAE,OAAO;GAAY,OAAO,OAAO;EAAW,CAAC;CACzD,CAAC;CAED,IAAI,CAAC,MACH,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;CAGH,IAAI,KAAK,WACP,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;CAGH,OAAO,MAAM,IAAI,QAAQ,QAAQ,OAAa;EAC5C,OAAO;EACP,OAAO,CAAC;GAAE,OAAO;GAAY,OAAO,OAAO;EAAW,CAAC;EACvD,QAAQ,EACN,WAAW,KACb;CACF,CAAC;CAED,IAAI,CAAC,MACH,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;CAGH,IAAI,QAAQ,aACV,MAAM,IAAI,QAAQ,uBAAuB,QAAQ,YAAY,EAAE,KAAK,GAAG,IAAI,OAAO,CAAC;CAGrF,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;AACH,CACF;AAEF,MAAM,yBAAyB,EAAE,OAAO,EACtC,OAAO,EAAE,OAAO,EAAE,KAAK,EACrB,aAAa,2BACf,CAAC,EACH,CAAC;AAED,MAAa,eAAsC,YACjD,mBACE,qBACA;CACE,QAAQ;CACR,OAAO;CACP,UAAU,EAGR,mBAAmB,CAAC,EACtB;AACF,GACA,OAAO,QAAQ;CACb,IAAI;CACJ,IAAI;EAMF,WAAU,MALW,UACnB,IAAI,MAAM,OACV,IAAI,YAAY,EAAE,OAAO,IAAI,QAAQ,MAAM,GAC3C,EAAE,YAAY,CAAC,OAAO,EAAE,CAC1B,GACiB;CACnB,SAAS,GAAG;EACV,IAAI,aAAa,YACf,MAAM,SAAS,KAAK,gBAAgB,iBAAiB,aAAa;EAEpE,MAAM,SAAS,KAAK,gBAAgB,iBAAiB,aAAa;CACpE;CAEA,MAAM,KAAK,QAAQ;CAYnB,IAAI,CAAC,MAVc,IAAI,QAAQ,QAAQ,QAAc;EACnD,OAAO;EACP,OAAO,CACL;GACE,OAAO;GACP,OAAO;EACT,CACF;CACF,CAAC,GAGC,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;CAGH,MAAM,IAAI,QAAQ,QAAQ,OAAO;EAC/B,OAAO;EACP,OAAO,CACL;GACE,OAAO;GACP,OAAO;EACT,CACF;CACF,CAAC;CAED,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;AACH,CACF;AAEF,MAAa,sBAA6C,aACxD,mBACE,6BACA;CACE,QAAQ;CACR,KAAK,CAAC,iBAAiB;AACzB,GACA,OAAO,QAAQ;CACb,MAAM,SAAS,IAAI,QAAQ,QAAQ,KAAK;CAOxC,IAAI,CAAC,MALc,IAAI,QAAQ,QAAQ,QAAc;EACnD,OAAO;EACP,OAAO,CAAC;GAAE,OAAO;GAAU,OAAO;EAAO,CAAC;CAC5C,CAAC,GAGC,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;CAGH,MAAM,IAAI,QAAQ,QAAQ,OAAO;EAC/B,OAAO;EACP,OAAO,CAAC;GAAE,OAAO;GAAU,OAAO;EAAO,CAAC;CAC5C,CAAC;CAED,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;AACH,CACF;AAEF,MAAM,eAAe,EAAE,OAAO,EAC5B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAChC,aAAa,oDACf,CAAC,EACH,CAAC;AAED,MAAa,UAAiC,YAC5C,mBACE,gBACA;CACE,QAAQ;CACR,MAAM;AACR,GACA,OAAO,QAAQ;CACb,MAAM,QAAQ,IAAI,KAAK;CAEvB,IAAI;CACJ,IAAI;CACJ,IAAI;CAEJ,IAAI,OAAO;EAET,IAAI,CADiB,EAAE,MAAM,EAAE,UAAU,KACzB,EAAE,SAChB,MAAM,SAAS,KAAK,eAAe,iBAAiB,aAAa;EAGnE,iBAAiB,MAAM,YAAY;EACnC,iBAAiB;EACjB,YAAY;CACd,OAAO;EACL,MAAM,UAAU,MAAM,kBAAkB,GAAG;EAE3C,IAAI,CAAC,SACH,MAAM,SAAS,KAAK,eAAe,iBAAiB,yBAAyB;EAE/E,iBAAiB,QAAQ,KAAK;EAC9B,iBAAiB;EACjB,YAAY,QAAQ,KAAK;CAC3B;CAEA,MAAM,aAAa,mBAAmB,UAAU,UAAU;CAE1D,MAAM,OAAO,MAAM,IAAI,QAAQ,QAAQ,QAAc;EACnD,OAAO;EACP,OAAO,CAAC;GAAE,OAAO;GAAY,OAAO;EAAe,CAAC;CACtD,CAAC;CAED,IAAI,CAAC,MACH,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;CAGH,IAAI,QAAQ,yBAAyB,CAAC,KAAK,WAAW;EACpD,MAAM,QAAQ,MAAM,wBAClB,IAAI,QAAQ,QACZ;GAAE,YAAY;GAAgB,MAAM;EAAe,GACnD,QAAQ,aAAa,IACvB;EACA,MAAM,MAAM,GAAG,IAAI,QAAQ,QAAQ,qBAAqB;EACxD,MAAM,mBAAmB,MAAM,uBAC7B,IAAI,QAAQ,QACZ,KAAK,IACL,QAAQ,oBACV;EACA,MAAM,iBAAiB,GAAG,IAAI,QAAQ,QAAQ,0BAA0B;EAaxE,IAAI,MAXe,QAAQ,sBACzB;GACE;GACA,OAAO;GACP;GACA;GACA;EACF,GACA,IAAI,OACN,GAGE,MAAM,IAAI,QAAQ,QAAQ,OAAa;GACrC,OAAO;GACP,OAAO,CAAC;IAAE,OAAO;IAAY,OAAO;GAAe,CAAC;GACpD,QAAQ,EAAE,oCAAoB,IAAI,KAAK,EAAE;EAC3C,CAAC;CAEL;CAEA,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;AACH,CACF;AAEF,MAAM,eAAe,EAAE,OAAO,EAC5B,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,EACtD,aAAa,6CACf,CAAC,EACH,CAAC;AAED,MAAa,UAAiC,YAC5C,mBACE,gBACA;CACE,QAAQ;CACR,MAAM;CACN,KAAK,CAAC,iBAAiB;CACvB,UAAU,EACR,QAAQ,EACN,MAAM,CAAC,EAGT,EACF;AACF,GACA,OAAO,QAAQ;CACb,MAAM,SAAS,IAAI,QAAQ,QAAQ,KAAK;CAOxC,IAAI,CAAC,MALc,IAAI,QAAQ,QAAQ,QAAc;EACnD,OAAO;EACP,OAAO,CAAC;GAAE,OAAO;GAAU,OAAO;EAAO,CAAC;CAC5C,CAAC,GAGC,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;CAGH,MAAM,WAAW,iBACf,SACA,IAAI,KAAK,UACT,IAAI,QAAQ,MACd;CAEA,MAAM,IAAI,QAAQ,QAAQ,OAAa;EACrC,OAAO;EACP,OAAO,CAAC;GAAE,OAAO;GAAU,OAAO;EAAO,CAAC;EAC1C,QAAQ,EACN,UAAU,WAAW,KAAK,UAAU,QAAQ,IAAI,KAAA,EAClD;CACF,CAAC;CAED,OAAO,IAAI,KAAK,EACd,QAAQ,KACV,CAAC;AACH,CACF;AAEF,MAAM,kBAAkB,EAAE,OAAO;CAC/B,OAAO,EAAE,OACN,OAAO,EACP,KAAK,EACJ,aAAa,+BACf,CAAC,EACA,SAAS;CACZ,QAAQ,EAAE,OACP,OAAO,EACP,KAAK,EACJ,aAAa,2BACf,CAAC,EACA,SAAS;AACd,CAAC;AAED,MAAa,QAA+B,YAC1C,mBACE,cACA;CACE,QAAQ;CACR,OAAO;CACP,KAAK,CAAC,iBAAiB;AACzB,GACA,OAAO,QAAQ;CACb,IAAI,CAAC,IAAI,QAAQ,UAAU,OAAO,GAChC,MAAM,SAAS,KAAK,aAAa,iBAAiB,qBAAqB;CAGzE,MAAM,eAAe,QAAQ,OAAO,SAAS,CAAC,OAAO;CAQrD,IAAI,EAPc,IAAI,QAAQ,QAAQ,KAA2B,QAAQ,IAEtE,MAAM,GAAG,EACT,KAAK,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OACc,EAAE,MAAM,MAAM,aAAa,SAAS,CAAC,CAElD,GACT,MAAM,SAAS,KAAK,aAAa,iBAAiB,SAAS;CAG7D,MAAM,QAAQ,IAAI,MAAM,SAAS;CACjC,MAAM,SAAS,IAAI,MAAM,UAAU;CAEnC,MAAM,CAAC,OAAO,SAAS,MAAM,QAAQ,IAAI,CACvC,IAAI,QAAQ,QAAQ,SAAe;EACjC,OAAO;EACP;EACA;CACF,CAAC,GACD,IAAI,QAAQ,QAAQ,MAAM,EAAE,OAAO,OAAO,CAAC,CAC7C,CAAC;CAED,OAAO,IAAI,KAAK;EAAE;EAAO;EAAO;EAAO;CAAO,CAAC;AACjD,CACF;AAEF,eAAe,wBACb,QACA,SACA,WACA;CACA,OAAO,IAAI,QAAQ,OAAO,EACvB,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI,SAAS,EAC3D,KAAK,IAAI,YAAY,EAAE,OAAO,MAAM,CAAC;AAC1C;AAEA,eAAe,uBAAuB,QAAgB,QAAgB,WAAoB;CACxF,MAAM,MAAM,IAAI,QAAQ,EAAE,IAAI,OAAO,CAAC,EAAE,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EAAE,YAAY;CACzF,IAAI,cAAc,KAAA,GAChB,IAAI,kBAAkB,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI,SAAS;CAEjE,OAAO,IAAI,KAAK,IAAI,YAAY,EAAE,OAAO,MAAM,CAAC;AAClD;AAEA,SAAS,iBACP,SACA,UACA,QACA;CACA,IAAI,CAAC,YAAY,CAAC,QAAQ,UAAU,kBAClC,OAAO;CAET,MAAM,mBAAmB,QAAQ,SAAS,iBAAiB,aAAa,SAAS,QAAQ;CAEzF,IAAI,4BAA4B,SAC9B,MAAM,SAAS,KAAK,yBAAyB,iBAAiB,8BAA8B;CAG9F,IAAI,iBAAiB,QAAQ;EAC3B,OAAO,MAAM,oBAAoB,iBAAiB,MAAM;EACxD,MAAM,SAAS,KAAK,eAAe,iBAAiB,gBAAgB;CACtE;CAEA,OAAO,iBAAiB;AAC1B;;;ACxjBA,MAAaA,SAAO,EAClB,MAAM,EACJ,QAAQ;CACN,WAAW;EACT,MAAM;EACN,oCAAoB,IAAI,KAAK;EAC7B,UAAU;EACV,OAAO;CACT;CACA,WAAW;EACT,MAAM;EACN,oCAAoB,IAAI,KAAK;EAC7B,gCAAgB,IAAI,KAAK;EACzB,UAAU;EACV,OAAO;CACT;CACA,OAAO;EACL,MAAM;EACN,UAAU;EACV,QAAQ;CACV;CACA,QAAQ;EACN,MAAM;EACN,UAAU;EACV,QAAQ;EACR,YAAY;GACV,OAAO;GACP,OAAO;EACT;CACF;CACA,WAAW;EACT,MAAM;EACN,cAAc;EACd,UAAU;EACV,OAAO;CACT;CACA,oBAAoB;EAClB,MAAM;EACN,UAAU;EACV,OAAO;CACT;CACA,UAAU;EACR,MAAM;EACN,UAAU;CACZ;AACF,EACF,EACF;AAEA,MAAa,aAAoC,YAAe;CAC9D,OAAO,YAAYA,QAAM,QAAQ,MAAM;AACzC;;;ACjDA,MAAa,QAA+B,UAAa,CAAC,MAAW;CACnE,MAAM,YAAY;EAChB,WAAW,UAAU,OAAO;EAC5B,QAAQ,OAAO,OAAO;EACtB,aAAa,YAAY,OAAO;EAChC,oBAAoB,mBAAmB,OAAO;EAC9C,QAAQ,OAAO,OAAO;EACtB,QAAQ,OAAO,OAAO;EACtB,GAAI,QAAQ,OAAO,UAAU,EAAE,MAAM,KAAK,OAAO,EAAE,IAAI,CAAC;CAC1D;CAEA,OAAO;EACL,IAAI;EACJ,QAAQ,UAAU,OAAO;EACzB;EACS;EACT,WAAW,CACT;GACE,cAAc,SAAS,CAAC,mBAAmB,cAAc,EAAE,SAAS,IAAI;GACxE,QAAQ,QAAQ,WAAW,UAAU;GACrC,KAAK,QAAQ,WAAW,OAAO;EACjC,CACF;EACA,cAAc;CAChB;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth-lead",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "Better Auth Lead plugin",
5
5
  "homepage": "https://github.com/marcjulian/better-auth-plugins#readme",
6
6
  "bugs": {