better-auth-studio 1.1.2-beta.2 → 1.1.2-beta.21
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 +48 -78
- package/data/default-geo.json +1 -1
- package/dist/adapters/astro.d.ts +1 -1
- package/dist/adapters/astro.js +13 -13
- package/dist/adapters/elysia.d.ts +2 -2
- package/dist/adapters/elysia.js +13 -13
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +4 -4
- package/dist/adapters/hono.d.ts +2 -2
- package/dist/adapters/hono.js +12 -12
- package/dist/adapters/nextjs.d.ts +1 -1
- package/dist/adapters/nextjs.js +10 -10
- package/dist/adapters/nuxt.d.ts +1 -1
- package/dist/adapters/nuxt.js +22 -22
- package/dist/adapters/remix.d.ts +1 -1
- package/dist/adapters/remix.js +13 -13
- package/dist/adapters/solid-start.d.ts +1 -1
- package/dist/adapters/solid-start.js +13 -13
- package/dist/adapters/svelte-kit.d.ts +2 -2
- package/dist/adapters/svelte-kit.js +13 -13
- package/dist/adapters/tanstack-start.d.ts +1 -1
- package/dist/adapters/tanstack-start.js +13 -13
- package/dist/add-svelte-kit-env-modules.js +11 -11
- package/dist/auth-adapter.d.ts +1 -1
- package/dist/auth-adapter.js +96 -96
- package/dist/cli/commands/init.js +57 -57
- package/dist/cli.js +75 -75
- package/dist/config.d.ts +5 -5
- package/dist/config.js +37 -37
- package/dist/core/handler.d.ts +1 -1
- package/dist/core/handler.js +116 -115
- package/dist/core/handler.js.map +1 -1
- package/dist/data.d.ts +2 -2
- package/dist/data.js +60 -60
- package/dist/geo-service.js +78 -78
- package/dist/get-tsconfig-info.js +4 -4
- package/dist/index.d.ts +8 -8
- package/dist/index.js +6 -6
- package/dist/providers/events/helpers.d.ts +2 -2
- package/dist/providers/events/helpers.d.ts.map +1 -1
- package/dist/providers/events/helpers.js +178 -154
- package/dist/providers/events/helpers.js.map +1 -1
- package/dist/public/assets/{main-BDJUrMKx.js → main-BeCk6LUx.js} +133 -133
- package/dist/public/assets/main-w2bJSKlF.css +1 -0
- package/dist/public/index.html +2 -2
- package/dist/routes/api-router.d.ts +3 -1
- package/dist/routes/api-router.d.ts.map +1 -1
- package/dist/routes/api-router.js +3 -3
- package/dist/routes/api-router.js.map +1 -1
- package/dist/routes.d.ts +6 -4
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +1333 -1277
- package/dist/routes.js.map +1 -1
- package/dist/studio.d.ts +3 -3
- package/dist/studio.d.ts.map +1 -1
- package/dist/studio.js +64 -65
- package/dist/studio.js.map +1 -1
- package/dist/types/events.d.ts +8 -7
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/events.js +165 -165
- package/dist/types/events.js.map +1 -1
- package/dist/types/handler.d.ts +14 -4
- package/dist/types/handler.d.ts.map +1 -1
- package/dist/types/handler.js.map +1 -1
- package/dist/utils/auth-callbacks-injector.d.ts +2 -2
- package/dist/utils/auth-callbacks-injector.js +27 -27
- package/dist/utils/auth-callbacks-wrapper.d.ts +3 -3
- package/dist/utils/auth-callbacks-wrapper.js +117 -107
- package/dist/utils/database-detection.d.ts +1 -1
- package/dist/utils/database-detection.js +44 -44
- package/dist/utils/database-hook-injector.d.ts +3 -3
- package/dist/utils/database-hook-injector.js +135 -131
- package/dist/utils/email-otp-hooks-injector.d.ts +28 -12
- package/dist/utils/email-otp-hooks-injector.js +104 -97
- package/dist/utils/event-ingestion.d.ts +5 -5
- package/dist/utils/event-ingestion.d.ts.map +1 -1
- package/dist/utils/event-ingestion.js +32 -12
- package/dist/utils/event-ingestion.js.map +1 -1
- package/dist/utils/hook-injector.d.ts +2 -2
- package/dist/utils/hook-injector.js +199 -199
- package/dist/utils/hook-injector.js.map +1 -1
- package/dist/utils/html-injector.d.ts +11 -2
- package/dist/utils/html-injector.d.ts.map +1 -1
- package/dist/utils/html-injector.js +40 -39
- package/dist/utils/html-injector.js.map +1 -1
- package/dist/utils/org-hooks-injector.d.ts +3 -3
- package/dist/utils/org-hooks-injector.js +63 -63
- package/dist/utils/org-hooks-wrapper.d.ts +41 -35
- package/dist/utils/org-hooks-wrapper.js +778 -658
- package/dist/utils/organization-hooks-wrapper.d.ts +23 -17
- package/dist/utils/organization-hooks-wrapper.js +325 -277
- package/dist/utils/package-json.js +11 -11
- package/dist/utils/paths.js +1 -1
- package/dist/utils/server-init.d.ts +1 -1
- package/dist/utils/server-init.js +25 -25
- package/dist/utils/session.d.ts +0 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +19 -12
- package/dist/utils/session.js.map +1 -1
- package/dist/utils.js +24 -24
- package/package.json +56 -47
- package/public/assets/{main-BDJUrMKx.js → main-BeCk6LUx.js} +133 -133
- package/public/assets/main-w2bJSKlF.css +1 -0
- package/public/index.html +2 -2
- package/scripts/download-geolite2.js +8 -8
- package/scripts/generate-default-db.js +324 -324
- package/scripts/postinstall.js +25 -25
- package/dist/public/assets/main-CBA9bZ-w.css +0 -1
- package/public/assets/main-CBA9bZ-w.css +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export function createPostgresProvider(options) {
|
|
2
|
-
const { client, tableName =
|
|
2
|
+
const { client, tableName = "auth_events", schema = "public", clientType } = options;
|
|
3
3
|
// Validate client is provided
|
|
4
4
|
if (!client) {
|
|
5
|
-
throw new Error(
|
|
5
|
+
throw new Error("Postgres client is required. Provide a pg Pool, Client, or Prisma client instance.");
|
|
6
6
|
}
|
|
7
7
|
let actualClient = client;
|
|
8
|
-
if (clientType ===
|
|
8
|
+
if (clientType === "drizzle") {
|
|
9
9
|
if (client.$client) {
|
|
10
10
|
actualClient = client.$client;
|
|
11
11
|
}
|
|
@@ -19,11 +19,11 @@ export function createPostgresProvider(options) {
|
|
|
19
19
|
const hasExecute = client?.execute;
|
|
20
20
|
const hasUnsafe = actualClient?.unsafe || client?.$client?.unsafe || client?.unsafe;
|
|
21
21
|
if (!hasQuery && !hasExecuteRaw && !hasExecute && !hasUnsafe) {
|
|
22
|
-
const errorMessage = clientType ===
|
|
23
|
-
?
|
|
24
|
-
: clientType ===
|
|
25
|
-
?
|
|
26
|
-
:
|
|
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
27
|
throw new Error(errorMessage);
|
|
28
28
|
}
|
|
29
29
|
// Ensure table exists
|
|
@@ -33,13 +33,13 @@ export function createPostgresProvider(options) {
|
|
|
33
33
|
try {
|
|
34
34
|
// Support Prisma client ($executeRaw), Drizzle instance ($client), or standard pg client (query)
|
|
35
35
|
let executeQuery;
|
|
36
|
-
if (clientType ===
|
|
36
|
+
if (clientType === "prisma" || client.$executeRaw) {
|
|
37
37
|
// Prisma client
|
|
38
38
|
executeQuery = async (query) => {
|
|
39
39
|
return await client.$executeRawUnsafe(query);
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
else if (clientType ===
|
|
42
|
+
else if (clientType === "drizzle") {
|
|
43
43
|
// Drizzle client - use $client which exposes the underlying client
|
|
44
44
|
if (actualClient?.unsafe) {
|
|
45
45
|
// postgres-js client via db.$client.unsafe()
|
|
@@ -54,7 +54,7 @@ export function createPostgresProvider(options) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
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(
|
|
57
|
+
throw new Error(`Drizzle client.$client doesn't expose 'unsafe' (postgres-js) or 'query' (pg) method. Available methods: ${Object.keys(actualClient || {}).join(", ")}`);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
else if (actualClient?.query) {
|
|
@@ -69,7 +69,7 @@ export function createPostgresProvider(options) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
|
-
throw new Error(`Postgres client doesn't support $executeRaw or query method. Available properties: ${Object.keys(client).join(
|
|
72
|
+
throw new Error(`Postgres client doesn't support $executeRaw or query method. Available properties: ${Object.keys(client).join(", ")}`);
|
|
73
73
|
}
|
|
74
74
|
// Use CREATE TABLE IF NOT EXISTS (simpler and more reliable)
|
|
75
75
|
const createTableQuery = `
|
|
@@ -106,7 +106,7 @@ export function createPostgresProvider(options) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
catch (error) {
|
|
109
|
-
if (error?.message?.includes(
|
|
109
|
+
if (error?.message?.includes("already exists") || error?.code === "42P07") {
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
112
|
console.error(`Failed to ensure ${schema}.${tableName} table:`, error);
|
|
@@ -127,7 +127,7 @@ export function createPostgresProvider(options) {
|
|
|
127
127
|
tableEnsured = true;
|
|
128
128
|
}
|
|
129
129
|
catch (error) {
|
|
130
|
-
console.error(
|
|
130
|
+
console.error("Failed to ensure table:", error);
|
|
131
131
|
tableEnsuringPromise = null;
|
|
132
132
|
throw error;
|
|
133
133
|
}
|
|
@@ -141,18 +141,18 @@ export function createPostgresProvider(options) {
|
|
|
141
141
|
return {
|
|
142
142
|
async ingest(event) {
|
|
143
143
|
await ensureTableSync();
|
|
144
|
-
if (clientType ===
|
|
144
|
+
if (clientType === "prisma" || client.$executeRaw) {
|
|
145
145
|
const query = `
|
|
146
146
|
INSERT INTO ${schema}.${tableName}
|
|
147
147
|
(id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
|
|
148
|
-
VALUES ('${event.id}'::uuid, '${event.type}', '${event.timestamp.toISOString()}', '${event.status ||
|
|
148
|
+
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"})
|
|
149
149
|
`;
|
|
150
150
|
try {
|
|
151
151
|
await client.$executeRawUnsafe(query);
|
|
152
152
|
}
|
|
153
153
|
catch (error) {
|
|
154
154
|
console.error(`Failed to insert event (${event.type}) into ${schema}.${tableName}:`, error);
|
|
155
|
-
if (error.code ===
|
|
155
|
+
if (error.code === "42P01" || error.meta?.code === "42P01" || error.code === "P2010") {
|
|
156
156
|
tableEnsured = false;
|
|
157
157
|
await ensureTableSync();
|
|
158
158
|
try {
|
|
@@ -178,14 +178,14 @@ export function createPostgresProvider(options) {
|
|
|
178
178
|
const query = `
|
|
179
179
|
INSERT INTO ${schema}.${tableName}
|
|
180
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 ||
|
|
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
182
|
`;
|
|
183
183
|
try {
|
|
184
184
|
await queryClient.unsafe(query);
|
|
185
185
|
}
|
|
186
186
|
catch (error) {
|
|
187
187
|
console.error(`Failed to insert event (${event.type}) into ${schema}.${tableName}:`, error);
|
|
188
|
-
if (error.code ===
|
|
188
|
+
if (error.code === "42P01") {
|
|
189
189
|
tableEnsured = false;
|
|
190
190
|
await ensureTableSync();
|
|
191
191
|
try {
|
|
@@ -209,7 +209,7 @@ export function createPostgresProvider(options) {
|
|
|
209
209
|
event.id,
|
|
210
210
|
event.type,
|
|
211
211
|
event.timestamp,
|
|
212
|
-
event.status ||
|
|
212
|
+
event.status || "success",
|
|
213
213
|
event.userId || null,
|
|
214
214
|
event.sessionId || null,
|
|
215
215
|
event.organizationId || null,
|
|
@@ -223,7 +223,7 @@ export function createPostgresProvider(options) {
|
|
|
223
223
|
}
|
|
224
224
|
catch (error) {
|
|
225
225
|
console.error(`Failed to insert event (${event.type}) into ${schema}.${tableName}:`, error);
|
|
226
|
-
if (error.code ===
|
|
226
|
+
if (error.code === "42P01") {
|
|
227
227
|
tableEnsured = false;
|
|
228
228
|
await ensureTableSync();
|
|
229
229
|
try {
|
|
@@ -233,7 +233,7 @@ export function createPostgresProvider(options) {
|
|
|
233
233
|
event.id,
|
|
234
234
|
event.type,
|
|
235
235
|
event.timestamp,
|
|
236
|
-
event.status ||
|
|
236
|
+
event.status || "success",
|
|
237
237
|
event.userId || null,
|
|
238
238
|
event.sessionId || null,
|
|
239
239
|
event.organizationId || null,
|
|
@@ -251,9 +251,9 @@ export function createPostgresProvider(options) {
|
|
|
251
251
|
throw retryError;
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
|
-
if (error.code ===
|
|
255
|
-
error.code ===
|
|
256
|
-
error.message?.includes(
|
|
254
|
+
if (error.code === "ECONNREFUSED" ||
|
|
255
|
+
error.code === "ETIMEDOUT" ||
|
|
256
|
+
error.message?.includes("Connection terminated")) {
|
|
257
257
|
if (client.end) {
|
|
258
258
|
console.warn(`⚠️ Connection error with pg Pool. The pool will retry automatically on next query.`);
|
|
259
259
|
}
|
|
@@ -263,7 +263,7 @@ export function createPostgresProvider(options) {
|
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
else {
|
|
266
|
-
throw new Error(
|
|
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.");
|
|
267
267
|
}
|
|
268
268
|
},
|
|
269
269
|
async ingestBatch(events) {
|
|
@@ -271,14 +271,14 @@ export function createPostgresProvider(options) {
|
|
|
271
271
|
return;
|
|
272
272
|
await ensureTableSync();
|
|
273
273
|
// Support Prisma client ($executeRaw), Drizzle instance, or standard pg client (query)
|
|
274
|
-
if (clientType ===
|
|
274
|
+
if (clientType === "prisma" || client.$executeRaw) {
|
|
275
275
|
// Prisma client - use $executeRawUnsafe for batch inserts
|
|
276
276
|
const CHUNK_SIZE = 500; // Reasonable chunk size for string-based queries
|
|
277
277
|
for (let i = 0; i < events.length; i += CHUNK_SIZE) {
|
|
278
278
|
const chunk = events.slice(i, i + CHUNK_SIZE);
|
|
279
279
|
const values = chunk
|
|
280
|
-
.map((event) => `('${event.id}', '${event.type}', '${event.timestamp.toISOString()}', '${event.status ||
|
|
281
|
-
.join(
|
|
280
|
+
.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"})`)
|
|
281
|
+
.join(", ");
|
|
282
282
|
const query = `
|
|
283
283
|
INSERT INTO ${schema}.${tableName}
|
|
284
284
|
(id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
|
|
@@ -305,8 +305,8 @@ export function createPostgresProvider(options) {
|
|
|
305
305
|
for (let i = 0; i < events.length; i += CHUNK_SIZE) {
|
|
306
306
|
const chunk = events.slice(i, i + CHUNK_SIZE);
|
|
307
307
|
const values = chunk
|
|
308
|
-
.map((event) => `('${event.id}', '${event.type}', '${event.timestamp.toISOString()}', '${event.status ||
|
|
309
|
-
.join(
|
|
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
310
|
const query = `
|
|
311
311
|
INSERT INTO ${schema}.${tableName}
|
|
312
312
|
(id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
|
|
@@ -333,7 +333,7 @@ export function createPostgresProvider(options) {
|
|
|
333
333
|
const base = i * PARAMS_PER_EVENT;
|
|
334
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
335
|
})
|
|
336
|
-
.join(
|
|
336
|
+
.join(", ");
|
|
337
337
|
const query = `
|
|
338
338
|
INSERT INTO ${schema}.${tableName}
|
|
339
339
|
(id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
|
|
@@ -343,7 +343,7 @@ export function createPostgresProvider(options) {
|
|
|
343
343
|
event.id,
|
|
344
344
|
event.type,
|
|
345
345
|
event.timestamp,
|
|
346
|
-
event.status ||
|
|
346
|
+
event.status || "success",
|
|
347
347
|
event.userId || null,
|
|
348
348
|
event.sessionId || null,
|
|
349
349
|
event.organizationId || null,
|
|
@@ -359,9 +359,9 @@ export function createPostgresProvider(options) {
|
|
|
359
359
|
}
|
|
360
360
|
catch (error) {
|
|
361
361
|
console.error(`Failed to insert batch chunk (${chunk.length} events) into ${schema}.${tableName}:`, error);
|
|
362
|
-
if (error.code ===
|
|
363
|
-
error.code ===
|
|
364
|
-
error.message?.includes(
|
|
362
|
+
if (error.code === "ECONNREFUSED" ||
|
|
363
|
+
error.code === "ETIMEDOUT" ||
|
|
364
|
+
error.message?.includes("Connection terminated")) {
|
|
365
365
|
if (client.end) {
|
|
366
366
|
console.warn(`⚠️ Connection error with pg Pool. The pool will retry automatically on next query.`);
|
|
367
367
|
}
|
|
@@ -372,11 +372,11 @@ export function createPostgresProvider(options) {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
else {
|
|
375
|
-
throw new Error(
|
|
375
|
+
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.");
|
|
376
376
|
}
|
|
377
377
|
},
|
|
378
378
|
async query(options) {
|
|
379
|
-
const { limit = 20, after, sort =
|
|
379
|
+
const { limit = 20, after, sort = "desc", type, userId, since } = options;
|
|
380
380
|
let queryFn;
|
|
381
381
|
if (client.$executeRaw) {
|
|
382
382
|
// Prisma client
|
|
@@ -385,14 +385,14 @@ export function createPostgresProvider(options) {
|
|
|
385
385
|
let processedQuery = query;
|
|
386
386
|
params.forEach((param, index) => {
|
|
387
387
|
const placeholder = `$${index + 1}`;
|
|
388
|
-
const value = typeof param ===
|
|
388
|
+
const value = typeof param === "string"
|
|
389
389
|
? `'${param.replace(/'/g, "''")}'`
|
|
390
390
|
: param === null
|
|
391
|
-
?
|
|
391
|
+
? "NULL"
|
|
392
392
|
: param instanceof Date
|
|
393
393
|
? `'${param.toISOString()}'`
|
|
394
394
|
: String(param);
|
|
395
|
-
processedQuery = processedQuery.replace(new RegExp(`\\${placeholder}(?![0-9])`,
|
|
395
|
+
processedQuery = processedQuery.replace(new RegExp(`\\${placeholder}(?![0-9])`, "g"), value);
|
|
396
396
|
});
|
|
397
397
|
return await client.$queryRawUnsafe(processedQuery);
|
|
398
398
|
}
|
|
@@ -409,7 +409,7 @@ export function createPostgresProvider(options) {
|
|
|
409
409
|
};
|
|
410
410
|
}
|
|
411
411
|
else {
|
|
412
|
-
throw new Error(
|
|
412
|
+
throw new Error("Postgres client does not support $executeRaw or query method");
|
|
413
413
|
}
|
|
414
414
|
try {
|
|
415
415
|
const checkTableQuery = `
|
|
@@ -423,8 +423,8 @@ export function createPostgresProvider(options) {
|
|
|
423
423
|
if (client.$executeRaw) {
|
|
424
424
|
// Prisma client - replace $1, $2 with actual values
|
|
425
425
|
const prismaQuery = checkTableQuery
|
|
426
|
-
.replace(
|
|
427
|
-
.replace(
|
|
426
|
+
.replace("$1", `'${schema}'`)
|
|
427
|
+
.replace("$2", `'${tableName}'`);
|
|
428
428
|
checkResult = await client.$queryRawUnsafe(prismaQuery);
|
|
429
429
|
}
|
|
430
430
|
else {
|
|
@@ -450,7 +450,7 @@ export function createPostgresProvider(options) {
|
|
|
450
450
|
let paramIndex = 1;
|
|
451
451
|
// Cursor-based pagination
|
|
452
452
|
if (after) {
|
|
453
|
-
if (sort ===
|
|
453
|
+
if (sort === "desc") {
|
|
454
454
|
whereClauses.push(`id < $${paramIndex++}`);
|
|
455
455
|
params.push(after);
|
|
456
456
|
}
|
|
@@ -467,8 +467,12 @@ export function createPostgresProvider(options) {
|
|
|
467
467
|
whereClauses.push(`user_id = $${paramIndex++}`);
|
|
468
468
|
params.push(userId);
|
|
469
469
|
}
|
|
470
|
-
|
|
471
|
-
|
|
470
|
+
if (since) {
|
|
471
|
+
whereClauses.push(`timestamp >= $${paramIndex++}`);
|
|
472
|
+
params.push(since instanceof Date ? since.toISOString() : since);
|
|
473
|
+
}
|
|
474
|
+
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
475
|
+
const orderDirection = sort === "desc" ? "DESC" : "ASC";
|
|
472
476
|
const query = `
|
|
473
477
|
SELECT id, type, timestamp, status, user_id, session_id, organization_id,
|
|
474
478
|
metadata, ip_address, user_agent, source, display_message, display_severity
|
|
@@ -486,17 +490,17 @@ export function createPostgresProvider(options) {
|
|
|
486
490
|
id: row.id,
|
|
487
491
|
type: row.type,
|
|
488
492
|
timestamp: new Date(row.timestamp),
|
|
489
|
-
status: row.status ||
|
|
493
|
+
status: row.status || "success",
|
|
490
494
|
userId: row.user_id,
|
|
491
495
|
sessionId: row.session_id,
|
|
492
496
|
organizationId: row.organization_id,
|
|
493
|
-
metadata: typeof row.metadata ===
|
|
497
|
+
metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata || {},
|
|
494
498
|
ipAddress: row.ip_address,
|
|
495
499
|
userAgent: row.user_agent,
|
|
496
|
-
source: row.source ||
|
|
500
|
+
source: row.source || "app",
|
|
497
501
|
display: {
|
|
498
502
|
message: row.display_message || row.type,
|
|
499
|
-
severity: row.display_severity ||
|
|
503
|
+
severity: row.display_severity || "info",
|
|
500
504
|
},
|
|
501
505
|
}));
|
|
502
506
|
return {
|
|
@@ -506,7 +510,7 @@ export function createPostgresProvider(options) {
|
|
|
506
510
|
};
|
|
507
511
|
}
|
|
508
512
|
catch (error) {
|
|
509
|
-
if (error?.message?.includes(
|
|
513
|
+
if (error?.message?.includes("does not exist") || error?.code === "42P01") {
|
|
510
514
|
return {
|
|
511
515
|
events: [],
|
|
512
516
|
hasMore: false,
|
|
@@ -519,29 +523,29 @@ export function createPostgresProvider(options) {
|
|
|
519
523
|
};
|
|
520
524
|
}
|
|
521
525
|
export function createSqliteProvider(options) {
|
|
522
|
-
const { client, tableName =
|
|
526
|
+
const { client, tableName = "auth_events" } = options;
|
|
523
527
|
// Validate client is provided
|
|
524
528
|
if (!client) {
|
|
525
|
-
throw new Error(
|
|
529
|
+
throw new Error("SQLite client is required. Provide a better-sqlite3 Database instance.");
|
|
526
530
|
}
|
|
527
531
|
// Handle case where client might be a function (lazy initialization) or already initialized
|
|
528
|
-
const actualClient = typeof client ===
|
|
532
|
+
const actualClient = typeof client === "function" ? client() : client;
|
|
529
533
|
// If client initialization failed (native module not found), try to provide helpful error
|
|
530
534
|
if (!actualClient) {
|
|
531
|
-
throw new Error(
|
|
532
|
-
|
|
535
|
+
throw new Error("SQLite client initialization failed. Make sure better-sqlite3 is properly installed and built. " +
|
|
536
|
+
"Run: pnpm rebuild better-sqlite3 or npm rebuild better-sqlite3");
|
|
533
537
|
}
|
|
534
538
|
// Validate client has required methods (better-sqlite3 Database)
|
|
535
|
-
const hasExec = typeof actualClient.exec ===
|
|
536
|
-
const hasPrepare = typeof actualClient.prepare ===
|
|
539
|
+
const hasExec = typeof actualClient.exec === "function";
|
|
540
|
+
const hasPrepare = typeof actualClient.prepare === "function";
|
|
537
541
|
if (!hasExec || !hasPrepare) {
|
|
538
542
|
// If methods don't exist, it might be an initialization error - provide helpful message
|
|
539
543
|
if (actualClient instanceof Error) {
|
|
540
544
|
throw new Error(`SQLite client initialization error: ${actualClient.message}. ` +
|
|
541
|
-
|
|
545
|
+
"Make sure better-sqlite3 native module is built. Run: pnpm rebuild better-sqlite3");
|
|
542
546
|
}
|
|
543
|
-
throw new Error(
|
|
544
|
-
|
|
547
|
+
throw new Error("Invalid SQLite client. Client must have `exec` and `prepare` methods (better-sqlite3 Database instance). " +
|
|
548
|
+
"If using better-sqlite3, make sure the native module is built: pnpm rebuild better-sqlite3");
|
|
545
549
|
}
|
|
546
550
|
// Ensure table exists
|
|
547
551
|
const ensureTable = async () => {
|
|
@@ -584,7 +588,7 @@ export function createSqliteProvider(options) {
|
|
|
584
588
|
}
|
|
585
589
|
}
|
|
586
590
|
catch (error) {
|
|
587
|
-
if (error?.message?.includes(
|
|
591
|
+
if (error?.message?.includes("already exists")) {
|
|
588
592
|
return;
|
|
589
593
|
}
|
|
590
594
|
console.error(`Failed to ensure ${tableName} table:`, error);
|
|
@@ -624,12 +628,12 @@ export function createSqliteProvider(options) {
|
|
|
624
628
|
(id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
|
|
625
629
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
626
630
|
`);
|
|
627
|
-
stmt.run(event.id, event.type, event.timestamp.toISOString(), event.status ||
|
|
631
|
+
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
632
|
}
|
|
629
633
|
catch (error) {
|
|
630
634
|
console.error(`Failed to insert event (${event.type}) into ${tableName}:`, error);
|
|
631
635
|
// If table doesn't exist, try to create it and retry
|
|
632
|
-
if (error.message?.includes(
|
|
636
|
+
if (error.message?.includes("no such table")) {
|
|
633
637
|
tableEnsured = false;
|
|
634
638
|
await ensureTableSync();
|
|
635
639
|
try {
|
|
@@ -638,7 +642,7 @@ export function createSqliteProvider(options) {
|
|
|
638
642
|
(id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
|
|
639
643
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
640
644
|
`);
|
|
641
|
-
stmt.run(event.id, event.type, event.timestamp.toISOString(), event.status ||
|
|
645
|
+
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
646
|
return;
|
|
643
647
|
}
|
|
644
648
|
catch (retryError) {
|
|
@@ -661,14 +665,14 @@ export function createSqliteProvider(options) {
|
|
|
661
665
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
662
666
|
`);
|
|
663
667
|
for (const event of events) {
|
|
664
|
-
stmt.run(event.id, event.type, event.timestamp.toISOString(), event.status ||
|
|
668
|
+
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
669
|
}
|
|
666
670
|
});
|
|
667
671
|
transaction(events);
|
|
668
672
|
}
|
|
669
673
|
catch (error) {
|
|
670
674
|
console.error(`Failed to insert batch (${events.length} events):`, error);
|
|
671
|
-
if (error.message?.includes(
|
|
675
|
+
if (error.message?.includes("no such table")) {
|
|
672
676
|
tableEnsured = false;
|
|
673
677
|
await ensureTableSync();
|
|
674
678
|
try {
|
|
@@ -679,7 +683,7 @@ export function createSqliteProvider(options) {
|
|
|
679
683
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
680
684
|
`);
|
|
681
685
|
for (const event of events) {
|
|
682
|
-
stmt.run(event.id, event.type, event.timestamp.toISOString(), event.status ||
|
|
686
|
+
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
687
|
}
|
|
684
688
|
});
|
|
685
689
|
transaction(events);
|
|
@@ -694,7 +698,7 @@ export function createSqliteProvider(options) {
|
|
|
694
698
|
}
|
|
695
699
|
},
|
|
696
700
|
async query(options) {
|
|
697
|
-
const { limit = 20, after, sort =
|
|
701
|
+
const { limit = 20, after, sort = "desc", type, userId, since } = options;
|
|
698
702
|
await ensureTableSync();
|
|
699
703
|
try {
|
|
700
704
|
let query = `SELECT * FROM ${tableName} WHERE 1=1`;
|
|
@@ -707,11 +711,15 @@ export function createSqliteProvider(options) {
|
|
|
707
711
|
query += ` AND user_id = ?`;
|
|
708
712
|
params.push(userId);
|
|
709
713
|
}
|
|
714
|
+
if (since) {
|
|
715
|
+
query += ` AND timestamp >= ?`;
|
|
716
|
+
params.push(since instanceof Date ? since.toISOString() : since);
|
|
717
|
+
}
|
|
710
718
|
if (after) {
|
|
711
719
|
query += ` AND id > ?`;
|
|
712
720
|
params.push(after);
|
|
713
721
|
}
|
|
714
|
-
query += ` ORDER BY timestamp ${sort ===
|
|
722
|
+
query += ` ORDER BY timestamp ${sort === "desc" ? "DESC" : "ASC"}`;
|
|
715
723
|
query += ` LIMIT ?`;
|
|
716
724
|
params.push(limit + 1); // Fetch one extra to check if there are more
|
|
717
725
|
const stmt = actualClient.prepare(query);
|
|
@@ -725,10 +733,10 @@ export function createSqliteProvider(options) {
|
|
|
725
733
|
userId: row.user_id || undefined,
|
|
726
734
|
sessionId: row.session_id || undefined,
|
|
727
735
|
organizationId: row.organization_id || undefined,
|
|
728
|
-
metadata: typeof row.metadata ===
|
|
736
|
+
metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata || {},
|
|
729
737
|
ipAddress: row.ip_address || undefined,
|
|
730
738
|
userAgent: row.user_agent || undefined,
|
|
731
|
-
source: row.source ||
|
|
739
|
+
source: row.source || "app",
|
|
732
740
|
display: row.display_message || row.display_severity
|
|
733
741
|
? {
|
|
734
742
|
message: row.display_message || undefined,
|
|
@@ -743,7 +751,7 @@ export function createSqliteProvider(options) {
|
|
|
743
751
|
};
|
|
744
752
|
}
|
|
745
753
|
catch (error) {
|
|
746
|
-
if (error.message?.includes(
|
|
754
|
+
if (error.message?.includes("no such table")) {
|
|
747
755
|
await ensureTableSync();
|
|
748
756
|
return { events: [], hasMore: false, nextCursor: null };
|
|
749
757
|
}
|
|
@@ -754,7 +762,7 @@ export function createSqliteProvider(options) {
|
|
|
754
762
|
};
|
|
755
763
|
}
|
|
756
764
|
export function createClickHouseProvider(options) {
|
|
757
|
-
const { client, table =
|
|
765
|
+
const { client, table = "auth_events", database } = options;
|
|
758
766
|
const ensureTable = async () => {
|
|
759
767
|
if (!client)
|
|
760
768
|
return;
|
|
@@ -793,7 +801,7 @@ export function createClickHouseProvider(options) {
|
|
|
793
801
|
}
|
|
794
802
|
}
|
|
795
803
|
catch (error) {
|
|
796
|
-
if (error?.message?.includes(
|
|
804
|
+
if (error?.message?.includes("already exists") || error?.code === 57) {
|
|
797
805
|
return;
|
|
798
806
|
}
|
|
799
807
|
console.error(`Failed to ensure ${table} table in ClickHouse:`, error);
|
|
@@ -810,7 +818,7 @@ export function createClickHouseProvider(options) {
|
|
|
810
818
|
tableEnsured = true;
|
|
811
819
|
}
|
|
812
820
|
catch (error) {
|
|
813
|
-
console.error(
|
|
821
|
+
console.error("Failed to ensure table:", error);
|
|
814
822
|
}
|
|
815
823
|
finally {
|
|
816
824
|
tableEnsuring = false;
|
|
@@ -828,30 +836,30 @@ export function createClickHouseProvider(options) {
|
|
|
828
836
|
id: event.id,
|
|
829
837
|
type: event.type,
|
|
830
838
|
timestamp: event.timestamp,
|
|
831
|
-
status: event.status ||
|
|
832
|
-
user_id: event.userId ||
|
|
833
|
-
session_id: event.sessionId ||
|
|
834
|
-
organization_id: event.organizationId ||
|
|
839
|
+
status: event.status || "success",
|
|
840
|
+
user_id: event.userId || "",
|
|
841
|
+
session_id: event.sessionId || "",
|
|
842
|
+
organization_id: event.organizationId || "",
|
|
835
843
|
metadata: JSON.stringify(event.metadata || {}),
|
|
836
|
-
ip_address: event.ipAddress ||
|
|
837
|
-
user_agent: event.userAgent ||
|
|
844
|
+
ip_address: event.ipAddress || "",
|
|
845
|
+
user_agent: event.userAgent || "",
|
|
838
846
|
source: event.source,
|
|
839
|
-
display_message: event.display?.message ||
|
|
840
|
-
display_severity: event.display?.severity ||
|
|
847
|
+
display_message: event.display?.message || "",
|
|
848
|
+
display_severity: event.display?.severity || "",
|
|
841
849
|
}));
|
|
842
850
|
try {
|
|
843
851
|
if (client.insert) {
|
|
844
852
|
await client.insert({
|
|
845
853
|
table: tableFullName,
|
|
846
854
|
values: rows,
|
|
847
|
-
format:
|
|
855
|
+
format: "JSONEachRow",
|
|
848
856
|
});
|
|
849
857
|
console.log(`✅ Inserted ${rows.length} event(s) into ClickHouse ${tableFullName}`);
|
|
850
858
|
}
|
|
851
859
|
else {
|
|
852
860
|
const values = rows
|
|
853
|
-
.map((row) => `('${row.id}', '${row.type}', '${new Date(row.timestamp).toISOString().replace(
|
|
854
|
-
.join(
|
|
861
|
+
.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"})`)
|
|
862
|
+
.join(", ");
|
|
855
863
|
const insertQuery = `
|
|
856
864
|
INSERT INTO ${tableFullName}
|
|
857
865
|
(id, type, timestamp, status, user_id, session_id, organization_id, metadata, ip_address, user_agent, source, display_message, display_severity)
|
|
@@ -864,7 +872,7 @@ export function createClickHouseProvider(options) {
|
|
|
864
872
|
await client.query({ query: insertQuery });
|
|
865
873
|
}
|
|
866
874
|
else {
|
|
867
|
-
throw new Error(
|
|
875
|
+
throw new Error("ClickHouse client does not support insert, exec, or query methods");
|
|
868
876
|
}
|
|
869
877
|
console.log(`✅ Inserted ${rows.length} event(s) into ClickHouse ${tableFullName} via query`);
|
|
870
878
|
}
|
|
@@ -882,7 +890,7 @@ export function createClickHouseProvider(options) {
|
|
|
882
890
|
await ingestBatchFn(events);
|
|
883
891
|
},
|
|
884
892
|
async query(options) {
|
|
885
|
-
const { limit = 20, after, sort =
|
|
893
|
+
const { limit = 20, after, sort = "desc", type, userId, since } = options;
|
|
886
894
|
const tableFullName = database ? `${database}.${table}` : table;
|
|
887
895
|
try {
|
|
888
896
|
const checkTableQuery = `EXISTS TABLE ${tableFullName}`;
|
|
@@ -891,7 +899,7 @@ export function createClickHouseProvider(options) {
|
|
|
891
899
|
try {
|
|
892
900
|
const checkResult = await client.query({
|
|
893
901
|
query: checkTableQuery,
|
|
894
|
-
format:
|
|
902
|
+
format: "JSONEachRow",
|
|
895
903
|
});
|
|
896
904
|
const rows = await checkResult.json();
|
|
897
905
|
tableExists =
|
|
@@ -904,7 +912,7 @@ export function createClickHouseProvider(options) {
|
|
|
904
912
|
else if (client.exec) {
|
|
905
913
|
try {
|
|
906
914
|
const checkResult = await client.exec({ query: checkTableQuery });
|
|
907
|
-
tableExists = checkResult ===
|
|
915
|
+
tableExists = checkResult === "1" || String(checkResult).includes("1");
|
|
908
916
|
}
|
|
909
917
|
catch {
|
|
910
918
|
tableExists = false;
|
|
@@ -952,7 +960,7 @@ export function createClickHouseProvider(options) {
|
|
|
952
960
|
try {
|
|
953
961
|
const columnResult = await client.query({
|
|
954
962
|
query: checkColumnQuery,
|
|
955
|
-
format:
|
|
963
|
+
format: "JSONEachRow",
|
|
956
964
|
});
|
|
957
965
|
const columnRows = await columnResult.json();
|
|
958
966
|
columnExists = columnRows && columnRows.length > 0 && columnRows[0]?.exists > 0;
|
|
@@ -964,7 +972,7 @@ export function createClickHouseProvider(options) {
|
|
|
964
972
|
else if (client.exec) {
|
|
965
973
|
try {
|
|
966
974
|
const columnResult = await client.exec({ query: checkColumnQuery });
|
|
967
|
-
columnExists = String(columnResult).includes(
|
|
975
|
+
columnExists = String(columnResult).includes("1") || columnResult === "1";
|
|
968
976
|
}
|
|
969
977
|
catch {
|
|
970
978
|
columnExists = false;
|
|
@@ -992,13 +1000,13 @@ export function createClickHouseProvider(options) {
|
|
|
992
1000
|
}
|
|
993
1001
|
}
|
|
994
1002
|
catch (error) {
|
|
995
|
-
if (!error?.message?.includes(
|
|
1003
|
+
if (!error?.message?.includes("already exists") && error?.code !== 57) {
|
|
996
1004
|
console.warn(`Failed to ensure ClickHouse table ${tableFullName}:`, error);
|
|
997
1005
|
}
|
|
998
1006
|
}
|
|
999
1007
|
const whereClauses = [];
|
|
1000
1008
|
if (after) {
|
|
1001
|
-
if (sort ===
|
|
1009
|
+
if (sort === "desc") {
|
|
1002
1010
|
whereClauses.push(`id < '${String(after).replace(/'/g, "''")}'`);
|
|
1003
1011
|
}
|
|
1004
1012
|
else {
|
|
@@ -1011,8 +1019,15 @@ export function createClickHouseProvider(options) {
|
|
|
1011
1019
|
if (userId) {
|
|
1012
1020
|
whereClauses.push(`user_id = '${String(userId).replace(/'/g, "''")}'`);
|
|
1013
1021
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1022
|
+
if (since) {
|
|
1023
|
+
const sinceDate = since instanceof Date ? since : new Date(since);
|
|
1024
|
+
// ClickHouse requires DateTime type, so we need to cast the string to DateTime
|
|
1025
|
+
// Format: 'YYYY-MM-DD HH:MM:SS' or use toDateTime() function
|
|
1026
|
+
const isoString = sinceDate.toISOString().replace("T", " ").slice(0, 19);
|
|
1027
|
+
whereClauses.push(`timestamp >= toDateTime('${isoString.replace(/'/g, "''")}')`);
|
|
1028
|
+
}
|
|
1029
|
+
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
1030
|
+
const orderDirection = sort === "desc" ? "DESC" : "ASC";
|
|
1016
1031
|
let query = `
|
|
1017
1032
|
SELECT id, type, timestamp, status, user_id, session_id, organization_id,
|
|
1018
1033
|
metadata, ip_address, user_agent, source, display_message, display_severity
|
|
@@ -1025,20 +1040,20 @@ export function createClickHouseProvider(options) {
|
|
|
1025
1040
|
let hasStatusColumn = true;
|
|
1026
1041
|
try {
|
|
1027
1042
|
if (client.query) {
|
|
1028
|
-
const queryResult = await client.query({ query, format:
|
|
1043
|
+
const queryResult = await client.query({ query, format: "JSONEachRow" });
|
|
1029
1044
|
result = await queryResult.json();
|
|
1030
1045
|
}
|
|
1031
1046
|
else if (client.exec) {
|
|
1032
|
-
const execResult = await client.exec({ query, format:
|
|
1033
|
-
result = typeof execResult ===
|
|
1047
|
+
const execResult = await client.exec({ query, format: "JSONEachRow" });
|
|
1048
|
+
result = typeof execResult === "string" ? JSON.parse(execResult) : execResult;
|
|
1034
1049
|
}
|
|
1035
1050
|
else {
|
|
1036
|
-
throw new Error(
|
|
1051
|
+
throw new Error("ClickHouse client does not support query or exec methods");
|
|
1037
1052
|
}
|
|
1038
1053
|
}
|
|
1039
1054
|
catch (error) {
|
|
1040
|
-
if (error?.message?.includes(
|
|
1041
|
-
error?.message?.includes(
|
|
1055
|
+
if (error?.message?.includes("Unknown expression identifier") &&
|
|
1056
|
+
error?.message?.includes("status")) {
|
|
1042
1057
|
console.warn(`Status column not found in ${tableFullName}, querying without it`);
|
|
1043
1058
|
hasStatusColumn = false;
|
|
1044
1059
|
query = `
|
|
@@ -1051,12 +1066,12 @@ export function createClickHouseProvider(options) {
|
|
|
1051
1066
|
`;
|
|
1052
1067
|
try {
|
|
1053
1068
|
if (client.query) {
|
|
1054
|
-
const queryResult = await client.query({ query, format:
|
|
1069
|
+
const queryResult = await client.query({ query, format: "JSONEachRow" });
|
|
1055
1070
|
result = await queryResult.json();
|
|
1056
1071
|
}
|
|
1057
1072
|
else if (client.exec) {
|
|
1058
|
-
const execResult = await client.exec({ query, format:
|
|
1059
|
-
result = typeof execResult ===
|
|
1073
|
+
const execResult = await client.exec({ query, format: "JSONEachRow" });
|
|
1074
|
+
result = typeof execResult === "string" ? JSON.parse(execResult) : execResult;
|
|
1060
1075
|
}
|
|
1061
1076
|
}
|
|
1062
1077
|
catch (retryError) {
|
|
@@ -1087,17 +1102,17 @@ export function createClickHouseProvider(options) {
|
|
|
1087
1102
|
id: row.id,
|
|
1088
1103
|
type: row.type,
|
|
1089
1104
|
timestamp: new Date(row.timestamp),
|
|
1090
|
-
status: hasStatusColumn ? row.status ||
|
|
1105
|
+
status: hasStatusColumn ? row.status || "success" : "success",
|
|
1091
1106
|
userId: row.user_id || undefined,
|
|
1092
1107
|
sessionId: row.session_id || undefined,
|
|
1093
1108
|
organizationId: row.organization_id || undefined,
|
|
1094
|
-
metadata: typeof row.metadata ===
|
|
1109
|
+
metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata || {},
|
|
1095
1110
|
ipAddress: row.ip_address || undefined,
|
|
1096
1111
|
userAgent: row.user_agent || undefined,
|
|
1097
|
-
source: row.source ||
|
|
1112
|
+
source: row.source || "app",
|
|
1098
1113
|
display: {
|
|
1099
1114
|
message: row.display_message || row.type,
|
|
1100
|
-
severity: row.display_severity ||
|
|
1115
|
+
severity: row.display_severity || "info",
|
|
1101
1116
|
},
|
|
1102
1117
|
}));
|
|
1103
1118
|
return {
|
|
@@ -1114,23 +1129,23 @@ export function createHttpProvider(options) {
|
|
|
1114
1129
|
async ingest(event) {
|
|
1115
1130
|
const payload = transform ? transform(event) : event;
|
|
1116
1131
|
await client(url, {
|
|
1117
|
-
method:
|
|
1118
|
-
headers: {
|
|
1132
|
+
method: "POST",
|
|
1133
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
1119
1134
|
body: JSON.stringify(payload),
|
|
1120
1135
|
});
|
|
1121
1136
|
},
|
|
1122
1137
|
async ingestBatch(events) {
|
|
1123
1138
|
const payload = events.map((event) => (transform ? transform(event) : event));
|
|
1124
1139
|
await client(url, {
|
|
1125
|
-
method:
|
|
1126
|
-
headers: {
|
|
1140
|
+
method: "POST",
|
|
1141
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
1127
1142
|
body: JSON.stringify({ events: payload }),
|
|
1128
1143
|
});
|
|
1129
1144
|
},
|
|
1130
1145
|
};
|
|
1131
1146
|
}
|
|
1132
1147
|
export function createStorageProvider(options) {
|
|
1133
|
-
const { adapter, tableName =
|
|
1148
|
+
const { adapter, tableName = "auth_events" } = options;
|
|
1134
1149
|
let tableEnsured = false;
|
|
1135
1150
|
let tableEnsuringPromise = null;
|
|
1136
1151
|
const ensureTable = async () => {
|
|
@@ -1154,10 +1169,10 @@ export function createStorageProvider(options) {
|
|
|
1154
1169
|
}
|
|
1155
1170
|
catch (error) {
|
|
1156
1171
|
// Table doesn't exist - try to create it if adapter supports raw SQL
|
|
1157
|
-
if (error?.message?.includes(
|
|
1158
|
-
error?.message?.includes(
|
|
1159
|
-
error?.code ===
|
|
1160
|
-
error?.code ===
|
|
1172
|
+
if (error?.message?.includes("not found in schema") ||
|
|
1173
|
+
error?.message?.includes("Model") ||
|
|
1174
|
+
error?.code === "P2025" ||
|
|
1175
|
+
error?.code === "42P01") {
|
|
1161
1176
|
// Try to create table using raw SQL if adapter supports it
|
|
1162
1177
|
if (adapter.executeRaw || adapter.$executeRaw || adapter.queryRaw) {
|
|
1163
1178
|
try {
|
|
@@ -1223,7 +1238,7 @@ export function createStorageProvider(options) {
|
|
|
1223
1238
|
id: event.id,
|
|
1224
1239
|
type: event.type,
|
|
1225
1240
|
timestamp: event.timestamp,
|
|
1226
|
-
status: event.status ||
|
|
1241
|
+
status: event.status || "success",
|
|
1227
1242
|
userId: event.userId,
|
|
1228
1243
|
sessionId: event.sessionId,
|
|
1229
1244
|
organizationId: event.organizationId,
|
|
@@ -1243,7 +1258,7 @@ export function createStorageProvider(options) {
|
|
|
1243
1258
|
id: event.id,
|
|
1244
1259
|
type: event.type,
|
|
1245
1260
|
timestamp: event.timestamp,
|
|
1246
|
-
status: event.status ||
|
|
1261
|
+
status: event.status || "success",
|
|
1247
1262
|
user_id: event.userId,
|
|
1248
1263
|
session_id: event.sessionId,
|
|
1249
1264
|
organization_id: event.organizationId,
|
|
@@ -1267,7 +1282,7 @@ export function createStorageProvider(options) {
|
|
|
1267
1282
|
id: event.id,
|
|
1268
1283
|
type: event.type,
|
|
1269
1284
|
timestamp: event.timestamp,
|
|
1270
|
-
status: event.status ||
|
|
1285
|
+
status: event.status || "success",
|
|
1271
1286
|
userId: event.userId,
|
|
1272
1287
|
sessionId: event.sessionId,
|
|
1273
1288
|
organizationId: event.organizationId,
|
|
@@ -1285,7 +1300,7 @@ export function createStorageProvider(options) {
|
|
|
1285
1300
|
}
|
|
1286
1301
|
},
|
|
1287
1302
|
async query(options) {
|
|
1288
|
-
const { limit = 20, after, sort =
|
|
1303
|
+
const { limit = 20, after, sort = "desc", type, userId, since } = options;
|
|
1289
1304
|
// Ensure table exists before querying
|
|
1290
1305
|
await ensureTable();
|
|
1291
1306
|
// First, try findMany (normal path)
|
|
@@ -1293,23 +1308,27 @@ export function createStorageProvider(options) {
|
|
|
1293
1308
|
try {
|
|
1294
1309
|
const where = [];
|
|
1295
1310
|
if (after) {
|
|
1296
|
-
if (sort ===
|
|
1297
|
-
where.push({ field:
|
|
1311
|
+
if (sort === "desc") {
|
|
1312
|
+
where.push({ field: "id", operator: "<", value: after });
|
|
1298
1313
|
}
|
|
1299
1314
|
else {
|
|
1300
|
-
where.push({ field:
|
|
1315
|
+
where.push({ field: "id", operator: ">", value: after });
|
|
1301
1316
|
}
|
|
1302
1317
|
}
|
|
1303
1318
|
if (type) {
|
|
1304
|
-
where.push({ field:
|
|
1319
|
+
where.push({ field: "type", value: type });
|
|
1305
1320
|
}
|
|
1306
1321
|
if (userId) {
|
|
1307
|
-
where.push({ field:
|
|
1322
|
+
where.push({ field: "userId", value: userId });
|
|
1323
|
+
}
|
|
1324
|
+
if (since) {
|
|
1325
|
+
const sinceDate = since instanceof Date ? since : new Date(since);
|
|
1326
|
+
where.push({ field: "timestamp", operator: ">=", value: sinceDate });
|
|
1308
1327
|
}
|
|
1309
1328
|
const events = await adapter.findMany({
|
|
1310
1329
|
model: tableName,
|
|
1311
1330
|
where,
|
|
1312
|
-
orderBy: [{ field:
|
|
1331
|
+
orderBy: [{ field: "timestamp", direction: sort === "desc" ? "desc" : "asc" }],
|
|
1313
1332
|
limit: limit + 1, // Get one extra to check hasMore
|
|
1314
1333
|
});
|
|
1315
1334
|
const hasMore = events.length > limit;
|
|
@@ -1317,19 +1336,19 @@ export function createStorageProvider(options) {
|
|
|
1317
1336
|
id: event.id,
|
|
1318
1337
|
type: event.type,
|
|
1319
1338
|
timestamp: new Date(event.timestamp || event.createdAt),
|
|
1320
|
-
status: event.status ||
|
|
1339
|
+
status: event.status || "success",
|
|
1321
1340
|
userId: event.userId || event.user_id,
|
|
1322
1341
|
sessionId: event.sessionId || event.session_id,
|
|
1323
1342
|
organizationId: event.organizationId || event.organization_id,
|
|
1324
|
-
metadata: typeof event.metadata ===
|
|
1343
|
+
metadata: typeof event.metadata === "string"
|
|
1325
1344
|
? JSON.parse(event.metadata)
|
|
1326
1345
|
: event.metadata || {},
|
|
1327
1346
|
ipAddress: event.ipAddress || event.ip_address,
|
|
1328
1347
|
userAgent: event.userAgent || event.user_agent,
|
|
1329
|
-
source: event.source ||
|
|
1348
|
+
source: event.source || "app",
|
|
1330
1349
|
display: {
|
|
1331
1350
|
message: event.displayMessage || event.display_message || event.type,
|
|
1332
|
-
severity: event.displaySeverity || event.display_severity ||
|
|
1351
|
+
severity: event.displaySeverity || event.display_severity || "info",
|
|
1333
1352
|
},
|
|
1334
1353
|
}));
|
|
1335
1354
|
return {
|
|
@@ -1340,9 +1359,9 @@ export function createStorageProvider(options) {
|
|
|
1340
1359
|
}
|
|
1341
1360
|
catch (findManyError) {
|
|
1342
1361
|
// If findMany fails with "model not found", try raw SQL
|
|
1343
|
-
if (findManyError?.message?.includes(
|
|
1344
|
-
findManyError?.message?.includes(
|
|
1345
|
-
findManyError?.code ===
|
|
1362
|
+
if (findManyError?.message?.includes("not found in schema") ||
|
|
1363
|
+
findManyError?.message?.includes("Model") ||
|
|
1364
|
+
findManyError?.code === "P2025") {
|
|
1346
1365
|
// Fall through to raw SQL fallback
|
|
1347
1366
|
}
|
|
1348
1367
|
else {
|
|
@@ -1375,7 +1394,7 @@ export function createStorageProvider(options) {
|
|
|
1375
1394
|
const params = [];
|
|
1376
1395
|
let paramIndex = 1;
|
|
1377
1396
|
if (after) {
|
|
1378
|
-
if (sort ===
|
|
1397
|
+
if (sort === "desc") {
|
|
1379
1398
|
whereConditions.push(`id < $${paramIndex}`);
|
|
1380
1399
|
}
|
|
1381
1400
|
else {
|
|
@@ -1394,8 +1413,13 @@ export function createStorageProvider(options) {
|
|
|
1394
1413
|
params.push(userId);
|
|
1395
1414
|
paramIndex++;
|
|
1396
1415
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1416
|
+
if (since) {
|
|
1417
|
+
whereConditions.push(`timestamp >= $${paramIndex}`);
|
|
1418
|
+
params.push(since instanceof Date ? since : new Date(since));
|
|
1419
|
+
paramIndex++;
|
|
1420
|
+
}
|
|
1421
|
+
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(" AND ")}` : "";
|
|
1422
|
+
const orderDirection = sort === "desc" ? "DESC" : "ASC";
|
|
1399
1423
|
// Use appropriate raw SQL method
|
|
1400
1424
|
let queryFn;
|
|
1401
1425
|
if (sqlAdapter.$queryRawUnsafe || adapter.$queryRawUnsafe) {
|
|
@@ -1404,14 +1428,14 @@ export function createStorageProvider(options) {
|
|
|
1404
1428
|
// Replace $1, $2, etc. with actual values for Prisma
|
|
1405
1429
|
params.forEach((param, index) => {
|
|
1406
1430
|
const placeholder = `$${index + 1}`;
|
|
1407
|
-
const value = typeof param ===
|
|
1431
|
+
const value = typeof param === "string"
|
|
1408
1432
|
? `'${param.replace(/'/g, "''")}'`
|
|
1409
1433
|
: param === null
|
|
1410
|
-
?
|
|
1434
|
+
? "NULL"
|
|
1411
1435
|
: param instanceof Date
|
|
1412
1436
|
? `'${param.toISOString()}'`
|
|
1413
1437
|
: String(param);
|
|
1414
|
-
query = query.replace(new RegExp(`\\${placeholder}(?![0-9])`,
|
|
1438
|
+
query = query.replace(new RegExp(`\\${placeholder}(?![0-9])`, "g"), value);
|
|
1415
1439
|
});
|
|
1416
1440
|
const queryMethod = sqlAdapter.$queryRawUnsafe || adapter.$queryRawUnsafe;
|
|
1417
1441
|
queryFn = async (q) => await queryMethod(q);
|
|
@@ -1421,19 +1445,19 @@ export function createStorageProvider(options) {
|
|
|
1421
1445
|
id: event.id,
|
|
1422
1446
|
type: event.type,
|
|
1423
1447
|
timestamp: new Date(event.timestamp || event.created_at),
|
|
1424
|
-
status: event.status ||
|
|
1448
|
+
status: event.status || "success",
|
|
1425
1449
|
userId: event.user_id || event.userId,
|
|
1426
1450
|
sessionId: event.session_id || event.sessionId,
|
|
1427
1451
|
organizationId: event.organization_id || event.organizationId,
|
|
1428
|
-
metadata: typeof event.metadata ===
|
|
1452
|
+
metadata: typeof event.metadata === "string"
|
|
1429
1453
|
? JSON.parse(event.metadata)
|
|
1430
1454
|
: event.metadata || {},
|
|
1431
1455
|
ipAddress: event.ip_address || event.ipAddress,
|
|
1432
1456
|
userAgent: event.user_agent || event.userAgent,
|
|
1433
|
-
source: event.source ||
|
|
1457
|
+
source: event.source || "app",
|
|
1434
1458
|
display: {
|
|
1435
1459
|
message: event.display_message || event.displayMessage || event.type,
|
|
1436
|
-
severity: event.display_severity || event.displaySeverity ||
|
|
1460
|
+
severity: event.display_severity || event.displaySeverity || "info",
|
|
1437
1461
|
},
|
|
1438
1462
|
}));
|
|
1439
1463
|
return {
|
|
@@ -1455,7 +1479,7 @@ export function createStorageProvider(options) {
|
|
|
1455
1479
|
queryFn = async (q) => await queryMethod(q, params);
|
|
1456
1480
|
}
|
|
1457
1481
|
else {
|
|
1458
|
-
throw new Error(
|
|
1482
|
+
throw new Error("Raw SQL not supported");
|
|
1459
1483
|
}
|
|
1460
1484
|
const result = await queryFn(query);
|
|
1461
1485
|
const hasMore = result.length > limit;
|
|
@@ -1463,19 +1487,19 @@ export function createStorageProvider(options) {
|
|
|
1463
1487
|
id: event.id,
|
|
1464
1488
|
type: event.type,
|
|
1465
1489
|
timestamp: new Date(event.timestamp || event.created_at),
|
|
1466
|
-
status: event.status ||
|
|
1490
|
+
status: event.status || "success",
|
|
1467
1491
|
userId: event.user_id || event.userId,
|
|
1468
1492
|
sessionId: event.session_id || event.sessionId,
|
|
1469
1493
|
organizationId: event.organization_id || event.organizationId,
|
|
1470
|
-
metadata: typeof event.metadata ===
|
|
1494
|
+
metadata: typeof event.metadata === "string"
|
|
1471
1495
|
? JSON.parse(event.metadata)
|
|
1472
1496
|
: event.metadata || {},
|
|
1473
1497
|
ipAddress: event.ip_address || event.ipAddress,
|
|
1474
1498
|
userAgent: event.user_agent || event.userAgent,
|
|
1475
|
-
source: event.source ||
|
|
1499
|
+
source: event.source || "app",
|
|
1476
1500
|
display: {
|
|
1477
1501
|
message: event.display_message || event.displayMessage || event.type,
|
|
1478
|
-
severity: event.display_severity || event.displaySeverity ||
|
|
1502
|
+
severity: event.display_severity || event.displaySeverity || "info",
|
|
1479
1503
|
},
|
|
1480
1504
|
}));
|
|
1481
1505
|
return {
|
|
@@ -1487,7 +1511,7 @@ export function createStorageProvider(options) {
|
|
|
1487
1511
|
}
|
|
1488
1512
|
catch (rawError) {
|
|
1489
1513
|
// If raw SQL also fails, return empty result
|
|
1490
|
-
console.warn(
|
|
1514
|
+
console.warn("Raw SQL query failed:", rawError);
|
|
1491
1515
|
return {
|
|
1492
1516
|
events: [],
|
|
1493
1517
|
hasMore: false,
|
|
@@ -1496,7 +1520,7 @@ export function createStorageProvider(options) {
|
|
|
1496
1520
|
}
|
|
1497
1521
|
}
|
|
1498
1522
|
// If we get here, neither findMany nor raw SQL worked
|
|
1499
|
-
throw new Error(
|
|
1523
|
+
throw new Error("Adapter does not support findMany or raw SQL");
|
|
1500
1524
|
},
|
|
1501
1525
|
};
|
|
1502
1526
|
}
|