better-auth-studio 1.1.1-beta.7 → 1.1.1-beta.9

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 (57) hide show
  1. package/dist/adapters/astro.d.ts.map +1 -1
  2. package/dist/adapters/astro.js +4 -0
  3. package/dist/adapters/astro.js.map +1 -1
  4. package/dist/adapters/elysia.d.ts.map +1 -1
  5. package/dist/adapters/elysia.js +4 -0
  6. package/dist/adapters/elysia.js.map +1 -1
  7. package/dist/adapters/express.d.ts.map +1 -1
  8. package/dist/adapters/express.js +4 -0
  9. package/dist/adapters/express.js.map +1 -1
  10. package/dist/adapters/hono.d.ts.map +1 -1
  11. package/dist/adapters/hono.js +0 -1
  12. package/dist/adapters/hono.js.map +1 -1
  13. package/dist/adapters/nextjs.d.ts.map +1 -1
  14. package/dist/adapters/nextjs.js +4 -0
  15. package/dist/adapters/nextjs.js.map +1 -1
  16. package/dist/adapters/nuxt.d.ts +6 -6
  17. package/dist/adapters/nuxt.d.ts.map +1 -1
  18. package/dist/adapters/nuxt.js +63 -44
  19. package/dist/adapters/nuxt.js.map +1 -1
  20. package/dist/adapters/remix.d.ts.map +1 -1
  21. package/dist/adapters/remix.js +4 -0
  22. package/dist/adapters/remix.js.map +1 -1
  23. package/dist/adapters/solid-start.d.ts.map +1 -1
  24. package/dist/adapters/solid-start.js +4 -0
  25. package/dist/adapters/solid-start.js.map +1 -1
  26. package/dist/adapters/svelte-kit.d.ts.map +1 -1
  27. package/dist/adapters/svelte-kit.js +4 -0
  28. package/dist/adapters/svelte-kit.js.map +1 -1
  29. package/dist/adapters/tanstack-start.d.ts.map +1 -1
  30. package/dist/adapters/tanstack-start.js +4 -0
  31. package/dist/adapters/tanstack-start.js.map +1 -1
  32. package/dist/core/handler.d.ts +5 -0
  33. package/dist/core/handler.d.ts.map +1 -1
  34. package/dist/core/handler.js +33 -22
  35. package/dist/core/handler.js.map +1 -1
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/providers/events/helpers.d.ts +5 -0
  41. package/dist/providers/events/helpers.d.ts.map +1 -1
  42. package/dist/providers/events/helpers.js +459 -124
  43. package/dist/providers/events/helpers.js.map +1 -1
  44. package/dist/types/handler.d.ts +1 -1
  45. package/dist/types/handler.d.ts.map +1 -1
  46. package/dist/utils/event-ingestion.d.ts.map +1 -1
  47. package/dist/utils/event-ingestion.js +28 -28
  48. package/dist/utils/event-ingestion.js.map +1 -1
  49. package/dist/utils/hook-injector.d.ts +0 -3
  50. package/dist/utils/hook-injector.d.ts.map +1 -1
  51. package/dist/utils/hook-injector.js +65 -4
  52. package/dist/utils/hook-injector.js.map +1 -1
  53. package/dist/utils/server-init.d.ts +10 -0
  54. package/dist/utils/server-init.d.ts.map +1 -0
  55. package/dist/utils/server-init.js +40 -0
  56. package/dist/utils/server-init.js.map +1 -0
  57. package/package.json +1 -1
