@vinetechke/next-error-logger 0.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +442 -0
- package/dist/adapters/drizzle.cjs +123 -0
- package/dist/adapters/drizzle.cjs.map +1 -0
- package/dist/adapters/drizzle.d.cts +76 -0
- package/dist/adapters/drizzle.d.ts +76 -0
- package/dist/adapters/drizzle.js +99 -0
- package/dist/adapters/drizzle.js.map +1 -0
- package/dist/adapters/prisma.cjs +120 -0
- package/dist/adapters/prisma.cjs.map +1 -0
- package/dist/adapters/prisma.d.cts +75 -0
- package/dist/adapters/prisma.d.ts +75 -0
- package/dist/adapters/prisma.js +96 -0
- package/dist/adapters/prisma.js.map +1 -0
- package/dist/adapters/sql.cjs +206 -0
- package/dist/adapters/sql.cjs.map +1 -0
- package/dist/adapters/sql.d.cts +111 -0
- package/dist/adapters/sql.d.ts +111 -0
- package/dist/adapters/sql.js +182 -0
- package/dist/adapters/sql.js.map +1 -0
- package/dist/api/index.cjs +257 -0
- package/dist/api/index.cjs.map +1 -0
- package/dist/api/index.d.cts +137 -0
- package/dist/api/index.d.ts +137 -0
- package/dist/api/index.js +231 -0
- package/dist/api/index.js.map +1 -0
- package/dist/auth/clerk.cjs +60 -0
- package/dist/auth/clerk.cjs.map +1 -0
- package/dist/auth/clerk.d.cts +83 -0
- package/dist/auth/clerk.d.ts +83 -0
- package/dist/auth/clerk.js +36 -0
- package/dist/auth/clerk.js.map +1 -0
- package/dist/auth/next-auth.cjs +50 -0
- package/dist/auth/next-auth.cjs.map +1 -0
- package/dist/auth/next-auth.d.cts +53 -0
- package/dist/auth/next-auth.d.ts +53 -0
- package/dist/auth/next-auth.js +26 -0
- package/dist/auth/next-auth.js.map +1 -0
- package/dist/components/index.cjs +1175 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.cts +141 -0
- package/dist/components/index.d.ts +141 -0
- package/dist/components/index.js +1147 -0
- package/dist/components/index.js.map +1 -0
- package/dist/index.cjs +241 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +109 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +212 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/drizzle.cjs +100 -0
- package/dist/schemas/drizzle.cjs.map +1 -0
- package/dist/schemas/drizzle.d.cts +32 -0
- package/dist/schemas/drizzle.d.ts +32 -0
- package/dist/schemas/drizzle.js +74 -0
- package/dist/schemas/drizzle.js.map +1 -0
- package/dist/types-C3x_Ry2e.d.cts +195 -0
- package/dist/types-C3x_Ry2e.d.ts +195 -0
- package/package.json +128 -0
- package/schemas/prisma.prisma +23 -0
- package/schemas/schema.sql +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VineTech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# @vinetechke/next-error-logger
|
|
2
|
+
|
|
3
|
+
> **Beta Release** - This package is under active development. APIs may change before v1.0.0.
|
|
4
|
+
|
|
5
|
+
Simple error logging for Next.js apps with user context, multiple database adapters, and a built-in dashboard.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@vinetechke/next-error-logger)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **User Context** - Captures user ID, email, and name from your auth provider
|
|
13
|
+
- **Multiple Database Adapters** - Works with Prisma, Drizzle, or raw SQL
|
|
14
|
+
- **Multiple Auth Adapters** - Supports NextAuth, Clerk, or custom auth
|
|
15
|
+
- **Built-in Dashboard** - Ready-to-use LogViewer component
|
|
16
|
+
- **Error Boundary** - React error boundary with automatic logging
|
|
17
|
+
- **TypeScript First** - Full type safety
|
|
18
|
+
- **Request Context** - Auto-captures path, method, IP, and user agent
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Next.js 14 or later
|
|
23
|
+
- React 18 or later
|
|
24
|
+
- Node.js 18 or later
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @vinetechke/next-error-logger
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm add @vinetechke/next-error-logger
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
yarn add @vinetechke/next-error-logger
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
### 1. Set Up Your Database Schema
|
|
43
|
+
|
|
44
|
+
#### Prisma
|
|
45
|
+
|
|
46
|
+
Add to your `schema.prisma`:
|
|
47
|
+
|
|
48
|
+
```prisma
|
|
49
|
+
model ErrorLog {
|
|
50
|
+
id String @id @default(cuid())
|
|
51
|
+
level String
|
|
52
|
+
message String @db.Text
|
|
53
|
+
stack String? @db.Text
|
|
54
|
+
userId String?
|
|
55
|
+
userEmail String?
|
|
56
|
+
userName String?
|
|
57
|
+
path String?
|
|
58
|
+
method String?
|
|
59
|
+
userAgent String?
|
|
60
|
+
ip String?
|
|
61
|
+
metadata Json?
|
|
62
|
+
createdAt DateTime @default(now())
|
|
63
|
+
|
|
64
|
+
@@index([level])
|
|
65
|
+
@@index([userId])
|
|
66
|
+
@@index([createdAt])
|
|
67
|
+
@@map("error_logs")
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then run:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx prisma db push
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Drizzle
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { pgTable, text, timestamp, json, index } from 'drizzle-orm/pg-core'
|
|
81
|
+
|
|
82
|
+
export const errorLogs = pgTable('error_logs', {
|
|
83
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
84
|
+
level: text('level').notNull(),
|
|
85
|
+
message: text('message').notNull(),
|
|
86
|
+
stack: text('stack'),
|
|
87
|
+
userId: text('user_id'),
|
|
88
|
+
userEmail: text('user_email'),
|
|
89
|
+
userName: text('user_name'),
|
|
90
|
+
path: text('path'),
|
|
91
|
+
method: text('method'),
|
|
92
|
+
userAgent: text('user_agent'),
|
|
93
|
+
ip: text('ip'),
|
|
94
|
+
metadata: json('metadata'),
|
|
95
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
96
|
+
}, (table) => ({
|
|
97
|
+
levelIdx: index('error_logs_level_idx').on(table.level),
|
|
98
|
+
userIdIdx: index('error_logs_user_id_idx').on(table.userId),
|
|
99
|
+
createdAtIdx: index('error_logs_created_at_idx').on(table.createdAt),
|
|
100
|
+
}))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Raw SQL
|
|
104
|
+
|
|
105
|
+
See [schemas/schema.sql](./schemas/schema.sql) for PostgreSQL, MySQL, and SQLite schemas.
|
|
106
|
+
|
|
107
|
+
### 2. Initialize the Logger
|
|
108
|
+
|
|
109
|
+
Create `lib/error-logger.ts`:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { initErrorLogger, errorLogger } from '@vinetechke/next-error-logger'
|
|
113
|
+
import { createPrismaAdapter } from '@vinetechke/next-error-logger/adapters/prisma'
|
|
114
|
+
import { createNextAuthAdapter } from '@vinetechke/next-error-logger/auth/next-auth'
|
|
115
|
+
import { prisma } from '@/lib/prisma'
|
|
116
|
+
import { auth } from '@/auth'
|
|
117
|
+
|
|
118
|
+
initErrorLogger({
|
|
119
|
+
adapter: createPrismaAdapter(prisma),
|
|
120
|
+
authAdapter: createNextAuthAdapter(auth),
|
|
121
|
+
retentionDays: 30,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
export { errorLogger }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 3. Log Errors in Your API Routes
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { errorLogger } from '@/lib/error-logger'
|
|
131
|
+
|
|
132
|
+
export async function POST(request: Request) {
|
|
133
|
+
const log = errorLogger.fromRequest(request)
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const body = await request.json()
|
|
137
|
+
// ... process order
|
|
138
|
+
|
|
139
|
+
await log.info('Order created', { orderId: order.id })
|
|
140
|
+
return Response.json(order)
|
|
141
|
+
} catch (error) {
|
|
142
|
+
await log.error('Failed to create order', error as Error, {
|
|
143
|
+
body: await request.json(),
|
|
144
|
+
})
|
|
145
|
+
return Response.json({ error: 'Failed to create order' }, { status: 500 })
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 4. Create API Routes for the Dashboard
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// app/api/admin/logs/route.ts
|
|
154
|
+
import { createLogAPIHandlers } from '@vinetechke/next-error-logger/api'
|
|
155
|
+
import { auth } from '@/auth'
|
|
156
|
+
|
|
157
|
+
const { GET, DELETE } = createLogAPIHandlers({
|
|
158
|
+
isAuthorized: async () => {
|
|
159
|
+
const session = await auth()
|
|
160
|
+
return session?.user?.role === 'ADMIN'
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
export { GET, DELETE }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// app/api/admin/logs/[id]/route.ts
|
|
169
|
+
import { createLogDetailAPIHandlers } from '@vinetechke/next-error-logger/api'
|
|
170
|
+
import { auth } from '@/auth'
|
|
171
|
+
|
|
172
|
+
const { GET, DELETE } = createLogDetailAPIHandlers({
|
|
173
|
+
isAuthorized: async () => {
|
|
174
|
+
const session = await auth()
|
|
175
|
+
return session?.user?.role === 'ADMIN'
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
export { GET, DELETE }
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 5. Add the Dashboard Page
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
// app/admin/logs/page.tsx
|
|
186
|
+
import { LogViewer } from '@vinetechke/next-error-logger/components'
|
|
187
|
+
|
|
188
|
+
export default function LogsPage() {
|
|
189
|
+
return (
|
|
190
|
+
<div className="p-6">
|
|
191
|
+
<h1 className="text-2xl font-bold mb-6">Error Logs</h1>
|
|
192
|
+
<LogViewer
|
|
193
|
+
apiBasePath="/api/admin/logs"
|
|
194
|
+
pageSize={50}
|
|
195
|
+
showDelete
|
|
196
|
+
autoRefresh={30}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Database Adapters
|
|
204
|
+
|
|
205
|
+
### Prisma
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { createPrismaAdapter } from '@vinetechke/next-error-logger/adapters/prisma'
|
|
209
|
+
import { prisma } from '@/lib/prisma'
|
|
210
|
+
|
|
211
|
+
const adapter = createPrismaAdapter(prisma)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Drizzle
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { createDrizzleAdapter } from '@vinetechke/next-error-logger/adapters/drizzle'
|
|
218
|
+
import { db } from '@/lib/db'
|
|
219
|
+
import { errorLogs } from '@/lib/schema'
|
|
220
|
+
import { eq, and, or, like, lt, gte, lte, desc, asc } from 'drizzle-orm'
|
|
221
|
+
|
|
222
|
+
const adapter = createDrizzleAdapter({
|
|
223
|
+
db,
|
|
224
|
+
table: errorLogs,
|
|
225
|
+
operators: { eq, and, or, like, lt, gte, lte, desc, asc },
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Raw SQL
|
|
230
|
+
|
|
231
|
+
Works with any SQL database:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { createSQLAdapter } from '@vinetechke/next-error-logger/adapters/sql'
|
|
235
|
+
import { Pool } from 'pg'
|
|
236
|
+
|
|
237
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
|
|
238
|
+
|
|
239
|
+
const adapter = createSQLAdapter({
|
|
240
|
+
executor: {
|
|
241
|
+
query: async (sql, params) => {
|
|
242
|
+
const result = await pool.query(sql, params)
|
|
243
|
+
return result.rows
|
|
244
|
+
},
|
|
245
|
+
execute: async (sql, params) => {
|
|
246
|
+
const result = await pool.query(sql, params)
|
|
247
|
+
return result.rowCount || 0
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
dialect: 'postgres',
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Auth Adapters
|
|
255
|
+
|
|
256
|
+
### NextAuth (Auth.js v5)
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { createNextAuthAdapter } from '@vinetechke/next-error-logger/auth/next-auth'
|
|
260
|
+
import { auth } from '@/auth'
|
|
261
|
+
|
|
262
|
+
const authAdapter = createNextAuthAdapter(auth)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### NextAuth v4
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { createNextAuthAdapter } from '@vinetechke/next-error-logger/auth/next-auth'
|
|
269
|
+
import { getServerSession } from 'next-auth'
|
|
270
|
+
import { authOptions } from '@/app/api/auth/[...nextauth]/route'
|
|
271
|
+
|
|
272
|
+
const authAdapter = createNextAuthAdapter(async () => {
|
|
273
|
+
return getServerSession(authOptions)
|
|
274
|
+
})
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Clerk
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { createClerkAdapter } from '@vinetechke/next-error-logger/auth/clerk'
|
|
281
|
+
import { auth, clerkClient } from '@clerk/nextjs/server'
|
|
282
|
+
|
|
283
|
+
const authAdapter = createClerkAdapter({
|
|
284
|
+
auth,
|
|
285
|
+
fetchUser: async (userId) => {
|
|
286
|
+
return clerkClient.users.getUser(userId)
|
|
287
|
+
},
|
|
288
|
+
})
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Custom Auth
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import type { AuthAdapter } from '@vinetechke/next-error-logger'
|
|
295
|
+
|
|
296
|
+
const customAuthAdapter: AuthAdapter = {
|
|
297
|
+
async getUser() {
|
|
298
|
+
const user = await getMyUser()
|
|
299
|
+
if (!user) return null
|
|
300
|
+
return {
|
|
301
|
+
id: user.id,
|
|
302
|
+
email: user.email,
|
|
303
|
+
name: user.name,
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## API Reference
|
|
310
|
+
|
|
311
|
+
### errorLogger
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
// Simple logging
|
|
315
|
+
await errorLogger.error('Something went wrong', error)
|
|
316
|
+
await errorLogger.warn('Deprecated API used')
|
|
317
|
+
await errorLogger.info('User completed checkout')
|
|
318
|
+
await errorLogger.debug('Debug info')
|
|
319
|
+
|
|
320
|
+
// With request context (recommended for API routes)
|
|
321
|
+
const log = errorLogger.fromRequest(request)
|
|
322
|
+
await log.error('API failed', error, { orderId: '123' })
|
|
323
|
+
|
|
324
|
+
// With explicit user context
|
|
325
|
+
await errorLogger.withUser({ id: 'user-123', email: 'user@example.com' })
|
|
326
|
+
.error('User action failed', error)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### LogViewer Component
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
<LogViewer
|
|
333
|
+
apiBasePath="/api/admin/logs"
|
|
334
|
+
pageSize={50}
|
|
335
|
+
showDelete={true}
|
|
336
|
+
autoRefresh={30}
|
|
337
|
+
className="my-custom-class"
|
|
338
|
+
theme={{
|
|
339
|
+
errorBg: '#fee2e2',
|
|
340
|
+
errorText: '#dc2626',
|
|
341
|
+
warnBg: '#fef3c7',
|
|
342
|
+
warnText: '#d97706',
|
|
343
|
+
}}
|
|
344
|
+
onLogSelect={(log) => {}}
|
|
345
|
+
/>
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### ErrorBoundary Component
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
import { ErrorBoundary } from '@vinetechke/next-error-logger/components'
|
|
352
|
+
import { errorLogger } from '@/lib/error-logger'
|
|
353
|
+
|
|
354
|
+
<ErrorBoundary
|
|
355
|
+
fallback={(error, reset) => (
|
|
356
|
+
<div>
|
|
357
|
+
<h2>Something went wrong!</h2>
|
|
358
|
+
<button onClick={reset}>Try again</button>
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
361
|
+
onError={async (error, info) => {
|
|
362
|
+
await errorLogger.error('React render error', error, {
|
|
363
|
+
metadata: { componentStack: info.componentStack },
|
|
364
|
+
})
|
|
365
|
+
}}
|
|
366
|
+
>
|
|
367
|
+
<MyApp />
|
|
368
|
+
</ErrorBoundary>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Configuration Options
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
initErrorLogger({
|
|
375
|
+
adapter: createPrismaAdapter(prisma),
|
|
376
|
+
authAdapter: createNextAuthAdapter(auth),
|
|
377
|
+
retentionDays: 30,
|
|
378
|
+
levels: ['error', 'warn'],
|
|
379
|
+
consoleInDev: true,
|
|
380
|
+
})
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
| Option | Type | Default | Description |
|
|
384
|
+
|--------|------|---------|-------------|
|
|
385
|
+
| `adapter` | `DatabaseAdapter` | required | Database adapter for storing logs |
|
|
386
|
+
| `authAdapter` | `AuthAdapter` | - | Auth adapter for user context |
|
|
387
|
+
| `retentionDays` | `number` | `30` | Days to retain logs |
|
|
388
|
+
| `levels` | `LogLevel[]` | all | Only capture these levels |
|
|
389
|
+
| `consoleInDev` | `boolean` | `true` | Console output in development |
|
|
390
|
+
|
|
391
|
+
## TypeScript
|
|
392
|
+
|
|
393
|
+
Full TypeScript support with exported types:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
import type {
|
|
397
|
+
ErrorLogEntry,
|
|
398
|
+
LogLevel,
|
|
399
|
+
DatabaseAdapter,
|
|
400
|
+
AuthAdapter,
|
|
401
|
+
ErrorLoggerConfig,
|
|
402
|
+
QueryOptions,
|
|
403
|
+
LogViewerProps,
|
|
404
|
+
} from '@vinetechke/next-error-logger'
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Development
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
# Install dependencies
|
|
411
|
+
pnpm install
|
|
412
|
+
|
|
413
|
+
# Run tests
|
|
414
|
+
pnpm test
|
|
415
|
+
|
|
416
|
+
# Build
|
|
417
|
+
pnpm build
|
|
418
|
+
|
|
419
|
+
# Lint
|
|
420
|
+
pnpm lint
|
|
421
|
+
|
|
422
|
+
# Format
|
|
423
|
+
pnpm format
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Changelog
|
|
427
|
+
|
|
428
|
+
### 0.1.0-beta.1
|
|
429
|
+
|
|
430
|
+
- Initial beta release
|
|
431
|
+
- Prisma, Drizzle, and raw SQL adapters
|
|
432
|
+
- NextAuth and Clerk auth adapters
|
|
433
|
+
- LogViewer dashboard component
|
|
434
|
+
- ErrorBoundary component
|
|
435
|
+
|
|
436
|
+
## Contributing
|
|
437
|
+
|
|
438
|
+
Contributions welcome. Please open an issue to discuss changes before submitting a PR.
|
|
439
|
+
|
|
440
|
+
## License
|
|
441
|
+
|
|
442
|
+
MIT License - see [LICENSE](./LICENSE) for details.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/adapters/drizzle.ts
|
|
22
|
+
var drizzle_exports = {};
|
|
23
|
+
__export(drizzle_exports, {
|
|
24
|
+
createDrizzleAdapter: () => createDrizzleAdapter
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(drizzle_exports);
|
|
27
|
+
function createDrizzleAdapter(config) {
|
|
28
|
+
const { db, table, operators } = config;
|
|
29
|
+
const { eq, and, or, like, lt, gte, lte, desc, asc } = operators;
|
|
30
|
+
return {
|
|
31
|
+
async create(entry) {
|
|
32
|
+
const id = crypto.randomUUID();
|
|
33
|
+
const result = await db.insert(table).values({
|
|
34
|
+
id,
|
|
35
|
+
level: entry.level,
|
|
36
|
+
message: entry.message,
|
|
37
|
+
stack: entry.stack,
|
|
38
|
+
userId: entry.userId,
|
|
39
|
+
userEmail: entry.userEmail,
|
|
40
|
+
userName: entry.userName,
|
|
41
|
+
path: entry.path,
|
|
42
|
+
method: entry.method,
|
|
43
|
+
userAgent: entry.userAgent,
|
|
44
|
+
ip: entry.ip,
|
|
45
|
+
metadata: entry.metadata
|
|
46
|
+
}).returning();
|
|
47
|
+
return result[0];
|
|
48
|
+
},
|
|
49
|
+
async findMany(options) {
|
|
50
|
+
const {
|
|
51
|
+
page = 1,
|
|
52
|
+
limit = 50,
|
|
53
|
+
level,
|
|
54
|
+
userId,
|
|
55
|
+
search,
|
|
56
|
+
startDate,
|
|
57
|
+
endDate,
|
|
58
|
+
orderBy = "createdAt",
|
|
59
|
+
order = "desc"
|
|
60
|
+
} = options;
|
|
61
|
+
const conditions = [];
|
|
62
|
+
if (level) {
|
|
63
|
+
conditions.push(eq(table.level, level));
|
|
64
|
+
}
|
|
65
|
+
if (userId) {
|
|
66
|
+
conditions.push(eq(table.userId, userId));
|
|
67
|
+
}
|
|
68
|
+
if (search) {
|
|
69
|
+
conditions.push(
|
|
70
|
+
or(
|
|
71
|
+
like(table.message, `%${search}%`),
|
|
72
|
+
like(table.stack, `%${search}%`),
|
|
73
|
+
like(table.path, `%${search}%`),
|
|
74
|
+
like(table.userEmail, `%${search}%`)
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (startDate) {
|
|
79
|
+
conditions.push(gte(table.createdAt, startDate));
|
|
80
|
+
}
|
|
81
|
+
if (endDate) {
|
|
82
|
+
conditions.push(lte(table.createdAt, endDate));
|
|
83
|
+
}
|
|
84
|
+
const whereCondition = conditions.length > 0 ? and(...conditions) : void 0;
|
|
85
|
+
const orderFn = order === "desc" ? desc : asc;
|
|
86
|
+
const orderColumn = orderBy === "level" ? table.level : table.createdAt;
|
|
87
|
+
let query = db.select().from(table);
|
|
88
|
+
if (whereCondition) {
|
|
89
|
+
query = query.where(whereCondition);
|
|
90
|
+
}
|
|
91
|
+
const logs = await query.orderBy(orderFn(orderColumn)).limit(limit).offset((page - 1) * limit);
|
|
92
|
+
const total = await db.$count(table, whereCondition);
|
|
93
|
+
return { logs, total };
|
|
94
|
+
},
|
|
95
|
+
async findById(id) {
|
|
96
|
+
const results = await db.select().from(table).where(eq(table.id, id)).limit(1).offset(0);
|
|
97
|
+
return results[0] || null;
|
|
98
|
+
},
|
|
99
|
+
async delete(id) {
|
|
100
|
+
await db.delete(table).where(eq(table.id, id));
|
|
101
|
+
},
|
|
102
|
+
async deleteMany(options) {
|
|
103
|
+
const conditions = [];
|
|
104
|
+
if (options.before) {
|
|
105
|
+
conditions.push(lt(table.createdAt, options.before));
|
|
106
|
+
}
|
|
107
|
+
if (options.level) {
|
|
108
|
+
conditions.push(eq(table.level, options.level));
|
|
109
|
+
}
|
|
110
|
+
const whereCondition = conditions.length > 0 ? and(...conditions) : void 0;
|
|
111
|
+
if (!whereCondition) {
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
const result = await db.delete(table).where(whereCondition);
|
|
115
|
+
return result.rowCount || 0;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
120
|
+
0 && (module.exports = {
|
|
121
|
+
createDrizzleAdapter
|
|
122
|
+
});
|
|
123
|
+
//# sourceMappingURL=drizzle.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/drizzle.ts"],"sourcesContent":["import type { DatabaseAdapter, ErrorLogEntry, QueryOptions } from '../types'\n\n/**\n * Drizzle table interface\n * Using flexible typing to support various Drizzle table definitions\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype DrizzleTable = any\n\n/**\n * Drizzle database interface\n * Using flexible typing to support various Drizzle dialects and query patterns\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype DrizzleDB = any\n\n/**\n * Configuration for the Drizzle adapter\n */\nexport interface DrizzleAdapterConfig {\n /** Your Drizzle database instance */\n db: DrizzleDB\n /** Your ErrorLog table definition */\n table: DrizzleTable\n /** Drizzle operators (eq, and, or, like, lt, desc, etc.) */\n operators: {\n eq: (column: unknown, value: unknown) => unknown\n and: (...conditions: unknown[]) => unknown\n or: (...conditions: unknown[]) => unknown\n like: (column: unknown, value: string) => unknown\n lt: (column: unknown, value: unknown) => unknown\n gte: (column: unknown, value: unknown) => unknown\n lte: (column: unknown, value: unknown) => unknown\n desc: (column: unknown) => unknown\n asc: (column: unknown) => unknown\n }\n}\n\n/**\n * Create a Drizzle database adapter\n *\n * Requires an errorLogs table in your Drizzle schema:\n *\n * ```ts\n * // schema.ts\n * import { pgTable, text, timestamp, json } from 'drizzle-orm/pg-core'\n *\n * export const errorLogs = pgTable('error_logs', {\n * id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),\n * level: text('level').notNull(),\n * message: text('message').notNull(),\n * stack: text('stack'),\n * userId: text('user_id'),\n * userEmail: text('user_email'),\n * userName: text('user_name'),\n * path: text('path'),\n * method: text('method'),\n * userAgent: text('user_agent'),\n * ip: text('ip'),\n * metadata: json('metadata'),\n * createdAt: timestamp('created_at').defaultNow().notNull(),\n * })\n * ```\n *\n * @example\n * ```ts\n * import { createDrizzleAdapter } from '@vinetechke/next-error-logger/adapters/drizzle'\n * import { db } from '@/lib/db'\n * import { errorLogs } from '@/lib/schema'\n * import { eq, and, or, like, lt, gte, lte, desc, asc } from 'drizzle-orm'\n *\n * const adapter = createDrizzleAdapter({\n * db,\n * table: errorLogs,\n * operators: { eq, and, or, like, lt, gte, lte, desc, asc },\n * })\n * ```\n */\nexport function createDrizzleAdapter(\n config: DrizzleAdapterConfig,\n): DatabaseAdapter {\n const { db, table, operators } = config\n const { eq, and, or, like, lt, gte, lte, desc, asc } = operators\n\n return {\n async create(entry) {\n const id = crypto.randomUUID()\n const result = await db\n .insert(table)\n .values({\n id,\n level: entry.level,\n message: entry.message,\n stack: entry.stack,\n userId: entry.userId,\n userEmail: entry.userEmail,\n userName: entry.userName,\n path: entry.path,\n method: entry.method,\n userAgent: entry.userAgent,\n ip: entry.ip,\n metadata: entry.metadata,\n })\n .returning()\n\n return result[0] as ErrorLogEntry\n },\n\n async findMany(options: QueryOptions) {\n const {\n page = 1,\n limit = 50,\n level,\n userId,\n search,\n startDate,\n endDate,\n orderBy = 'createdAt',\n order = 'desc',\n } = options\n\n // Build conditions array\n const conditions: unknown[] = []\n\n if (level) {\n conditions.push(eq(table.level, level))\n }\n\n if (userId) {\n conditions.push(eq(table.userId, userId))\n }\n\n if (search) {\n conditions.push(\n or(\n like(table.message, `%${search}%`),\n like(table.stack, `%${search}%`),\n like(table.path, `%${search}%`),\n like(table.userEmail, `%${search}%`),\n ),\n )\n }\n\n if (startDate) {\n conditions.push(gte(table.createdAt, startDate))\n }\n\n if (endDate) {\n conditions.push(lte(table.createdAt, endDate))\n }\n\n const whereCondition =\n conditions.length > 0 ? and(...conditions) : undefined\n const orderFn = order === 'desc' ? desc : asc\n const orderColumn =\n orderBy === 'level' ? table.level : table.createdAt\n\n let query = db.select().from(table)\n\n if (whereCondition) {\n query = query.where(whereCondition) as typeof query\n }\n\n const logs = await query\n .orderBy(orderFn(orderColumn))\n .limit(limit)\n .offset((page - 1) * limit)\n\n const total = await db.$count(table, whereCondition)\n\n return { logs: logs as ErrorLogEntry[], total }\n },\n\n async findById(id: string) {\n const results = await db\n .select()\n .from(table)\n .where(eq(table.id, id))\n .limit(1)\n .offset(0)\n\n return (results[0] as ErrorLogEntry) || null\n },\n\n async delete(id: string) {\n await db.delete(table).where(eq(table.id, id))\n },\n\n async deleteMany(options) {\n const conditions: unknown[] = []\n\n if (options.before) {\n conditions.push(lt(table.createdAt, options.before))\n }\n\n if (options.level) {\n conditions.push(eq(table.level, options.level))\n }\n\n const whereCondition =\n conditions.length > 0 ? and(...conditions) : undefined\n\n if (!whereCondition) {\n return 0\n }\n\n const result = await db.delete(table).where(whereCondition)\n return result.rowCount || 0\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { D as DatabaseAdapter } from '../types-C3x_Ry2e.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Drizzle table interface
|
|
5
|
+
* Using flexible typing to support various Drizzle table definitions
|
|
6
|
+
*/
|
|
7
|
+
type DrizzleTable = any;
|
|
8
|
+
/**
|
|
9
|
+
* Drizzle database interface
|
|
10
|
+
* Using flexible typing to support various Drizzle dialects and query patterns
|
|
11
|
+
*/
|
|
12
|
+
type DrizzleDB = any;
|
|
13
|
+
/**
|
|
14
|
+
* Configuration for the Drizzle adapter
|
|
15
|
+
*/
|
|
16
|
+
interface DrizzleAdapterConfig {
|
|
17
|
+
/** Your Drizzle database instance */
|
|
18
|
+
db: DrizzleDB;
|
|
19
|
+
/** Your ErrorLog table definition */
|
|
20
|
+
table: DrizzleTable;
|
|
21
|
+
/** Drizzle operators (eq, and, or, like, lt, desc, etc.) */
|
|
22
|
+
operators: {
|
|
23
|
+
eq: (column: unknown, value: unknown) => unknown;
|
|
24
|
+
and: (...conditions: unknown[]) => unknown;
|
|
25
|
+
or: (...conditions: unknown[]) => unknown;
|
|
26
|
+
like: (column: unknown, value: string) => unknown;
|
|
27
|
+
lt: (column: unknown, value: unknown) => unknown;
|
|
28
|
+
gte: (column: unknown, value: unknown) => unknown;
|
|
29
|
+
lte: (column: unknown, value: unknown) => unknown;
|
|
30
|
+
desc: (column: unknown) => unknown;
|
|
31
|
+
asc: (column: unknown) => unknown;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create a Drizzle database adapter
|
|
36
|
+
*
|
|
37
|
+
* Requires an errorLogs table in your Drizzle schema:
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* // schema.ts
|
|
41
|
+
* import { pgTable, text, timestamp, json } from 'drizzle-orm/pg-core'
|
|
42
|
+
*
|
|
43
|
+
* export const errorLogs = pgTable('error_logs', {
|
|
44
|
+
* id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
45
|
+
* level: text('level').notNull(),
|
|
46
|
+
* message: text('message').notNull(),
|
|
47
|
+
* stack: text('stack'),
|
|
48
|
+
* userId: text('user_id'),
|
|
49
|
+
* userEmail: text('user_email'),
|
|
50
|
+
* userName: text('user_name'),
|
|
51
|
+
* path: text('path'),
|
|
52
|
+
* method: text('method'),
|
|
53
|
+
* userAgent: text('user_agent'),
|
|
54
|
+
* ip: text('ip'),
|
|
55
|
+
* metadata: json('metadata'),
|
|
56
|
+
* createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
57
|
+
* })
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { createDrizzleAdapter } from '@vinetechke/next-error-logger/adapters/drizzle'
|
|
63
|
+
* import { db } from '@/lib/db'
|
|
64
|
+
* import { errorLogs } from '@/lib/schema'
|
|
65
|
+
* import { eq, and, or, like, lt, gte, lte, desc, asc } from 'drizzle-orm'
|
|
66
|
+
*
|
|
67
|
+
* const adapter = createDrizzleAdapter({
|
|
68
|
+
* db,
|
|
69
|
+
* table: errorLogs,
|
|
70
|
+
* operators: { eq, and, or, like, lt, gte, lte, desc, asc },
|
|
71
|
+
* })
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function createDrizzleAdapter(config: DrizzleAdapterConfig): DatabaseAdapter;
|
|
75
|
+
|
|
76
|
+
export { type DrizzleAdapterConfig, createDrizzleAdapter };
|