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.
- package/dist/adapters/hono.d.ts.map +1 -1
- package/dist/adapters/hono.js +5 -0
- package/dist/adapters/hono.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/core/handler.d.ts.map +1 -1
- package/dist/core/handler.js +85 -4
- package/dist/core/handler.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/events/helpers.d.ts +22 -0
- package/dist/providers/events/helpers.d.ts.map +1 -0
- package/dist/providers/events/helpers.js +874 -0
- package/dist/providers/events/helpers.js.map +1 -0
- package/dist/public/assets/{main-BIlYgyPi.js → main-B_Zdj1LN.js} +139 -139
- package/dist/public/assets/main-RoeYO1I-.css +1 -0
- package/dist/public/index.html +2 -2
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +193 -0
- package/dist/routes.js.map +1 -1
- package/dist/studio.d.ts.map +1 -1
- package/dist/studio.js +6 -1
- package/dist/studio.js.map +1 -1
- package/dist/types/events.d.ts +43 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +306 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/handler.d.ts +31 -0
- 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 +3 -0
- package/dist/utils/auth-callbacks-injector.d.ts.map +1 -0
- package/dist/utils/auth-callbacks-injector.js +227 -0
- package/dist/utils/auth-callbacks-injector.js.map +1 -0
- package/dist/utils/auth-callbacks-wrapper.d.ts +7 -0
- package/dist/utils/auth-callbacks-wrapper.d.ts.map +1 -0
- package/dist/utils/auth-callbacks-wrapper.js +123 -0
- package/dist/utils/auth-callbacks-wrapper.js.map +1 -0
- package/dist/utils/database-hook-injector.d.ts +3 -0
- package/dist/utils/database-hook-injector.d.ts.map +1 -0
- package/dist/utils/database-hook-injector.js +141 -0
- package/dist/utils/database-hook-injector.js.map +1 -0
- package/dist/utils/email-otp-hooks-injector.d.ts +29 -0
- package/dist/utils/email-otp-hooks-injector.d.ts.map +1 -0
- package/dist/utils/email-otp-hooks-injector.js +134 -0
- package/dist/utils/email-otp-hooks-injector.js.map +1 -0
- package/dist/utils/event-ingestion.d.ts +38 -0
- package/dist/utils/event-ingestion.d.ts.map +1 -0
- package/dist/utils/event-ingestion.js +169 -0
- package/dist/utils/event-ingestion.js.map +1 -0
- package/dist/utils/hook-injector.d.ts +9 -0
- package/dist/utils/hook-injector.d.ts.map +1 -0
- package/dist/utils/hook-injector.js +649 -0
- package/dist/utils/hook-injector.js.map +1 -0
- package/dist/utils/html-injector.d.ts +17 -0
- package/dist/utils/html-injector.d.ts.map +1 -1
- package/dist/utils/html-injector.js +15 -0
- package/dist/utils/html-injector.js.map +1 -1
- package/dist/utils/org-hooks-injector.d.ts +74 -0
- package/dist/utils/org-hooks-injector.d.ts.map +1 -0
- package/dist/utils/org-hooks-injector.js +678 -0
- package/dist/utils/org-hooks-injector.js.map +1 -0
- package/dist/utils/org-hooks-wrapper.d.ts +74 -0
- package/dist/utils/org-hooks-wrapper.d.ts.map +1 -0
- package/dist/utils/org-hooks-wrapper.js +687 -0
- package/dist/utils/org-hooks-wrapper.js.map +1 -0
- package/dist/utils/organization-hooks-wrapper.d.ts +38 -0
- package/dist/utils/organization-hooks-wrapper.d.ts.map +1 -0
- package/dist/utils/organization-hooks-wrapper.js +297 -0
- package/dist/utils/organization-hooks-wrapper.js.map +1 -0
- package/package.json +7 -4
- package/public/assets/{main-BIlYgyPi.js → main-B_Zdj1LN.js} +139 -139
- package/public/assets/main-RoeYO1I-.css +1 -0
- package/public/index.html +2 -2
- package/dist/public/assets/main-s8HrXBxq.css +0 -1
- 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
|