@@ -1,41 +1,75 @@
1
1
  export function createPostgresProvider(options) {
2
- const { client, tableName = 'auth_events', schema = 'public' } = options;
2
+ const { client, tableName = 'auth_events', schema = 'public', clientType } = options;
3
3
  // Validate client is provided
4
4
  if (!client) {
5
5
  throw new Error('Postgres client is required. Provide a pg Pool, Client, or Prisma client instance.');
6
6
  }
7
- // Validate client has required methods (either query for pg or $executeRaw for Prisma)
8
- if (!client.query && !client.$executeRaw) {
9
- throw new Error('Invalid Postgres client. Client must have either a `query` method (pg Pool/Client) or `$executeRaw` method (Prisma client).');
7
+ let actualClient = client;
8
+ if (clientType === 'drizzle') {
9
+ if (client.$client) {
10
+ actualClient = client.$client;
11
+ }
12
+ else if (client.client) {
13
+ // Fallback for older Drizzle versions
14
+ actualClient = client.client;
15
+ }
16
+ }
17
+ const hasQuery = actualClient?.query || client?.query;
18
+ const hasExecuteRaw = client?.$executeRaw;
19
+ const hasExecute = client?.execute;
20
+ const hasUnsafe = actualClient?.unsafe || client?.$client?.unsafe || client?.unsafe;
21
+ if (!hasQuery && !hasExecuteRaw && !hasExecute && !hasUnsafe) {
22
+ const errorMessage = clientType === 'prisma'
23
+ ? 'Invalid Prisma client. Client must have a `$executeRaw` method.'
24
+ : clientType === 'drizzle'
25
+ ? '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).';
27
+ throw new Error(errorMessage);
10
28
  }
11
29
  // Ensure table exists
12
30
  const ensureTable = async () => {
13
31
  if (!client)
14
32
  return;
15
33
  try {
16
- // Support different Postgres client types (pg, postgres.js, etc.)
17
- const queryFn = client.query || (typeof client === 'function' ? client : null);
18
- if (!queryFn) {
19
- console.warn(`⚠️ Postgres client doesn't support query method. Table ${schema}.${tableName} must be created manually.`);
20
- return;
21
- }
22
- // Support Prisma client ($executeRaw) or standard pg client (query)
34
+ // Support Prisma client ($executeRaw), Drizzle instance ($client), or standard pg client (query)
23
35
  let executeQuery;
24
- if (client.$executeRaw) {
36
+ if (clientType === 'prisma' || client.$executeRaw) {
25
37
  // Prisma client
26
38
  executeQuery = async (query) => {
27
39
  return await client.$executeRawUnsafe(query);
28
40
  };
29
41
  }
42
+ else if (clientType === 'drizzle') {
43
+ // Drizzle client - use $client which exposes the underlying client
44
+ if (actualClient?.unsafe) {
45
+ // postgres-js client via db.$client.unsafe()
46
+ executeQuery = async (query) => {
47
+ return await actualClient.unsafe(query);
48
+ };
49
+ }
50
+ else if (actualClient?.query) {
51
+ // pg Pool/Client via db.$client.query()
52
+ executeQuery = async (query) => {
53
+ return await actualClient.query(query);
54
+ };
55
+ }
56
+ else {
57
+ throw new Error(`Drizzle client.$client doesn't expose 'unsafe' (postgres-js) or 'query' (pg) method. Available methods: ${Object.keys(actualClient || {}).join(', ')}`);
58
+ }
59
+ }
60
+ else if (actualClient?.query) {
61
+ // Standard pg client (Pool or Client)
62
+ executeQuery = async (query) => {
63
+ return await actualClient.query(query);
64
+ };
65
+ }
30
66
  else if (client.query) {
31
- // Standard pg client
32
67
  executeQuery = async (query) => {
33
68
  return await client.query(query);
34
69
  };
35
70
  }
36
71
  else {
37
- console.warn(`⚠️ Postgres client doesn't support $executeRaw or query method. Table ${schema}.${tableName} must be created manually.`);
38
- return;
72
+ throw new Error(`Postgres client doesn't support $executeRaw or query method. Available properties: ${Object.keys(client).join(', ')}`);
39
73
  }
40
74
  // Use CREATE TABLE IF NOT EXISTS (simpler and more reliable)
41
75
  const createTableQuery = `
@@ -68,22 +102,16 @@ export function createPostgresProvider(options) {
68
102
  try {
69
103
  await executeQuery(indexQuery);
70
104
  }
71
- catch (err) {
72
- // Index might already exist, ignore
73
- }
105
+ catch (err) { }
74
106
  }
75
- console.log(`✅ Ensured ${schema}.${tableName} table exists for events`);
76
107
  }
77
108
  catch (error) {
78
- // If table already exists, that's fine
79
109
  if (error?.message?.includes('already exists') || error?.code === '42P07') {
80
110
  return;
81
111
  }
82
112
  console.error(`Failed to ensure ${schema}.${tableName} table:`, error);
83
- // Don't throw - allow provider to work even if table creation fails
84
113
  }
85
114
  };
86
- // Track if table creation is in progress or completed
87
115
  let tableEnsured = false;
88
116
  let tableEnsuringPromise = null;
89
117
  const ensureTableSync = async () => {
@@ -112,12 +140,8 @@ export function createPostgresProvider(options) {
112
140
  ensureTableSync().catch(console.error);
113
141
  return {
114
142
  async ingest(event) {
115
- if (!tableEnsured) {
116
- await ensureTableSync();
117
- }
118
- // Support Prisma client ($executeRaw) or standard pg client (query/Pool)
119
- if (client.$executeRaw) {
120
- // Prisma client - use $executeRawUnsafe for parameterized queries
143
+ await ensureTableSync();
144
+ if (clientType === 'prisma' || client.$executeRaw) {
121
145
  const query = `
122
146
  INSERT INTO ${schema}.${tableName}
123
147
  (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
@@ -128,52 +152,11 @@ export function createPostgresProvider(options) {
128
152
  }
129
153
  catch (error) {
130
154
  console.error(`Failed to insert event (${event.type}) into ${schema}.${tableName}:`, error);
131
- throw error;
132
- }
133
- }
134
- else if (client.query) {
135
- // Standard pg client (Pool or Client) - use parameterized queries for safety
136
- try {
137
- await client.query(`INSERT INTO ${schema}.${tableName}
138
- (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
139
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, [
140
- event.id,
141
- event.type,
142
- event.timestamp,
143
- event.status || 'success',
144
- event.userId || null,
145
- event.sessionId || null,
146
- event.organizationId || null,
147
- JSON.stringify(event.metadata || {}),
148
- event.ipAddress || null,
149
- event.userAgent || null,
150
- event.source,
151
- event.display?.message || null,
152
- event.display?.severity || null,
153
- ]);
154
- }
155
- catch (error) {
156
- console.error(`Failed to insert event (${event.type}) into ${schema}.${tableName}:`, error);
157
- if (error.code === '42P01') {
155
+ if (error.code === '42P01' || error.meta?.code === '42P01' || error.code === 'P2010') {
156
+ tableEnsured = false;
158
157
  await ensureTableSync();
159
158
  try {
160
- await client.query(`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 ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, [
163
- event.id,
164
- event.type,
165
- event.timestamp,
166
- event.status || 'success',
167
- event.userId || null,
168
- event.sessionId || null,
169
- event.organizationId || null,
170
- JSON.stringify(event.metadata || {}),
171
- event.ipAddress || null,
172
- event.userAgent || null,
173
- event.source,
174
- event.display?.message || null,
175
- event.display?.severity || null,
176
- ]);
159
+ await client.$executeRawUnsafe(query);
177
160
  return;
178
161
  }
179
162
  catch (retryError) {
@@ -181,26 +164,114 @@ export function createPostgresProvider(options) {
181
164
  throw retryError;
182
165
  }
183
166
  }
184
- if (error.code === 'ECONNREFUSED' ||
185
- error.code === 'ETIMEDOUT' ||
186
- error.message?.includes('Connection terminated')) {
187
- if (client.end) {
188
- console.warn(`⚠️ Connection error with pg Pool. The pool will retry automatically on next query.`);
167
+ throw error;
168
+ }
169
+ }
170
+ else if ((actualClient && (actualClient.query || actualClient.unsafe)) ||
171
+ client.query ||
172
+ client.unsafe) {
173
+ // Standard pg client (Pool/Client with query) or postgres-js client (with unsafe) or Drizzle with underlying client
174
+ const queryClient = actualClient || client;
175
+ const useUnsafe = queryClient.unsafe && !queryClient.query; // Use unsafe if postgres-js (has unsafe but no query)
176
+ if (useUnsafe) {
177
+ // postgres-js client - use unsafe() for raw SQL
178
+ const query = `
179
+ INSERT INTO ${schema}.${tableName}
180
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
181
+ 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
+ `;
183
+ try {
184
+ await queryClient.unsafe(query);
185
+ }
186
+ catch (error) {
187
+ console.error(`Failed to insert event (${event.type}) into ${schema}.${tableName}:`, error);
188
+ if (error.code === '42P01') {
189
+ tableEnsured = false;
190
+ await ensureTableSync();
191
+ try {
192
+ await queryClient.unsafe(query);
193
+ return;
194
+ }
195
+ catch (retryError) {
196
+ console.error(`Retry after table creation also failed:`, retryError);
197
+ throw retryError;
198
+ }
189
199
  }
200
+ throw error;
201
+ }
202
+ }
203
+ else {
204
+ // pg Pool/Client - use parameterized queries
205
+ try {
206
+ await queryClient.query(`INSERT INTO ${schema}.${tableName}
207
+ (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
+ ]);
223
+ }
224
+ catch (error) {
225
+ console.error(`Failed to insert event (${event.type}) into ${schema}.${tableName}:`, error);
226
+ if (error.code === '42P01') {
227
+ tableEnsured = false;
228
+ await ensureTableSync();
229
+ try {
230
+ await queryClient.query(`INSERT INTO ${schema}.${tableName}
231
+ (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
+ ]);
247
+ return;
248
+ }
249
+ catch (retryError) {
250
+ console.error(`Retry after table creation also failed:`, retryError);
251
+ throw retryError;
252
+ }
253
+ }
254
+ if (error.code === 'ECONNREFUSED' ||
255
+ error.code === 'ETIMEDOUT' ||
256
+ error.message?.includes('Connection terminated')) {
257
+ if (client.end) {
258
+ console.warn(`⚠️ Connection error with pg Pool. The pool will retry automatically on next query.`);
259
+ }
260
+ }
261
+ throw error;
190
262
  }
191
- throw error;
192
263
  }
193
264
  }
194
265
  else {
195
- throw new Error('Postgres client does not support $executeRaw or query method. Make sure you are passing a valid pg Pool, Client, or Prisma client.');
266
+ throw new Error('Postgres client does not support $executeRaw, query, or unsafe method. Make sure you are passing a valid pg Pool, Client, Prisma client, or Drizzle instance.');
196
267
  }
197
268
  },
198
269
  async ingestBatch(events) {
199
270
  if (events.length === 0)
200
271
  return;
201
272
  await ensureTableSync();
202
- // Support Prisma client ($executeRaw) or standard pg client (query)
203
- if (client.$executeRaw) {
273
+ // Support Prisma client ($executeRaw), Drizzle instance, or standard pg client (query)
274
+ if (clientType === 'prisma' || client.$executeRaw) {
204
275
  // Prisma client - use $executeRawUnsafe for batch inserts
205
276
  const CHUNK_SIZE = 500; // Reasonable chunk size for string-based queries
206
277
  for (let i = 0; i < events.length; i += CHUNK_SIZE) {
@@ -222,52 +293,81 @@ export function createPostgresProvider(options) {
222
293
  }
223
294
  }
224
295
  }
225
- else if (client.query) {
226
- // Standard pg client (Pool or Client)
227
- const PARAMS_PER_EVENT = 13;
228
- const MAX_PARAMS = 65535;
229
- const CHUNK_SIZE = Math.floor(MAX_PARAMS / PARAMS_PER_EVENT) - 1; // ~5000, but use 1000 for safety
230
- for (let chunkStart = 0; chunkStart < events.length; chunkStart += CHUNK_SIZE) {
231
- const chunk = events.slice(chunkStart, chunkStart + CHUNK_SIZE);
232
- const values = chunk
233
- .map((_, i) => {
234
- const base = i * PARAMS_PER_EVENT;
235
- 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})`;
236
- })
237
- .join(', ');
238
- const query = `
239
- INSERT INTO ${schema}.${tableName}
240
- (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
241
- VALUES ${values}
242
- `;
243
- const params = chunk.flatMap((event) => [
244
- event.id,
245
- event.type,
246
- event.timestamp,
247
- event.status || 'success',
248
- event.userId || null,
249
- event.sessionId || null,
250
- event.organizationId || null,
251
- JSON.stringify(event.metadata || {}),
252
- event.ipAddress || null,
253
- event.userAgent || null,
254
- event.source,
255
- event.display?.message || null,
256
- event.display?.severity || null,
257
- ]);
258
- try {
259
- await client.query(query, params);
296
+ else if ((actualClient && (actualClient.query || actualClient.unsafe)) ||
297
+ client.query ||
298
+ client.unsafe) {
299
+ // Standard pg client (Pool/Client with query) or postgres-js client (with unsafe) or Drizzle with underlying client
300
+ const batchQueryClient = actualClient || client;
301
+ const useUnsafe = batchQueryClient.unsafe && !batchQueryClient.query; // Use unsafe if postgres-js
302
+ if (useUnsafe) {
303
+ // postgres-js client - use unsafe() for raw SQL (similar to Prisma)
304
+ const CHUNK_SIZE = 500; // Reasonable chunk size for string-based queries
305
+ for (let i = 0; i < events.length; i += CHUNK_SIZE) {
306
+ const chunk = events.slice(i, i + CHUNK_SIZE);
307
+ const values = chunk
308
+ .map((event) => `('${event.id}', '${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'})`)
309
+ .join(', ');
310
+ const query = `
311
+ INSERT INTO ${schema}.${tableName}
312
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
313
+ VALUES ${values}
314
+ `;
315
+ try {
316
+ await batchQueryClient.unsafe(query);
317
+ }
318
+ catch (error) {
319
+ console.error(`Failed to insert batch chunk (${chunk.length} events):`, error);
320
+ throw error;
321
+ }
260
322
  }
261
- catch (error) {
262
- console.error(`Failed to insert batch chunk (${chunk.length} events) into ${schema}.${tableName}:`, error);
263
- if (error.code === 'ECONNREFUSED' ||
264
- error.code === 'ETIMEDOUT' ||
265
- error.message?.includes('Connection terminated')) {
266
- if (client.end) {
267
- console.warn(`⚠️ Connection error with pg Pool. The pool will retry automatically on next query.`);
323
+ }
324
+ else {
325
+ // pg Pool/Client - use parameterized queries
326
+ const PARAMS_PER_EVENT = 13;
327
+ const MAX_PARAMS = 65535;
328
+ const CHUNK_SIZE = Math.floor(MAX_PARAMS / PARAMS_PER_EVENT) - 1; // ~5000, but use 1000 for safety
329
+ for (let chunkStart = 0; chunkStart < events.length; chunkStart += CHUNK_SIZE) {
330
+ const chunk = events.slice(chunkStart, chunkStart + CHUNK_SIZE);
331
+ const values = chunk
332
+ .map((_, i) => {
333
+ const base = i * PARAMS_PER_EVENT;
334
+ 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})`;
335
+ })
336
+ .join(', ');
337
+ const query = `
338
+ INSERT INTO ${schema}.${tableName}
339
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
340
+ VALUES ${values}
341
+ `;
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
+ ]);
357
+ try {
358
+ await batchQueryClient.query(query, params);
359
+ }
360
+ catch (error) {
361
+ console.error(`Failed to insert batch chunk (${chunk.length} events) into ${schema}.${tableName}:`, error);
362
+ if (error.code === 'ECONNREFUSED' ||
363
+ error.code === 'ETIMEDOUT' ||
364
+ error.message?.includes('Connection terminated')) {
365
+ if (client.end) {
366
+ console.warn(`⚠️ Connection error with pg Pool. The pool will retry automatically on next query.`);
367
+ }
268
368
  }
369
+ throw error;
269
370
  }
270
- throw error;
271
371
  }
272
372
  }
273
373
  }
