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 +31 -5
- package/dist/cli.js +6 -0
- package/dist/core/handler.js +1 -0
- package/dist/providers/events/helpers.d.ts +3 -1
- package/dist/providers/events/helpers.js +111 -51
- package/dist/public/assets/{main-B-gWWl9A.js → main-MRP9atW1.js} +1 -1
- package/dist/public/index.html +1 -1
- package/dist/types/handler.d.ts +1 -1
- package/dist/utils/database-detection.js +33 -3
- package/dist/utils/event-ingestion.js +1 -0
- package/dist/utils/hook-injector.js +1 -0
- package/dist/utils/package-json.d.ts +1 -0
- package/dist/utils/package-json.js +21 -0
- package/package.json +1 -1
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
|
|
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
|
|
96
|
-
- **MySQL** (via Prisma or
|
|
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
|
|
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);
|
package/dist/core/handler.js
CHANGED
|
@@ -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?:
|
|
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
|
-
|
|
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
|
-
:
|
|
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 (
|
|
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 (
|
|
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]);
|