better-auth-studio 1.1.3-beta.49 → 1.1.3-beta.51

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
@@ -29,7 +29,7 @@ For the hosted version: **[better-auth.build](https://better-auth.build)**
29
29
 
30
30
  ### Installation
31
31
 
32
- 📖 **[Documentation](https://better-auth-studio.vercel.app)**
32
+ 📖 **[Documentation](https://better-auth.studio)**
33
33
 
34
34
  **Recommended: Install as a dev dependency** (for project-specific versions):
35
35
 
@@ -81,7 +81,7 @@ Before using Better Auth Studio, ensure you have:
81
81
 
82
82
  - **Node.js** (v18 or higher)
83
83
  - **A Better Auth project** with a valid `auth.ts` configuration file
84
- - **Database setup** (Prisma, Drizzle, or SQLite)
84
+ - **Database setup** (Prisma, Drizzle, Kysely, or SQLite)
85
85
 
86
86
  ## 🔧 Configuration
87
87
 
@@ -91,9 +91,10 @@ Better Auth Studio automatically detects and works with:
91
91
 
92
92
  - **Prisma** (`prismaAdapter`)
93
93
  - **Drizzle** (`drizzleAdapter`)
94
+ - **Kysely** (`database: { db, type }`)
94
95
  - **SQLite** (`new Database()` from better-sqlite3)
95
- - **PostgreSQL** (via Prisma or Drizzle)
96
- - **MySQL** (via Prisma or Drizzle)
96
+ - **PostgreSQL** (via Prisma, Drizzle, or Kysely)
97
+ - **MySQL** (via Prisma, Drizzle, or Kysely)
97
98
 
98
99
  ### Example Configuration Files
99
100
 
@@ -131,6 +132,31 @@ export const auth = betterAuth({
131
132
  });
132
133
  ```
133
134
 
135
+ #### Kysely Setup
136
+
137
+ ```typescript
138
+ // auth.ts
139
+ import { betterAuth } from "better-auth";
140
+ import { Kysely, PostgresDialect } from "kysely";
141
+ import { Pool } from "pg";
142
+
143
+ const db = new Kysely<any>({
144
+ dialect: new PostgresDialect({
145
+ pool: new Pool({
146
+ connectionString: process.env.DATABASE_URL,
147
+ }),
148
+ }),
149
+ });
150
+
151
+ export const auth = betterAuth({
152
+ database: {
153
+ db,
154
+ type: "postgres", // or "mysql", "sqlite", "mssql"
155
+ },
156
+ // ... other config
157
+ });
158
+ ```
159
+
134
160
  #### SQLite Setup
135
161
 
136
162
  ```typescript
@@ -158,7 +184,7 @@ export const auth = betterAuth({
158
184
  - **Delete users** - Remove users from the system
159
185
  - **Bulk operations** - Seed multiple test users
160
186
  - **User details** - View user profiles, and accounts
161
- - **Last seen** - When events are enabled, the studio injects an optional `lastSeenAt` field on the user model and updates it on each sign-in or session creation. Run your database migration (e.g. `prisma migrate dev` or Drizzle push) to add the `lastSeenAt` column to the user table.
187
+ - **Last seen** - When events are enabled, the studio injects an optional `lastSeenAt` field on the user model and updates it on each sign-in or session creation. Run your database migration (e.g. `prisma migrate dev`, Drizzle push, or a Kysely migration) to add the `lastSeenAt` column to the user table.
162
188
 
163
189
  ### 🏢 Organization Management
164
190
 
package/dist/cli.js CHANGED
@@ -281,6 +281,12 @@ program
281
281
  else if (content.includes("prismaAdapter")) {
282
282
  databaseInfo = "Prisma";
283
283
  }
284
+ else if (content.includes("kyselyAdapter") ||
285
+ content.includes("Kysely") ||
286
+ content.includes('from "kysely"') ||
287
+ content.includes("from 'kysely'")) {
288
+ databaseInfo = "Kysely";
289
+ }
284
290
  else if (authConfig.database.adapter && authConfig.database.provider) {
285
291
  const adapter = authConfig.database.adapter;
286
292
  const adapterName = adapter.charAt(0).toUpperCase() + adapter.slice(1);
@@ -33,6 +33,7 @@ export async function initializeEventIngestionAndHooks(config) {
33
33
  case "postgres":
34
34
  case "prisma":
35
35
  case "drizzle":
36
+ case "kysely":
36
37
  try {
37
38
  provider = createPostgresProvider({
38
39
  client: config.events.client,
@@ -1,9 +1,10 @@
1
1
  import type { AuthEvent, EventIngestionProvider } from "../../types/events.js";
2
+ type PostgresProviderClientType = "postgres" | "prisma" | "drizzle" | "kysely";
2
3
  export declare function createPostgresProvider(options: {
3
4
  client: any;
4
5
  tableName?: string;
5
6
  schema?: string;
6
- clientType?: "postgres" | "prisma" | "drizzle";
7
+ clientType?: PostgresProviderClientType;
7
8
  }): EventIngestionProvider;
8
9
  export declare function createSqliteProvider(options: {
9
10
  client: any;
@@ -33,3 +34,4 @@ export declare function createStorageProvider(options: {
33
34
  adapter: any;
34
35
  tableName?: string;
35
36
  }): EventIngestionProvider;
37
+ export {};
@@ -1,3 +1,37 @@
1
+ function isKyselyClient(client, clientType) {
2
+ const hasExecuteQuery = typeof client?.executeQuery === "function";
3
+ if (clientType === "kysely") {
4
+ return hasExecuteQuery;
5
+ }
6
+ return hasExecuteQuery && typeof client?.selectFrom === "function";
7
+ }
8
+ function createKyselyCompiledQuery(sql, parameters = []) {
9
+ return {
10
+ sql,
11
+ parameters,
12
+ queryId: { queryId: `bas_${Date.now()}_${Math.random().toString(36).slice(2)}` },
13
+ };
14
+ }
15
+ async function executeKyselyQuery(client, query, params = []) {
16
+ return await client.executeQuery(createKyselyCompiledQuery(query, params));
17
+ }
18
+ function getEventInsertParams(event) {
19
+ return [
20
+ event.id,
21
+ event.type,
22
+ event.timestamp,
23
+ event.status || "success",
24
+ event.userId || null,
25
+ event.sessionId || null,
26
+ event.organizationId || null,
27
+ JSON.stringify(event.metadata || {}),
28
+ event.ipAddress || null,
29
+ event.userAgent || null,
30
+ event.source,
31
+ event.display?.message || null,
32
+ event.display?.severity || null,
33
+ ];
34
+ }
1
35
  export function createPostgresProvider(options) {
2
36
  const { client, tableName = "auth_events", schema = "public", clientType } = options;
3
37
  // Validate client is provided
@@ -18,12 +52,15 @@ export function createPostgresProvider(options) {
18
52
  const hasExecuteRaw = client?.$executeRaw;
19
53
  const hasExecute = client?.execute;
20
54
  const hasUnsafe = actualClient?.unsafe || client?.$client?.unsafe || client?.unsafe;
21
- if (!hasQuery && !hasExecuteRaw && !hasExecute && !hasUnsafe) {
55
+ const hasKysely = isKyselyClient(client, clientType);
56
+ if (!hasQuery && !hasExecuteRaw && !hasExecute && !hasUnsafe && !hasKysely) {
22
57
  const errorMessage = clientType === "prisma"
23
58
  ? "Invalid Prisma client. Client must have a `$executeRaw` method."
24
59
  : clientType === "drizzle"
25
60
  ? "Invalid Drizzle client. Drizzle instance must wrap a postgres-js (with `unsafe` method) or pg (with `query` method) client."
26
- : "Invalid Postgres client. Client must have either a `query` method (pg Pool/Client) or `$executeRaw` method (Prisma client).";
61
+ : clientType === "kysely"
62
+ ? "Invalid Kysely client. Client must have an `executeQuery` method."
63
+ : "Invalid Postgres client. Client must have either a `query` method (pg Pool/Client), `$executeRaw` method (Prisma client), or `executeQuery` method (Kysely client).";
27
64
  throw new Error(errorMessage);
28
65
  }
29
66
  // Ensure table exists
@@ -39,6 +76,11 @@ export function createPostgresProvider(options) {
39
76
  return await client.$executeRawUnsafe(query);
40
77
  };
41
78
  }
79
+ else if (hasKysely) {
80
+ executeQuery = async (query) => {
81
+ return await executeKyselyQuery(client, query);
82
+ };
83
+ }
42
84
  else if (clientType === "drizzle") {
43
85
  // Drizzle client - use $client which exposes the underlying client
44
86
  if (actualClient?.unsafe) {
@@ -69,7 +111,7 @@ export function createPostgresProvider(options) {
69
111
  };
70
112
  }
71
113
  else {
72
- throw new Error(`Postgres client doesn't support $executeRaw or query method. Available properties: ${Object.keys(client).join(", ")}`);
114
+ throw new Error(`Postgres client doesn't support $executeRaw, executeQuery, or query method. Available properties: ${Object.keys(client).join(", ")}`);
73
115
  }
74
116
  // Use CREATE TABLE IF NOT EXISTS (simpler and more reliable)
75
117
  const createTableQuery = `
@@ -141,7 +183,32 @@ export function createPostgresProvider(options) {
141
183
  return {
142
184
  async ingest(event) {
143
185
  await ensureTableSync();
144
- if (clientType === "prisma" || client.$executeRaw) {
186
+ if (hasKysely) {
187
+ try {
188
+ await executeKyselyQuery(client, `INSERT INTO ${schema}.${tableName}
189
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
190
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, getEventInsertParams(event));
191
+ }
192
+ catch (error) {
193
+ console.error(`Failed to insert event (${event.type}) into ${schema}.${tableName}:`, error);
194
+ if (error.code === "42P01") {
195
+ tableEnsured = false;
196
+ await ensureTableSync();
197
+ try {
198
+ await executeKyselyQuery(client, `INSERT INTO ${schema}.${tableName}
199
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
200
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, getEventInsertParams(event));
201
+ return;
202
+ }
203
+ catch (retryError) {
204
+ console.error(`Retry after table creation also failed:`, retryError);
205
+ throw retryError;
206
+ }
207
+ }
208
+ throw error;
209
+ }
210
+ }
211
+ else if (clientType === "prisma" || client.$executeRaw) {
145
212
  const query = `
146
213
  INSERT INTO ${schema}.${tableName}
147
214
  (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
@@ -176,7 +243,7 @@ export function createPostgresProvider(options) {
176
243
  if (useUnsafe) {
177
244
  // postgres-js client - use unsafe() for raw SQL
178
245
  const query = `
179
- INSERT INTO ${schema}.${tableName}
246
+ INSERT INTO ${schema}.${tableName}
180
247
  (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
181
248
  VALUES ('${event.id}'::uuid, '${event.type}', '${event.timestamp.toISOString()}', '${event.status || "success"}', ${event.userId ? `'${event.userId.replace(/'/g, "''")}'` : "NULL"}, ${event.sessionId ? `'${event.sessionId.replace(/'/g, "''")}'` : "NULL"}, ${event.organizationId ? `'${event.organizationId.replace(/'/g, "''")}'` : "NULL"}, '${JSON.stringify(event.metadata || {}).replace(/'/g, "''")}'::jsonb, ${event.ipAddress ? `'${event.ipAddress.replace(/'/g, "''")}'` : "NULL"}, ${event.userAgent ? `'${event.userAgent.replace(/'/g, "''")}'` : "NULL"}, '${event.source}', ${event.display?.message ? `'${event.display.message.replace(/'/g, "''")}'` : "NULL"}, ${event.display?.severity ? `'${event.display.severity}'` : "NULL"})
182
249
  `;
@@ -205,21 +272,7 @@ export function createPostgresProvider(options) {
205
272
  try {
206
273
  await queryClient.query(`INSERT INTO ${schema}.${tableName}
207
274
  (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
208
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, [
209
- event.id,
210
- event.type,
211
- event.timestamp,
212
- event.status || "success",
213
- event.userId || null,
214
- event.sessionId || null,
215
- event.organizationId || null,
216
- JSON.stringify(event.metadata || {}),
217
- event.ipAddress || null,
218
- event.userAgent || null,
219
- event.source,
220
- event.display?.message || null,
221
- event.display?.severity || null,
222
- ]);
275
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, [...getEventInsertParams(event)]);
223
276
  }
224
277
  catch (error) {
225
278
  console.error(`Failed to insert event (${event.type}) into ${schema}.${tableName}:`, error);
@@ -229,21 +282,7 @@ export function createPostgresProvider(options) {
229
282
  try {
230
283
  await queryClient.query(`INSERT INTO ${schema}.${tableName}
231
284
  (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
232
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, [
233
- event.id,
234
- event.type,
235
- event.timestamp,
236
- event.status || "success",
237
- event.userId || null,
238
- event.sessionId || null,
239
- event.organizationId || null,
240
- JSON.stringify(event.metadata || {}),
241
- event.ipAddress || null,
242
- event.userAgent || null,
243
- event.source,
244
- event.display?.message || null,
245
- event.display?.severity || null,
246
- ]);
285
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, [...getEventInsertParams(event)]);
247
286
  return;
248
287
  }
249
288
  catch (retryError) {
@@ -271,7 +310,34 @@ export function createPostgresProvider(options) {
271
310
  return;
272
311
  await ensureTableSync();
273
312
  // Support Prisma client ($executeRaw), Drizzle instance, or standard pg client (query)
274
- if (clientType === "prisma" || client.$executeRaw) {
313
+ if (hasKysely) {
314
+ const PARAMS_PER_EVENT = 13;
315
+ const MAX_PARAMS = 65535;
316
+ const CHUNK_SIZE = Math.floor(MAX_PARAMS / PARAMS_PER_EVENT) - 1;
317
+ for (let chunkStart = 0; chunkStart < events.length; chunkStart += CHUNK_SIZE) {
318
+ const chunk = events.slice(chunkStart, chunkStart + CHUNK_SIZE);
319
+ const values = chunk
320
+ .map((_, i) => {
321
+ const base = i * PARAMS_PER_EVENT;
322
+ return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}, $${base + 5}, $${base + 6}, $${base + 7}, $${base + 8}, $${base + 9}, $${base + 10}, $${base + 11}, $${base + 12}, $${base + 13})`;
323
+ })
324
+ .join(", ");
325
+ const query = `
326
+ INSERT INTO ${schema}.${tableName}
327
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
328
+ VALUES ${values}
329
+ `;
330
+ const params = chunk.flatMap((event) => getEventInsertParams(event));
331
+ try {
332
+ await executeKyselyQuery(client, query, params);
333
+ }
334
+ catch (error) {
335
+ console.error(`Failed to insert batch chunk (${chunk.length} events) into ${schema}.${tableName}:`, error);
336
+ throw error;
337
+ }
338
+ }
339
+ }
340
+ else if (clientType === "prisma" || client.$executeRaw) {
275
341
  // Prisma client - use $executeRawUnsafe for batch inserts
276
342
  const CHUNK_SIZE = 500; // Reasonable chunk size for string-based queries
277
343
  for (let i = 0; i < events.length; i += CHUNK_SIZE) {
@@ -339,21 +405,7 @@ export function createPostgresProvider(options) {
339
405
  (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
340
406
  VALUES ${values}
341
407
  `;
342
- const params = chunk.flatMap((event) => [
343
- event.id,
344
- event.type,
345
- event.timestamp,
346
- event.status || "success",
347
- event.userId || null,
348
- event.sessionId || null,
349
- event.organizationId || null,
350
- JSON.stringify(event.metadata || {}),
351
- event.ipAddress || null,
352
- event.userAgent || null,
353
- event.source,
354
- event.display?.message || null,
355
- event.display?.severity || null,
356
- ]);
408
+ const params = chunk.flatMap((event) => getEventInsertParams(event));
357
409
  try {
358
410
  await batchQueryClient.query(query, params);
359
411
  }
@@ -401,6 +453,11 @@ export function createPostgresProvider(options) {
401
453
  }
402
454
  };
403
455
  }
456
+ else if (hasKysely) {
457
+ queryFn = async (query, params) => {
458
+ return await executeKyselyQuery(client, query, params || []);
459
+ };
460
+ }
404
461
  else if (client.query) {
405
462
  // Standard pg client (Pool or Client)
406
463
  queryFn = async (query, params) => {
@@ -427,6 +484,9 @@ export function createPostgresProvider(options) {
427
484
  .replace("$2", `'${tableName}'`);
428
485
  checkResult = await client.$queryRawUnsafe(prismaQuery);
429
486
  }
487
+ else if (hasKysely) {
488
+ checkResult = await queryFn(checkTableQuery, [schema, tableName]);
489
+ }
430
490
  else {
431
491
  // Standard pg client
432
492
  checkResult = await queryFn(checkTableQuery, [schema, tableName]);