@@ -418,6 +518,241 @@ export function createPostgresProvider(options) {
418
518
  },
419
519
  };
420
520
  }
521
+ export function createSqliteProvider(options) {
522
+ const { client, tableName = 'auth_events' } = options;
523
+ // Validate client is provided
524
+ if (!client) {
525
+ throw new Error('SQLite client is required. Provide a better-sqlite3 Database instance.');
526
+ }
527
+ // Handle case where client might be a function (lazy initialization) or already initialized
528
+ const actualClient = typeof client === 'function' ? client() : client;
529
+ // If client initialization failed (native module not found), try to provide helpful error
530
+ if (!actualClient) {
531
+ throw new Error('SQLite client initialization failed. Make sure better-sqlite3 is properly installed and built. ' +
532
+ 'Run: pnpm rebuild better-sqlite3 or npm rebuild better-sqlite3');
533
+ }
534
+ // Validate client has required methods (better-sqlite3 Database)
535
+ const hasExec = typeof actualClient.exec === 'function';
536
+ const hasPrepare = typeof actualClient.prepare === 'function';
537
+ if (!hasExec || !hasPrepare) {
538
+ // If methods don't exist, it might be an initialization error - provide helpful message
539
+ if (actualClient instanceof Error) {
540
+ throw new Error(`SQLite client initialization error: ${actualClient.message}. ` +
541
+ 'Make sure better-sqlite3 native module is built. Run: pnpm rebuild better-sqlite3');
542
+ }
543
+ throw new Error('Invalid SQLite client. Client must have `exec` and `prepare` methods (better-sqlite3 Database instance). ' +
544
+ 'If using better-sqlite3, make sure the native module is built: pnpm rebuild better-sqlite3');
545
+ }
546
+ // Ensure table exists
547
+ const ensureTable = async () => {
548
+ if (!client)
549
+ return;
550
+ try {
551
+ // Use CREATE TABLE IF NOT EXISTS (SQLite doesn't support schemas like Postgres)
552
+ const createTableQuery = `
553
+ CREATE TABLE IF NOT EXISTS ${tableName} (
554
+ id TEXT PRIMARY KEY,
555
+ type TEXT NOT NULL,
556
+ timestamp TEXT NOT NULL DEFAULT (datetime('now')),
557
+ status TEXT NOT NULL DEFAULT 'success',
558
+ user_id TEXT,
559
+ session_id TEXT,
560
+ organization_id TEXT,
561
+ metadata TEXT DEFAULT '{}',
562
+ ip_address TEXT,
563
+ user_agent TEXT,
564
+ source TEXT DEFAULT 'app',
565
+ display_message TEXT,
566
+ display_severity TEXT,
567
+ created_at TEXT DEFAULT (datetime('now'))
568
+ );
569
+ `;
570
+ actualClient.exec(createTableQuery);
571
+ const indexQueries = [
572
+ `CREATE INDEX IF NOT EXISTS idx_${tableName}_user_id ON ${tableName}(user_id)`,
573
+ `CREATE INDEX IF NOT EXISTS idx_${tableName}_type ON ${tableName}(type)`,
574
+ `CREATE INDEX IF NOT EXISTS idx_${tableName}_timestamp ON ${tableName}(timestamp DESC)`,
575
+ `CREATE INDEX IF NOT EXISTS idx_${tableName}_id_timestamp ON ${tableName}(id, timestamp DESC)`,
576
+ ];
577
+ for (const indexQuery of indexQueries) {
578
+ try {
579
+ actualClient.exec(indexQuery);
580
+ }
581
+ catch (err) {
582
+ // Index might already exist, ignore
583
+ }
584
+ }
585
+ }
586
+ catch (error) {
587
+ if (error?.message?.includes('already exists')) {
588
+ return;
589
+ }
590
+ console.error(`Failed to ensure ${tableName} table:`, error);
591
+ }
592
+ };
593
+ let tableEnsured = false;
594
+ let tableEnsuringPromise = null;
595
+ const ensureTableSync = async () => {
596
+ if (tableEnsured) {
597
+ return;
598
+ }
599
+ if (tableEnsuringPromise) {
600
+ return tableEnsuringPromise;
601
+ }
602
+ tableEnsuringPromise = (async () => {
603
+ try {
604
+ await ensureTable();
605
+ tableEnsured = true;
606
+ }
607
+ catch (error) {
608
+ tableEnsuringPromise = null;
609
+ throw error;
610
+ }
611
+ finally {
612
+ tableEnsuringPromise = null;
613
+ }
614
+ })();
615
+ return tableEnsuringPromise;
616
+ };
617
+ ensureTableSync().catch(console.error);
618
+ return {
619
+ async ingest(event) {
620
+ await ensureTableSync();
621
+ try {
622
+ const stmt = actualClient.prepare(`
623
+ INSERT INTO ${tableName}
624
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
625
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
626
+ `);
627
+ stmt.run(event.id, event.type, event.timestamp.toISOString(), event.status || 'success', event.userId || null, event.sessionId || null, event.organizationId || null, JSON.stringify(event.metadata || {}), event.ipAddress || null, event.userAgent || null, event.source, event.display?.message || null, event.display?.severity || null);
628
+ }
629
+ catch (error) {
630
+ console.error(`Failed to insert event (${event.type}) into ${tableName}:`, error);
631
+ // If table doesn't exist, try to create it and retry
632
+ if (error.message?.includes('no such table')) {
633
+ tableEnsured = false;
634
+ await ensureTableSync();
635
+ try {
636
+ const stmt = actualClient.prepare(`
637
+ INSERT INTO ${tableName}
638
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
639
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
640
+ `);
641
+ stmt.run(event.id, event.type, event.timestamp.toISOString(), event.status || 'success', event.userId || null, event.sessionId || null, event.organizationId || null, JSON.stringify(event.metadata || {}), event.ipAddress || null, event.userAgent || null, event.source, event.display?.message || null, event.display?.severity || null);
642
+ return;
643
+ }
644
+ catch (retryError) {
645
+ console.error(`Retry after table creation also failed:`, retryError);
646
+ throw retryError;
647
+ }
648
+ }
649
+ throw error;
650
+ }
651
+ },
652
+ async ingestBatch(events) {
653
+ if (events.length === 0)
654
+ return;
655
+ await ensureTableSync();
656
+ try {
657
+ const transaction = actualClient.transaction((events) => {
658
+ const stmt = actualClient.prepare(`
659
+ INSERT INTO ${tableName}
660
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
661
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
662
+ `);
663
+ for (const event of events) {
664
+ stmt.run(event.id, event.type, event.timestamp.toISOString(), event.status || 'success', event.userId || null, event.sessionId || null, event.organizationId || null, JSON.stringify(event.metadata || {}), event.ipAddress || null, event.userAgent || null, event.source, event.display?.message || null, event.display?.severity || null);
665
+ }
666
+ });
667
+ transaction(events);
668
+ }
669
+ catch (error) {
670
+ console.error(`Failed to insert batch (${events.length} events):`, error);
671
+ if (error.message?.includes('no such table')) {
672
+ tableEnsured = false;
673
+ await ensureTableSync();
674
+ try {
675
+ const transaction = actualClient.transaction((events) => {
676
+ const stmt = actualClient.prepare(`
677
+ INSERT INTO ${tableName}
678
+ (id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
679
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
680
+ `);
681
+ for (const event of events) {
682
+ stmt.run(event.id, event.type, event.timestamp.toISOString(), event.status || 'success', event.userId || null, event.sessionId || null, event.organizationId || null, JSON.stringify(event.metadata || {}), event.ipAddress || null, event.userAgent || null, event.source, event.display?.message || null, event.display?.severity || null);
683
+ }
684
+ });
685
+ transaction(events);
686
+ return;
687
+ }
688
+ catch (retryError) {
689
+ console.error(`Retry after table creation also failed:`, retryError);
690
+ throw retryError;
691
+ }
692
+ }
693
+ throw error;
694
+ }
695
+ },
696
+ async query(options) {
697
+ const { limit = 20, after, sort = 'desc', type, userId } = options;
698
+ await ensureTableSync();
699
+ try {
700
+ let query = `SELECT * FROM ${tableName} WHERE 1=1`;
701
+ const params = [];
702
+ if (type) {
703
+ query += ` AND type = ?`;
704
+ params.push(type);
705
+ }
706
+ if (userId) {
707
+ query += ` AND user_id = ?`;
708
+ params.push(userId);
709
+ }
710
+ if (after) {
711
+ query += ` AND id > ?`;
712
+ params.push(after);
713
+ }
714
+ query += ` ORDER BY timestamp ${sort === 'desc' ? 'DESC' : 'ASC'}`;
715
+ query += ` LIMIT ?`;
716
+ params.push(limit + 1); // Fetch one extra to check if there are more
717
+ const stmt = actualClient.prepare(query);
718
+ const rows = stmt.all(...params);
719
+ const hasMore = rows.length > limit;
720
+ const events = (hasMore ? rows.slice(0, limit) : rows).map((row) => ({
721
+ id: row.id,
722
+ type: row.type,
723
+ timestamp: new Date(row.timestamp),
724
+ status: row.status,
725
+ userId: row.user_id || undefined,
726
+ sessionId: row.session_id || undefined,
727
+ organizationId: row.organization_id || undefined,
728
+ metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata || {},
729
+ ipAddress: row.ip_address || undefined,
730
+ userAgent: row.user_agent || undefined,
731
+ source: row.source || 'app',
732
+ display: row.display_message || row.display_severity
733
+ ? {
734
+ message: row.display_message || undefined,
735
+ severity: row.display_severity || undefined,
736
+ }
737
+ : undefined,
738
+ }));
739
+ return {
740
+ events,
741
+ hasMore,
742
+ nextCursor: hasMore && events.length > 0 ? events[events.length - 1].id : null,
743
+ };
744
+ }
745
+ catch (error) {
746
+ if (error.message?.includes('no such table')) {
747
+ await ensureTableSync();
748
+ return { events: [], hasMore: false, nextCursor: null };
749
+ }
750
+ console.error(`Failed to query events from ${tableName}:`, error);
751
+ throw error;
752
+ }
753
+ },
754
+ };
755
+ }
421
756
  export function createClickHouseProvider(options) {
422
757
  const { client, table = 'auth_events', database } = options;
423
758
  const ensureTable = async () => {