better-auth-studio 1.1.1-beta.3 → 1.1.1-beta.5

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.
Files changed (78) hide show
  1. package/dist/adapters/hono.d.ts.map +1 -1
  2. package/dist/adapters/hono.js +5 -0
  3. package/dist/adapters/hono.js.map +1 -1
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/core/handler.d.ts.map +1 -1
  7. package/dist/core/handler.js +85 -4
  8. package/dist/core/handler.js.map +1 -1
  9. package/dist/index.d.ts +4 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/providers/events/helpers.d.ts +22 -0
  14. package/dist/providers/events/helpers.d.ts.map +1 -0
  15. package/dist/providers/events/helpers.js +874 -0
  16. package/dist/providers/events/helpers.js.map +1 -0
  17. package/dist/public/assets/{main-BIlYgyPi.js → main-B_Zdj1LN.js} +139 -139
  18. package/dist/public/assets/main-RoeYO1I-.css +1 -0
  19. package/dist/public/index.html +2 -2
  20. package/dist/routes.d.ts.map +1 -1
  21. package/dist/routes.js +193 -0
  22. package/dist/routes.js.map +1 -1
  23. package/dist/studio.d.ts.map +1 -1
  24. package/dist/studio.js +6 -1
  25. package/dist/studio.js.map +1 -1
  26. package/dist/types/events.d.ts +43 -0
  27. package/dist/types/events.d.ts.map +1 -0
  28. package/dist/types/events.js +306 -0
  29. package/dist/types/events.js.map +1 -0
  30. package/dist/types/handler.d.ts +31 -0
  31. package/dist/types/handler.d.ts.map +1 -1
  32. package/dist/types/handler.js.map +1 -1
  33. package/dist/utils/auth-callbacks-injector.d.ts +3 -0
  34. package/dist/utils/auth-callbacks-injector.d.ts.map +1 -0
  35. package/dist/utils/auth-callbacks-injector.js +227 -0
  36. package/dist/utils/auth-callbacks-injector.js.map +1 -0
  37. package/dist/utils/auth-callbacks-wrapper.d.ts +7 -0
  38. package/dist/utils/auth-callbacks-wrapper.d.ts.map +1 -0
  39. package/dist/utils/auth-callbacks-wrapper.js +123 -0
  40. package/dist/utils/auth-callbacks-wrapper.js.map +1 -0
  41. package/dist/utils/database-hook-injector.d.ts +3 -0
  42. package/dist/utils/database-hook-injector.d.ts.map +1 -0
  43. package/dist/utils/database-hook-injector.js +141 -0
  44. package/dist/utils/database-hook-injector.js.map +1 -0
  45. package/dist/utils/email-otp-hooks-injector.d.ts +29 -0
  46. package/dist/utils/email-otp-hooks-injector.d.ts.map +1 -0
  47. package/dist/utils/email-otp-hooks-injector.js +134 -0
  48. package/dist/utils/email-otp-hooks-injector.js.map +1 -0
  49. package/dist/utils/event-ingestion.d.ts +38 -0
  50. package/dist/utils/event-ingestion.d.ts.map +1 -0
  51. package/dist/utils/event-ingestion.js +169 -0
  52. package/dist/utils/event-ingestion.js.map +1 -0
  53. package/dist/utils/hook-injector.d.ts +9 -0
  54. package/dist/utils/hook-injector.d.ts.map +1 -0
  55. package/dist/utils/hook-injector.js +649 -0
  56. package/dist/utils/hook-injector.js.map +1 -0
  57. package/dist/utils/html-injector.d.ts +17 -0
  58. package/dist/utils/html-injector.d.ts.map +1 -1
  59. package/dist/utils/html-injector.js +15 -0
  60. package/dist/utils/html-injector.js.map +1 -1
  61. package/dist/utils/org-hooks-injector.d.ts +74 -0
  62. package/dist/utils/org-hooks-injector.d.ts.map +1 -0
  63. package/dist/utils/org-hooks-injector.js +678 -0
  64. package/dist/utils/org-hooks-injector.js.map +1 -0
  65. package/dist/utils/org-hooks-wrapper.d.ts +74 -0
  66. package/dist/utils/org-hooks-wrapper.d.ts.map +1 -0
  67. package/dist/utils/org-hooks-wrapper.js +687 -0
  68. package/dist/utils/org-hooks-wrapper.js.map +1 -0
  69. package/dist/utils/organization-hooks-wrapper.d.ts +38 -0
  70. package/dist/utils/organization-hooks-wrapper.d.ts.map +1 -0
  71. package/dist/utils/organization-hooks-wrapper.js +297 -0
  72. package/dist/utils/organization-hooks-wrapper.js.map +1 -0
  73. package/package.json +7 -4
  74. package/public/assets/{main-BIlYgyPi.js → main-B_Zdj1LN.js} +139 -139
  75. package/public/assets/main-RoeYO1I-.css +1 -0
  76. package/public/index.html +2 -2
  77. package/dist/public/assets/main-s8HrXBxq.css +0 -1
  78. package/public/assets/main-s8HrXBxq.css +0 -1
@@ -0,0 +1,874 @@
1
+ export function createPostgresProvider(options) {
2
+ const { client, tableName = 'auth_events', schema = 'public' } = options;
3
+ // Ensure table exists
4
+ const ensureTable = async () => {
5
+ if (!client)
6
+ return;
7
+ try {
8
+ // Support different Postgres client types (pg, postgres.js, etc.)
9
+ const queryFn = client.query || (typeof client === 'function' ? client : null);
10
+ if (!queryFn) {
11
+ console.warn(`⚠️ Postgres client doesn't support query method. Table ${schema}.${tableName} must be created manually.`);
12
+ return;
13
+ }
14
+ // Support Prisma client ($executeRaw) or standard pg client (query)
15
+ let executeQuery;
16
+ if (client.$executeRaw) {
17
+ // Prisma client
18
+ executeQuery = async (query) => {
19
+ return await client.$executeRawUnsafe(query);
20
+ };
21
+ }
22
+ else if (client.query) {
23
+ // Standard pg client
24
+ executeQuery = async (query) => {
25
+ return await client.query(query);
26
+ };
27
+ }
28
+ else {
29
+ console.warn(`⚠️ Postgres client doesn't support $executeRaw or query method. Table ${schema}.${tableName} must be created manually.`);
30
+ return;
31
+ }
32
+ // Use CREATE TABLE IF NOT EXISTS (simpler and more reliable)
33
+ const createTableQuery = `
34
+ CREATE TABLE IF NOT EXISTS ${schema}.${tableName} (
35
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
36
+ type VARCHAR(100) NOT NULL,
37
+ timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
38
+ status VARCHAR(20) NOT NULL DEFAULT 'success',
39
+ user_id VARCHAR(255),
40
+ session_id VARCHAR(255),
41
+ organization_id VARCHAR(255),
42
+ metadata JSONB DEFAULT '{}',
43
+ ip_address INET,
44
+ user_agent TEXT,
45
+ source VARCHAR(50) DEFAULT 'app',
46
+ display_message TEXT,
47
+ display_severity VARCHAR(20),
48
+ created_at TIMESTAMPTZ DEFAULT NOW()
49
+ );
50
+ `;
51
+ await executeQuery(createTableQuery);
52
+ // Create indexes separately (ignore errors if they already exist)
53
+ const indexQueries = [
54
+ `CREATE INDEX IF NOT EXISTS idx_${tableName}_user_id ON ${schema}.${tableName}(user_id)`,
55
+ `CREATE INDEX IF NOT EXISTS idx_${tableName}_type ON ${schema}.${tableName}(type)`,
56
+ `CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${schema}.${tableName}(timestamp DESC)`,
57
+ `CREATE INDEX IF NOT EXISTS idx_${tableName}_id_timestamp ON ${schema}.${tableName}(id, timestamp DESC)`,
58
+ ];
59
+ for (const indexQuery of indexQueries) {
60
+ try {
61
+ await executeQuery(indexQuery);
62
+ }
63
+ catch (err) {
64
+ // Index might already exist, ignore
65
+ }
66
+ }
67
+ console.log(`✅ Ensured ${schema}.${tableName} table exists for events`);
68
+ }
69
+ catch (error) {
70
+ // If table already exists, that's fine
71
+ if (error?.message?.includes('already exists') || error?.code === '42P07') {
72
+ return;
73
+ }
74
+ console.error(`Failed to ensure ${schema}.${tableName} table:`, error);
75
+ // Don't throw - allow provider to work even if table creation fails
76
+ }
77
+ };
78
+ // Track if table creation is in progress or completed
79
+ let tableEnsured = false;
80
+ let tableEnsuring = false;
81
+ const ensureTableSync = async () => {
82
+ if (tableEnsured || tableEnsuring)
83
+ return;
84
+ tableEnsuring = true;
85
+ try {
86
+ await ensureTable();
87
+ tableEnsured = true;
88
+ }
89
+ catch (error) {
90
+ console.error('Failed to ensure table:', error);
91
+ }
92
+ finally {
93
+ tableEnsuring = false;
94
+ }
95
+ };
96
+ // Call ensureTable asynchronously (don't block initialization)
97
+ ensureTableSync().catch(console.error);
98
+ return {
99
+ async ingest(event) {
100
+ // Ensure table exists before ingesting
101
+ if (!tableEnsured) {
102
+ await ensureTableSync();
103
+ }
104
+ // Support Prisma client ($executeRaw) or standard pg client (query/Pool)
105
+ if (client.$executeRaw) {
106
+ // Prisma client - use $executeRawUnsafe for parameterized queries
107
+ const query = `
108
+ INSERT INTO ${schema}.${tableName}
109
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
110
+ 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'})
111
+ `;
112
+ await client.$executeRawUnsafe(query);
113
+ }
114
+ else if (client.query) {
115
+ // Standard pg client (Pool or Client) - use parameterized queries for safety
116
+ await client.query(`INSERT INTO ${schema}.${tableName}
117
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
118
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, [
119
+ event.id,
120
+ event.type,
121
+ event.timestamp,
122
+ event.status || 'success',
123
+ event.userId || null,
124
+ event.sessionId || null,
125
+ event.organizationId || null,
126
+ JSON.stringify(event.metadata || {}),
127
+ event.ipAddress || null,
128
+ event.userAgent || null,
129
+ event.source,
130
+ event.display?.message || null,
131
+ event.display?.severity || null,
132
+ ]);
133
+ }
134
+ },
135
+ async ingestBatch(events) {
136
+ if (events.length === 0)
137
+ return;
138
+ // Support Prisma client ($executeRaw) or standard pg client (query)
139
+ if (client.$executeRaw) {
140
+ // Prisma client - use $executeRawUnsafe for batch inserts
141
+ const values = events
142
+ .map((event) => `('${event.id}', '${event.type}', '${event.timestamp.toISOString()}', '${event.status || 'success'}', ${event.userId ? `'${event.userId}'` : 'NULL'}, ${event.sessionId ? `'${event.sessionId}'` : 'NULL'}, ${event.organizationId ? `'${event.organizationId}'` : 'NULL'}, '${JSON.stringify(event.metadata || {}).replace(/'/g, "''")}'::jsonb, ${event.ipAddress ? `'${event.ipAddress}'` : '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'})`)
143
+ .join(', ');
144
+ const query = `
145
+ INSERT INTO ${schema}.${tableName}
146
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
147
+ VALUES ${values}
148
+ `;
149
+ await client.$executeRawUnsafe(query);
150
+ }
151
+ else if (client.query) {
152
+ // Standard pg client
153
+ const values = events
154
+ .map((_, i) => {
155
+ const base = i * 13;
156
+ 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})`;
157
+ })
158
+ .join(', ');
159
+ const query = `
160
+ INSERT INTO ${schema}.${tableName}
161
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
162
+ VALUES ${values}
163
+ `;
164
+ const params = events.flatMap((event) => [
165
+ event.id,
166
+ event.type,
167
+ event.timestamp,
168
+ event.status || 'success',
169
+ event.userId || null,
170
+ event.sessionId || null,
171
+ event.organizationId || null,
172
+ JSON.stringify(event.metadata || {}),
173
+ event.ipAddress || null,
174
+ event.userAgent || null,
175
+ event.source,
176
+ event.display?.message || null,
177
+ event.display?.severity || null,
178
+ ]);
179
+ await client.query(query, params);
180
+ }
181
+ },
182
+ async query(options) {
183
+ const { limit = 20, after, sort = 'desc', type, userId } = options;
184
+ let queryFn;
185
+ if (client.$executeRaw) {
186
+ // Prisma client
187
+ queryFn = async (query, params) => {
188
+ if (params && params.length > 0) {
189
+ let processedQuery = query;
190
+ params.forEach((param, index) => {
191
+ const placeholder = `$${index + 1}`;
192
+ const value = typeof param === 'string'
193
+ ? `'${param.replace(/'/g, "''")}'`
194
+ : param === null
195
+ ? 'NULL'
196
+ : param instanceof Date
197
+ ? `'${param.toISOString()}'`
198
+ : String(param);
199
+ processedQuery = processedQuery.replace(new RegExp(`\\${placeholder}(?![0-9])`, 'g'), value);
200
+ });
201
+ return await client.$queryRawUnsafe(processedQuery);
202
+ }
203
+ else {
204
+ return await client.$queryRawUnsafe(query);
205
+ }
206
+ };
207
+ }
208
+ else if (client.query) {
209
+ // Standard pg client (Pool or Client)
210
+ queryFn = async (query, params) => {
211
+ const result = await client.query(query, params);
212
+ return result;
213
+ };
214
+ }
215
+ else {
216
+ throw new Error('Postgres client does not support $executeRaw or query method');
217
+ }
218
+ try {
219
+ const checkTableQuery = `
220
+ SELECT EXISTS (
221
+ SELECT FROM information_schema.tables
222
+ WHERE table_schema = $1
223
+ AND table_name = $2
224
+ );
225
+ `;
226
+ let checkResult;
227
+ if (client.$executeRaw) {
228
+ // Prisma client - replace $1, $2 with actual values
229
+ const prismaQuery = checkTableQuery
230
+ .replace('$1', `'${schema}'`)
231
+ .replace('$2', `'${tableName}'`);
232
+ checkResult = await client.$queryRawUnsafe(prismaQuery);
233
+ }
234
+ else {
235
+ // Standard pg client
236
+ checkResult = await queryFn(checkTableQuery, [schema, tableName]);
237
+ }
238
+ const exists = Array.isArray(checkResult)
239
+ ? checkResult[0]?.exists || false
240
+ : checkResult.rows?.[0]?.exists || checkResult?.[0]?.exists || false;
241
+ if (!exists) {
242
+ return {
243
+ events: [],
244
+ hasMore: false,
245
+ nextCursor: null,
246
+ };
247
+ }
248
+ }
249
+ catch (error) {
250
+ console.warn(`Failed to check table existence:`, error);
251
+ }
252
+ const whereClauses = [];
253
+ const params = [];
254
+ let paramIndex = 1;
255
+ // Cursor-based pagination
256
+ if (after) {
257
+ if (sort === 'desc') {
258
+ whereClauses.push(`id < $${paramIndex++}`);
259
+ params.push(after);
260
+ }
261
+ else {
262
+ whereClauses.push(`id > $${paramIndex++}`);
263
+ params.push(after);
264
+ }
265
+ }
266
+ if (type) {
267
+ whereClauses.push(`type = $${paramIndex++}`);
268
+ params.push(type);
269
+ }
270
+ if (userId) {
271
+ whereClauses.push(`user_id = $${paramIndex++}`);
272
+ params.push(userId);
273
+ }
274
+ const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(' AND ')}` : '';
275
+ const orderDirection = sort === 'desc' ? 'DESC' : 'ASC';
276
+ const query = `
277
+ SELECT id, type, timestamp, status, user_id, session_id, organization_id,
278
+ metadata, ip_address, user_agent, source, display_message, display_severity
279
+ FROM ${schema}.${tableName}
280
+ ${whereClause}
281
+ ORDER BY timestamp ${orderDirection}, id ${orderDirection}
282
+ LIMIT $${paramIndex++}
283
+ `;
284
+ params.push(limit + 1); // Get one extra to check hasMore
285
+ try {
286
+ const result = await queryFn(query, params);
287
+ const rows = result.rows || result || [];
288
+ const hasMore = rows.length > limit;
289
+ const events = rows.slice(0, limit).map((row) => ({
290
+ id: row.id,
291
+ type: row.type,
292
+ timestamp: new Date(row.timestamp),
293
+ status: row.status || 'success',
294
+ userId: row.user_id,
295
+ sessionId: row.session_id,
296
+ organizationId: row.organization_id,
297
+ metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata || {},
298
+ ipAddress: row.ip_address,
299
+ userAgent: row.user_agent,
300
+ source: row.source || 'app',
301
+ display: {
302
+ message: row.display_message || row.type,
303
+ severity: row.display_severity || 'info',
304
+ },
305
+ }));
306
+ return {
307
+ events,
308
+ hasMore,
309
+ nextCursor: hasMore ? events[events.length - 1].id : null,
310
+ };
311
+ }
312
+ catch (error) {
313
+ // If table doesn't exist, return empty result instead of throwing
314
+ if (error?.message?.includes('does not exist') || error?.code === '42P01') {
315
+ return {
316
+ events: [],
317
+ hasMore: false,
318
+ nextCursor: null,
319
+ };
320
+ }
321
+ throw error;
322
+ }
323
+ },
324
+ };
325
+ }
326
+ export function createClickHouseProvider(options) {
327
+ const { client, table = 'auth_events', database } = options;
328
+ const ensureTable = async () => {
329
+ if (!client)
330
+ return;
331
+ try {
332
+ const tableFullName = database ? `${database}.${table}` : table;
333
+ const createTableQuery = `
334
+ CREATE TABLE IF NOT EXISTS ${tableFullName} (
335
+ id UUID,
336
+ type String,
337
+ timestamp DateTime,
338
+ status String DEFAULT 'success',
339
+ user_id Nullable(String),
340
+ session_id Nullable(String),
341
+ organization_id Nullable(String),
342
+ metadata String,
343
+ ip_address Nullable(String),
344
+ user_agent Nullable(String),
345
+ source String DEFAULT 'app',
346
+ display_message Nullable(String),
347
+ display_severity Nullable(String),
348
+ created_at DateTime DEFAULT now()
349
+ ) ENGINE = MergeTree()
350
+ ORDER BY (timestamp, type)
351
+ PARTITION BY toYYYYMM(timestamp);
352
+ `;
353
+ if (client.exec) {
354
+ const result = await client.exec({ query: createTableQuery });
355
+ console.log(`✅ Ensured ${tableFullName} table exists in ClickHouse`);
356
+ }
357
+ else if (client.query) {
358
+ await client.query({ query: createTableQuery });
359
+ console.log(`✅ Ensured ${tableFullName} table exists in ClickHouse`);
360
+ }
361
+ else {
362
+ console.warn(`⚠️ ClickHouse client doesn't support exec or query methods`);
363
+ }
364
+ }
365
+ catch (error) {
366
+ if (error?.message?.includes('already exists') || error?.code === 57) {
367
+ return;
368
+ }
369
+ console.error(`Failed to ensure ${table} table in ClickHouse:`, error);
370
+ }
371
+ };
372
+ let tableEnsured = false;
373
+ let tableEnsuring = false;
374
+ const ensureTableSync = async () => {
375
+ if (tableEnsured || tableEnsuring)
376
+ return;
377
+ tableEnsuring = true;
378
+ try {
379
+ await ensureTable();
380
+ tableEnsured = true;
381
+ }
382
+ catch (error) {
383
+ console.error('Failed to ensure table:', error);
384
+ }
385
+ finally {
386
+ tableEnsuring = false;
387
+ }
388
+ };
389
+ // Call ensureTable asynchronously (don't block initialization)
390
+ ensureTableSync().catch(console.error);
391
+ const ingestBatchFn = async (events) => {
392
+ if (events.length === 0)
393
+ return;
394
+ if (!tableEnsured) {
395
+ await ensureTableSync();
396
+ }
397
+ const tableFullName = database ? `${database}.${table}` : table;
398
+ const rows = events.map((event) => ({
399
+ id: event.id,
400
+ type: event.type,
401
+ timestamp: event.timestamp,
402
+ status: event.status || 'success',
403
+ user_id: event.userId || '',
404
+ session_id: event.sessionId || '',
405
+ organization_id: event.organizationId || '',
406
+ metadata: JSON.stringify(event.metadata || {}),
407
+ ip_address: event.ipAddress || '',
408
+ user_agent: event.userAgent || '',
409
+ source: event.source,
410
+ display_message: event.display?.message || '',
411
+ display_severity: event.display?.severity || '',
412
+ }));
413
+ try {
414
+ if (client.insert) {
415
+ await client.insert({
416
+ table: tableFullName,
417
+ values: rows,
418
+ format: 'JSONEachRow',
419
+ });
420
+ console.log(`✅ Inserted ${rows.length} event(s) into ClickHouse ${tableFullName}`);
421
+ }
422
+ else {
423
+ // Fallback: use INSERT query
424
+ const values = rows
425
+ .map((row) => `('${row.id}', '${row.type}', '${new Date(row.timestamp).toISOString().replace('T', ' ').slice(0, 19)}', '${row.status || 'success'}', ${row.user_id ? `'${row.user_id.replace(/'/g, "''")}'` : 'NULL'}, ${row.session_id ? `'${row.session_id.replace(/'/g, "''")}'` : 'NULL'}, ${row.organization_id ? `'${row.organization_id.replace(/'/g, "''")}'` : 'NULL'}, '${row.metadata.replace(/'/g, "''")}', ${row.ip_address ? `'${row.ip_address.replace(/'/g, "''")}'` : 'NULL'}, ${row.user_agent ? `'${row.user_agent.replace(/'/g, "''")}'` : 'NULL'}, '${row.source}', ${row.display_message ? `'${row.display_message.replace(/'/g, "''")}'` : 'NULL'}, ${row.display_severity ? `'${row.display_severity}'` : 'NULL'})`)
426
+ .join(', ');
427
+ const insertQuery = `
428
+ INSERT INTO ${tableFullName}
429
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
430
+ VALUES ${values}
431
+ `;
432
+ if (client.exec) {
433
+ await client.exec({ query: insertQuery });
434
+ }
435
+ else if (client.query) {
436
+ await client.query({ query: insertQuery });
437
+ }
438
+ else {
439
+ throw new Error('ClickHouse client does not support insert, exec, or query methods');
440
+ }
441
+ console.log(`✅ Inserted ${rows.length} event(s) into ClickHouse ${tableFullName} via query`);
442
+ }
443
+ }
444
+ catch (error) {
445
+ console.error(`❌ Failed to insert events into ClickHouse ${tableFullName}:`, error);
446
+ throw error;
447
+ }
448
+ };
449
+ return {
450
+ async ingest(event) {
451
+ await ingestBatchFn([event]);
452
+ },
453
+ async ingestBatch(events) {
454
+ await ingestBatchFn(events);
455
+ },
456
+ async query(options) {
457
+ const { limit = 20, after, sort = 'desc', type, userId } = options;
458
+ const tableFullName = database ? `${database}.${table}` : table;
459
+ try {
460
+ const checkTableQuery = `EXISTS TABLE ${tableFullName}`;
461
+ let tableExists = false;
462
+ if (client.query) {
463
+ try {
464
+ const checkResult = await client.query({
465
+ query: checkTableQuery,
466
+ format: 'JSONEachRow',
467
+ });
468
+ const rows = await checkResult.json();
469
+ tableExists =
470
+ rows && rows.length > 0 && (rows[0]?.result === 1 || rows[0]?.exists === 1);
471
+ }
472
+ catch {
473
+ tableExists = false;
474
+ }
475
+ }
476
+ else if (client.exec) {
477
+ try {
478
+ const checkResult = await client.exec({ query: checkTableQuery });
479
+ tableExists = checkResult === '1' || String(checkResult).includes('1');
480
+ }
481
+ catch {
482
+ tableExists = false;
483
+ }
484
+ }
485
+ // If table doesn't exist, try to create it
486
+ if (!tableExists) {
487
+ const createTableQuery = `
488
+ CREATE TABLE IF NOT EXISTS ${tableFullName} (
489
+ id UUID,
490
+ type String,
491
+ timestamp DateTime,
492
+ status String DEFAULT 'success',
493
+ user_id Nullable(String),
494
+ session_id Nullable(String),
495
+ organization_id Nullable(String),
496
+ metadata String,
497
+ ip_address Nullable(String),
498
+ user_agent Nullable(String),
499
+ source String DEFAULT 'app',
500
+ display_message Nullable(String),
501
+ display_severity Nullable(String),
502
+ created_at DateTime DEFAULT now()
503
+ ) ENGINE = MergeTree()
504
+ ORDER BY (timestamp, type)
505
+ PARTITION BY toYYYYMM(timestamp);
506
+ `;
507
+ if (client.exec) {
508
+ await client.exec({ query: createTableQuery });
509
+ }
510
+ else if (client.query) {
511
+ await client.query({ query: createTableQuery });
512
+ }
513
+ }
514
+ else {
515
+ // Table exists, check if status column exists
516
+ try {
517
+ const checkColumnQuery = `
518
+ SELECT count() as exists
519
+ FROM system.columns
520
+ WHERE database = currentDatabase()
521
+ AND table = '${table}'
522
+ AND name = 'status'
523
+ `;
524
+ let columnExists = false;
525
+ if (client.query) {
526
+ try {
527
+ const columnResult = await client.query({
528
+ query: checkColumnQuery,
529
+ format: 'JSONEachRow',
530
+ });
531
+ const columnRows = await columnResult.json();
532
+ columnExists = columnRows && columnRows.length > 0 && columnRows[0]?.exists > 0;
533
+ }
534
+ catch {
535
+ columnExists = false;
536
+ }
537
+ }
538
+ else if (client.exec) {
539
+ try {
540
+ const columnResult = await client.exec({ query: checkColumnQuery });
541
+ columnExists = String(columnResult).includes('1') || columnResult === '1';
542
+ }
543
+ catch {
544
+ columnExists = false;
545
+ }
546
+ }
547
+ if (!columnExists) {
548
+ const addColumnQuery = `ALTER TABLE ${tableFullName} ADD COLUMN IF NOT EXISTS status String DEFAULT 'success'`;
549
+ try {
550
+ if (client.exec) {
551
+ await client.exec({ query: addColumnQuery });
552
+ }
553
+ else if (client.query) {
554
+ await client.query({ query: addColumnQuery });
555
+ }
556
+ console.log(`✅ Added status column to ${tableFullName}`);
557
+ }
558
+ catch (alterError) {
559
+ console.warn(`Failed to add status column to ${tableFullName}:`, alterError);
560
+ // Continue anyway - we'll handle missing column in query
561
+ }
562
+ }
563
+ }
564
+ catch (checkError) {
565
+ console.warn(`Failed to check for status column:`, checkError);
566
+ }
567
+ }
568
+ }
569
+ catch (error) {
570
+ if (!error?.message?.includes('already exists') && error?.code !== 57) {
571
+ console.warn(`Failed to ensure ClickHouse table ${tableFullName}:`, error);
572
+ }
573
+ }
574
+ const whereClauses = [];
575
+ if (after) {
576
+ if (sort === 'desc') {
577
+ whereClauses.push(`id < '${String(after).replace(/'/g, "''")}'`);
578
+ }
579
+ else {
580
+ whereClauses.push(`id > '${String(after).replace(/'/g, "''")}'`);
581
+ }
582
+ }
583
+ if (type) {
584
+ whereClauses.push(`type = '${String(type).replace(/'/g, "''")}'`);
585
+ }
586
+ if (userId) {
587
+ whereClauses.push(`user_id = '${String(userId).replace(/'/g, "''")}'`);
588
+ }
589
+ const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(' AND ')}` : '';
590
+ const orderDirection = sort === 'desc' ? 'DESC' : 'ASC';
591
+ // Try to query with status column first, fallback if it doesn't exist
592
+ let query = `
593
+ SELECT id, type, timestamp, status, user_id, session_id, organization_id,
594
+ metadata, ip_address, user_agent, source, display_message, display_severity
595
+ FROM ${tableFullName}
596
+ ${whereClause}
597
+ ORDER BY timestamp ${orderDirection}, id ${orderDirection}
598
+ LIMIT ${limit + 1}
599
+ `;
600
+ let result;
601
+ let hasStatusColumn = true;
602
+ try {
603
+ if (client.query) {
604
+ const queryResult = await client.query({ query, format: 'JSONEachRow' });
605
+ result = await queryResult.json();
606
+ }
607
+ else if (client.exec) {
608
+ const execResult = await client.exec({ query, format: 'JSONEachRow' });
609
+ result = typeof execResult === 'string' ? JSON.parse(execResult) : execResult;
610
+ }
611
+ else {
612
+ throw new Error('ClickHouse client does not support query or exec methods');
613
+ }
614
+ }
615
+ catch (error) {
616
+ // If error is about missing status column, retry without it
617
+ if (error?.message?.includes('Unknown expression identifier') &&
618
+ error?.message?.includes('status')) {
619
+ console.warn(`Status column not found in ${tableFullName}, querying without it`);
620
+ hasStatusColumn = false;
621
+ // Retry query without status column
622
+ query = `
623
+ SELECT id, type, timestamp, user_id, session_id, organization_id,
624
+ metadata, ip_address, user_agent, source, display_message, display_severity
625
+ FROM ${tableFullName}
626
+ ${whereClause}
627
+ ORDER BY timestamp ${orderDirection}, id ${orderDirection}
628
+ LIMIT ${limit + 1}
629
+ `;
630
+ try {
631
+ if (client.query) {
632
+ const queryResult = await client.query({ query, format: 'JSONEachRow' });
633
+ result = await queryResult.json();
634
+ }
635
+ else if (client.exec) {
636
+ const execResult = await client.exec({ query, format: 'JSONEachRow' });
637
+ result = typeof execResult === 'string' ? JSON.parse(execResult) : execResult;
638
+ }
639
+ }
640
+ catch (retryError) {
641
+ if (retryError?.message?.includes("doesn't exist") || retryError?.code === 60) {
642
+ return {
643
+ events: [],
644
+ hasMore: false,
645
+ nextCursor: null,
646
+ };
647
+ }
648
+ throw retryError;
649
+ }
650
+ }
651
+ else if (error?.message?.includes("doesn't exist") || error?.code === 60) {
652
+ return {
653
+ events: [],
654
+ hasMore: false,
655
+ nextCursor: null,
656
+ };
657
+ }
658
+ else {
659
+ throw error;
660
+ }
661
+ }
662
+ // Handle case where result might be an array or object
663
+ const rows = Array.isArray(result) ? result : result?.data || [];
664
+ const hasMore = rows.length > limit;
665
+ const events = rows.slice(0, limit).map((row) => ({
666
+ id: row.id,
667
+ type: row.type,
668
+ timestamp: new Date(row.timestamp),
669
+ status: hasStatusColumn ? row.status || 'success' : 'success', // Default to 'success' if column doesn't exist
670
+ userId: row.user_id || undefined,
671
+ sessionId: row.session_id || undefined,
672
+ organizationId: row.organization_id || undefined,
673
+ metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata || {},
674
+ ipAddress: row.ip_address || undefined,
675
+ userAgent: row.user_agent || undefined,
676
+ source: row.source || 'app',
677
+ display: {
678
+ message: row.display_message || row.type,
679
+ severity: row.display_severity || 'info',
680
+ },
681
+ }));
682
+ return {
683
+ events,
684
+ hasMore,
685
+ nextCursor: hasMore ? events[events.length - 1].id : null,
686
+ };
687
+ },
688
+ };
689
+ }
690
+ export function createHttpProvider(options) {
691
+ const { url, client = fetch, headers = {}, transform } = options;
692
+ return {
693
+ async ingest(event) {
694
+ const payload = transform ? transform(event) : event;
695
+ await client(url, {
696
+ method: 'POST',
697
+ headers: { 'Content-Type': 'application/json', ...headers },
698
+ body: JSON.stringify(payload),
699
+ });
700
+ },
701
+ async ingestBatch(events) {
702
+ const payload = events.map((event) => (transform ? transform(event) : event));
703
+ await client(url, {
704
+ method: 'POST',
705
+ headers: { 'Content-Type': 'application/json', ...headers },
706
+ body: JSON.stringify({ events: payload }),
707
+ });
708
+ },
709
+ };
710
+ }
711
+ export function createStorageProvider(options) {
712
+ const { adapter, tableName = 'auth_events' } = options;
713
+ // Ensure table exists (for Prisma/Drizzle adapters)
714
+ const ensureTable = async () => {
715
+ if (!adapter)
716
+ return;
717
+ try {
718
+ if (adapter.findMany) {
719
+ await adapter.findMany({
720
+ model: tableName,
721
+ limit: 1,
722
+ });
723
+ return;
724
+ }
725
+ }
726
+ catch (error) {
727
+ console.warn(`Table ${tableName} may not exist. Please create it manually or run migrations.`);
728
+ console.warn('SQL schema for reference:');
729
+ console.warn(`
730
+ CREATE TABLE IF NOT EXISTS ${tableName} (
731
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
732
+ type VARCHAR(100) NOT NULL,
733
+ timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
734
+ status VARCHAR(20) NOT NULL DEFAULT 'success',
735
+ user_id VARCHAR(255),
736
+ session_id VARCHAR(255),
737
+ organization_id VARCHAR(255),
738
+ metadata JSONB DEFAULT '{}',
739
+ ip_address INET,
740
+ user_agent TEXT,
741
+ source VARCHAR(50) DEFAULT 'app',
742
+ display_message TEXT,
743
+ display_severity VARCHAR(20),
744
+ created_at TIMESTAMPTZ DEFAULT NOW()
745
+ );
746
+
747
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_user_id ON ${tableName}(user_id);
748
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_type ON ${tableName}(type);
749
+ CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp DESC);
750
+ `);
751
+ }
752
+ };
753
+ ensureTable().catch(console.error);
754
+ return {
755
+ async ingest(event) {
756
+ if (adapter.create) {
757
+ await adapter.create({
758
+ model: tableName,
759
+ data: {
760
+ id: event.id,
761
+ type: event.type,
762
+ timestamp: event.timestamp,
763
+ status: event.status || 'success',
764
+ userId: event.userId,
765
+ sessionId: event.sessionId,
766
+ organizationId: event.organizationId,
767
+ metadata: event.metadata || {},
768
+ ipAddress: event.ipAddress,
769
+ userAgent: event.userAgent,
770
+ source: event.source,
771
+ displayMessage: event.display?.message,
772
+ displaySeverity: event.display?.severity,
773
+ },
774
+ });
775
+ }
776
+ else if (adapter.insert) {
777
+ await adapter.insert({
778
+ table: tableName,
779
+ values: {
780
+ id: event.id,
781
+ type: event.type,
782
+ timestamp: event.timestamp,
783
+ status: event.status || 'success',
784
+ user_id: event.userId,
785
+ session_id: event.sessionId,
786
+ organization_id: event.organizationId,
787
+ metadata: JSON.stringify(event.metadata || {}),
788
+ ip_address: event.ipAddress,
789
+ user_agent: event.userAgent,
790
+ source: event.source,
791
+ display_message: event.display?.message,
792
+ display_severity: event.display?.severity,
793
+ },
794
+ });
795
+ }
796
+ },
797
+ async ingestBatch(events) {
798
+ if (adapter.createMany) {
799
+ await adapter.createMany({
800
+ model: tableName,
801
+ data: events.map((event) => ({
802
+ id: event.id,
803
+ type: event.type,
804
+ timestamp: event.timestamp,
805
+ status: event.status || 'success',
806
+ userId: event.userId,
807
+ sessionId: event.sessionId,
808
+ organizationId: event.organizationId,
809
+ metadata: event.metadata || {},
810
+ ipAddress: event.ipAddress,
811
+ userAgent: event.userAgent,
812
+ source: event.source,
813
+ displayMessage: event.display?.message,
814
+ displaySeverity: event.display?.severity,
815
+ })),
816
+ });
817
+ }
818
+ else {
819
+ await Promise.all(events.map((event) => this.ingest(event)));
820
+ }
821
+ },
822
+ async query(options) {
823
+ const { limit = 20, after, sort = 'desc', type, userId } = options;
824
+ if (!adapter || !adapter.findMany) {
825
+ throw new Error('Adapter does not support findMany');
826
+ }
827
+ const where = [];
828
+ if (after) {
829
+ if (sort === 'desc') {
830
+ where.push({ field: 'id', operator: '<', value: after });
831
+ }
832
+ else {
833
+ where.push({ field: 'id', operator: '>', value: after });
834
+ }
835
+ }
836
+ if (type) {
837
+ where.push({ field: 'type', value: type });
838
+ }
839
+ if (userId) {
840
+ where.push({ field: 'userId', value: userId });
841
+ }
842
+ const events = await adapter.findMany({
843
+ model: tableName,
844
+ where,
845
+ orderBy: [{ field: 'timestamp', direction: sort === 'desc' ? 'desc' : 'asc' }],
846
+ limit: limit + 1, // Get one extra to check hasMore
847
+ });
848
+ const hasMore = events.length > limit;
849
+ const paginatedEvents = events.slice(0, limit).map((event) => ({
850
+ id: event.id,
851
+ type: event.type,
852
+ timestamp: new Date(event.timestamp || event.createdAt),
853
+ status: event.status || 'success',
854
+ userId: event.userId || event.user_id,
855
+ sessionId: event.sessionId || event.session_id,
856
+ organizationId: event.organizationId || event.organization_id,
857
+ metadata: typeof event.metadata === 'string' ? JSON.parse(event.metadata) : event.metadata || {},
858
+ ipAddress: event.ipAddress || event.ip_address,
859
+ userAgent: event.userAgent || event.user_agent,
860
+ source: event.source || 'app',
861
+ display: {
862
+ message: event.displayMessage || event.display_message || event.type,
863
+ severity: event.displaySeverity || event.display_severity || 'info',
864
+ },
865
+ }));
866
+ return {
867
+ events: paginatedEvents,
868
+ hasMore,
869
+ nextCursor: hasMore ? paginatedEvents[paginatedEvents.length - 1].id : null,
870
+ };
871
+ },
872
+ };
873
+ }
874
+ //# sourceMappingURL=helpers.js.map