@vipin733/nodescope 0.1.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vipin
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,412 @@
1
+ # @vipin733/nodescope
2
+
3
+ > 🔭 A Laravel Telescope-inspired debugging and monitoring package for Node.js and Bun applications
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@vipin733/nodescope.svg)](https://www.npmjs.com/package/@vipin733/nodescope)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## ✨ Features
9
+
10
+ - 📊 **Real-time Monitoring** - Watch requests, queries, and logs as they happen via WebSocket streaming
11
+ - 💾 **Multiple Storage Backends** - Choose from in-memory, SQLite, PostgreSQL, or MySQL
12
+ - 🔌 **Framework Agnostic** - First-class support for Express, Hono, Fastify, and vanilla Node.js
13
+ - 🎨 **Beautiful Dashboard** - React-based UI with dark/light mode (coming soon)
14
+ - 🚀 **Zero Configuration** - Sensible defaults, just install and go
15
+ - 🔍 **Comprehensive Watchers** - Track requests, database queries, logs, exceptions, and more
16
+ - ⚡ **Blazing Fast** - Minimal performance overhead, designed for production use
17
+ - 🌐 **WebSocket Support** - Real-time data streaming to connected clients
18
+
19
+ ## 📦 Installation
20
+
21
+ ```bash
22
+ npm install @vipin733/nodescope
23
+ # or
24
+ pnpm add @vipin733/nodescope
25
+ # or
26
+ yarn add @vipin733/nodescope
27
+ # or
28
+ bun add @vipin733/nodescope
29
+ ```
30
+
31
+ ### Optional Database Drivers
32
+
33
+ NodeScope supports multiple storage backends. Install the driver you need:
34
+
35
+ ```bash
36
+ # SQLite
37
+ npm install better-sqlite3
38
+
39
+ # PostgreSQL
40
+ npm install pg
41
+
42
+ # MySQL
43
+ npm install mysql2
44
+ ```
45
+
46
+ ## 🚀 Quick Start
47
+
48
+ ### Express.js
49
+
50
+ ```typescript
51
+ import express from 'express';
52
+ import { NodeScope } from '@vipin733/nodescope';
53
+
54
+ const app = express();
55
+
56
+ // Initialize NodeScope
57
+ const nodescope = new NodeScope({
58
+ storage: 'sqlite',
59
+ storagePath: './nodescope.db',
60
+ dashboardPath: '/_debug',
61
+ enabled: process.env.NODE_ENV === 'development',
62
+ });
63
+
64
+ // Mount NodeScope middleware and routes
65
+ app.use(nodescope.middleware());
66
+ nodescope.mountExpressRoutes(app);
67
+
68
+ // Your app routes
69
+ app.get('/', (req, res) => {
70
+ res.send('Hello World!');
71
+ });
72
+
73
+ // Start server
74
+ app.listen(3000, () => {
75
+ console.log('Server running on http://localhost:3000');
76
+ console.log('Dashboard available at http://localhost:3000/_debug');
77
+ });
78
+ ```
79
+
80
+ ### Hono (Bun/Node)
81
+
82
+ ```typescript
83
+ import { Hono } from 'hono';
84
+ import { createHonoMiddleware } from '@vipin733/nodescope';
85
+
86
+ const app = new Hono();
87
+
88
+ // Add NodeScope middleware
89
+ app.use('*', createHonoMiddleware({
90
+ storage: 'memory',
91
+ dashboardPath: '/_debug'
92
+ }));
93
+
94
+ // Your app routes
95
+ app.get('/', (c) => c.text('Hello Hono!'));
96
+
97
+ export default app;
98
+ ```
99
+
100
+ ### Fastify
101
+
102
+ ```typescript
103
+ import Fastify from 'fastify';
104
+ import { createFastifyPlugin } from '@vipin733/nodescope';
105
+
106
+ const fastify = Fastify({ logger: true });
107
+
108
+ // Register NodeScope plugin
109
+ await fastify.register(createFastifyPlugin, {
110
+ storage: 'memory',
111
+ dashboardPath: '/_debug',
112
+ });
113
+
114
+ // Your app routes
115
+ fastify.get('/', async (request, reply) => {
116
+ return { hello: 'world' };
117
+ });
118
+
119
+ await fastify.listen({ port: 3000 });
120
+ ```
121
+
122
+ ## ⚙️ Configuration
123
+
124
+ ### Constructor Options
125
+
126
+ ```typescript
127
+ interface NodeScopeOptions {
128
+ // Enable/disable NodeScope (default: true)
129
+ enabled?: boolean;
130
+
131
+ // Storage backend: 'memory' | 'sqlite' | 'postgresql' | 'mysql'
132
+ storage?: StorageType;
133
+
134
+ // Storage configuration
135
+ storagePath?: string; // For SQLite
136
+ databaseUrl?: string; // For PostgreSQL/MySQL
137
+
138
+ // Dashboard configuration
139
+ dashboardPath?: string; // Default: '/_debug'
140
+
141
+ // Data retention
142
+ maxEntries?: number; // Default: 1000
143
+ pruneInterval?: number; // In milliseconds, default: 60000
144
+
145
+ // WebSocket configuration
146
+ wsPath?: string; // Default: '/_debug/ws'
147
+
148
+ // Watchers to enable
149
+ watchers?: {
150
+ request?: boolean;
151
+ query?: boolean;
152
+ cache?: boolean;
153
+ log?: boolean;
154
+ exception?: boolean;
155
+ httpClient?: boolean;
156
+ event?: boolean;
157
+ job?: boolean;
158
+ };
159
+ }
160
+ ```
161
+
162
+ ### Example Configurations
163
+
164
+ #### Development (In-Memory)
165
+
166
+ ```typescript
167
+ const nodescope = new NodeScope({
168
+ storage: 'memory',
169
+ maxEntries: 500,
170
+ });
171
+ ```
172
+
173
+ #### Production (PostgreSQL)
174
+
175
+ ```typescript
176
+ const nodescope = new NodeScope({
177
+ storage: 'postgresql',
178
+ databaseUrl: process.env.DATABASE_URL,
179
+ maxEntries: 5000,
180
+ pruneInterval: 300000, // Prune every 5 minutes
181
+ enabled: process.env.ENABLE_NODESCOPE === 'true',
182
+ });
183
+ ```
184
+
185
+ #### SQLite for Local Development
186
+
187
+ ```typescript
188
+ const nodescope = new NodeScope({
189
+ storage: 'sqlite',
190
+ storagePath: './data/nodescope.db',
191
+ dashboardPath: '/_telescope',
192
+ });
193
+ ```
194
+
195
+ ## 📊 Watchers
196
+
197
+ NodeScope includes the following watchers to monitor different aspects of your application:
198
+
199
+ | Watcher | Description | Status |
200
+ |---------|-------------|--------|
201
+ | **Request** | HTTP requests and responses with timing | ✅ Active |
202
+ | **Query** | Database queries with execution time | ✅ Active |
203
+ | **Cache** | Cache operations (Redis, memory) | 🚧 Coming Soon |
204
+ | **Log** | Console and custom logs | ✅ Active |
205
+ | **Exception** | Errors with stack traces | ✅ Active |
206
+ | **HTTP Client** | Outgoing HTTP requests | 🚧 Coming Soon |
207
+ | **Event** | Application events | 🚧 Coming Soon |
208
+ | **Job** | Background job processing | 🚧 Coming Soon |
209
+
210
+ ## 💾 Storage Options
211
+
212
+ ### Memory (Development)
213
+
214
+ Fast, in-memory storage. **Not recommended for production** as data is lost on restart.
215
+
216
+ ```typescript
217
+ { storage: 'memory' }
218
+ ```
219
+
220
+ ### SQLite (Development/Small Production)
221
+
222
+ Single-file database, perfect for local development and small production deployments.
223
+
224
+ ```typescript
225
+ {
226
+ storage: 'sqlite',
227
+ storagePath: './nodescope.db'
228
+ }
229
+ ```
230
+
231
+ ### PostgreSQL (Production)
232
+
233
+ Production-ready with connection pooling and excellent performance.
234
+
235
+ ```typescript
236
+ {
237
+ storage: 'postgresql',
238
+ databaseUrl: 'postgresql://user:password@localhost:5432/mydb'
239
+ }
240
+ ```
241
+
242
+ ### MySQL (Production)
243
+
244
+ Production-ready MySQL support with connection pooling.
245
+
246
+ ```typescript
247
+ {
248
+ storage: 'mysql',
249
+ databaseUrl: 'mysql://user:password@localhost:3306/mydb'
250
+ }
251
+ ```
252
+
253
+ ## 🔍 Manual Logging
254
+
255
+ You can manually log events to NodeScope:
256
+
257
+ ```typescript
258
+ import { nodescope } from '@vipin733/nodescope';
259
+
260
+ // Log a request
261
+ nodescope.record('request', {
262
+ method: 'GET',
263
+ path: '/api/users',
264
+ statusCode: 200,
265
+ duration: 145,
266
+ });
267
+
268
+ // Log a database query
269
+ nodescope.record('query', {
270
+ sql: 'SELECT * FROM users WHERE id = ?',
271
+ bindings: [1],
272
+ duration: 12,
273
+ });
274
+
275
+ // Log an exception
276
+ nodescope.record('exception', {
277
+ message: 'User not found',
278
+ stack: error.stack,
279
+ });
280
+
281
+ // Log a custom log
282
+ nodescope.record('log', {
283
+ level: 'info',
284
+ message: 'User logged in',
285
+ context: { userId: 123 },
286
+ });
287
+ ```
288
+
289
+ ## 🌐 WebSocket API
290
+
291
+ NodeScope provides real-time updates via WebSocket. Connect to the WebSocket endpoint to receive live data:
292
+
293
+ ```javascript
294
+ const ws = new WebSocket('ws://localhost:3000/_debug/ws');
295
+
296
+ ws.onmessage = (event) => {
297
+ const data = JSON.parse(event.data);
298
+ console.log('New entry:', data);
299
+ };
300
+ ```
301
+
302
+ ## 🔒 Security Considerations
303
+
304
+ **⚠️ Important**: NodeScope can expose sensitive application data. Always ensure proper security measures:
305
+
306
+ 1. **Never enable in production** without authentication
307
+ 2. **Use environment variables** to control when it's enabled
308
+ 3. **Restrict access** to the dashboard endpoint using middleware
309
+ 4. **Consider IP whitelisting** for production debugging
310
+
311
+ ### Example: Protect Dashboard with Auth
312
+
313
+ ```typescript
314
+ // Express example
315
+ app.use('/_debug', (req, res, next) => {
316
+ const auth = req.headers.authorization;
317
+ if (auth !== 'Bearer YOUR_SECRET_TOKEN') {
318
+ return res.status(401).send('Unauthorized');
319
+ }
320
+ next();
321
+ });
322
+
323
+ app.use(nodescope.middleware());
324
+ nodescope.mountExpressRoutes(app);
325
+ ```
326
+
327
+ ## 📖 API Reference
328
+
329
+ ### `NodeScope` Class
330
+
331
+ #### Constructor
332
+
333
+ ```typescript
334
+ new NodeScope(options?: NodeScopeOptions)
335
+ ```
336
+
337
+ #### Methods
338
+
339
+ - `middleware()` - Returns middleware function for your framework
340
+ - `record(type: string, data: any)` - Manually record an entry
341
+ - `getEntries(type?: string, limit?: number)` - Retrieve stored entries
342
+ - `clear()` - Clear all stored entries
343
+ - `mountExpressRoutes(app: Express)` - Mount Express routes (Express only)
344
+
345
+ ### Framework Helpers
346
+
347
+ #### Express
348
+
349
+ ```typescript
350
+ import { NodeScope } from '@vipin733/nodescope';
351
+ const nodescope = new NodeScope(options);
352
+ app.use(nodescope.middleware());
353
+ nodescope.mountExpressRoutes(app);
354
+ ```
355
+
356
+ #### Hono
357
+
358
+ ```typescript
359
+ import { createHonoMiddleware } from '@vipin733/nodescope';
360
+ app.use('*', createHonoMiddleware(options));
361
+ ```
362
+
363
+ #### Fastify
364
+
365
+ ```typescript
366
+ import { createFastifyPlugin } from '@vipin733/nodescope';
367
+ await fastify.register(createFastifyPlugin, options);
368
+ ```
369
+
370
+ ## 🛠️ Development
371
+
372
+ ```bash
373
+ # Clone the repository
374
+ git clone https://github.com/yourusername/nodescope.git
375
+ cd nodescope
376
+
377
+ # Install dependencies
378
+ npm install
379
+
380
+ # Build the package
381
+ npm run build
382
+
383
+ # Run tests
384
+ npm test
385
+
386
+ # Watch mode for development
387
+ npm run dev
388
+ ```
389
+
390
+ ## 📝 Examples
391
+
392
+ Check out the [examples](https://github.com/yourusername/nodescope/tree/main/examples) directory for complete working examples:
393
+
394
+ - Express.js example
395
+ - Hono example
396
+ - Fastify example
397
+
398
+ ## 🤝 Contributing
399
+
400
+ Contributions are welcome! Please feel free to submit a Pull Request.
401
+
402
+ ## 📄 License
403
+
404
+ MIT © [Your Name]
405
+
406
+ ## 🙏 Acknowledgments
407
+
408
+ Inspired by [Laravel Telescope](https://laravel.com/docs/telescope) - the amazing debugging assistant for Laravel applications.
409
+
410
+ ---
411
+
412
+ **Made with ❤️ for the Node.js and Bun communities**
@@ -0,0 +1,198 @@
1
+ import {
2
+ defaultEntryCounts
3
+ } from "./chunk-OF6NKXP5.js";
4
+
5
+ // src/storage/postgresql.ts
6
+ var PostgreSQLStorage = class {
7
+ pool;
8
+ connectionString;
9
+ constructor(connectionString) {
10
+ this.connectionString = connectionString;
11
+ }
12
+ async initialize() {
13
+ const { Pool } = await import("pg");
14
+ this.pool = new Pool({ connectionString: this.connectionString });
15
+ await this.pool.query(`
16
+ CREATE TABLE IF NOT EXISTS nodescope_entries (
17
+ id TEXT PRIMARY KEY,
18
+ batch_id TEXT NOT NULL,
19
+ type TEXT NOT NULL,
20
+ content JSONB NOT NULL,
21
+ tags JSONB NOT NULL,
22
+ created_at TIMESTAMPTZ NOT NULL,
23
+ duration INTEGER,
24
+ memory_usage INTEGER
25
+ );
26
+
27
+ CREATE INDEX IF NOT EXISTS idx_nodescope_entries_batch_id ON nodescope_entries(batch_id);
28
+ CREATE INDEX IF NOT EXISTS idx_nodescope_entries_type ON nodescope_entries(type);
29
+ CREATE INDEX IF NOT EXISTS idx_nodescope_entries_created_at ON nodescope_entries(created_at);
30
+ CREATE INDEX IF NOT EXISTS idx_nodescope_entries_tags ON nodescope_entries USING GIN(tags);
31
+ `);
32
+ }
33
+ async save(entry) {
34
+ await this.pool.query(
35
+ `INSERT INTO nodescope_entries (id, batch_id, type, content, tags, created_at, duration, memory_usage)
36
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
37
+ ON CONFLICT (id) DO UPDATE SET
38
+ content = EXCLUDED.content,
39
+ tags = EXCLUDED.tags,
40
+ duration = EXCLUDED.duration,
41
+ memory_usage = EXCLUDED.memory_usage`,
42
+ [
43
+ entry.id,
44
+ entry.batchId,
45
+ entry.type,
46
+ JSON.stringify(entry.content),
47
+ JSON.stringify(entry.tags),
48
+ entry.createdAt,
49
+ entry.duration ?? null,
50
+ entry.memoryUsage ?? null
51
+ ]
52
+ );
53
+ }
54
+ async saveBatch(entries) {
55
+ const client = await this.pool.connect();
56
+ try {
57
+ await client.query("BEGIN");
58
+ for (const entry of entries) {
59
+ await client.query(
60
+ `INSERT INTO nodescope_entries (id, batch_id, type, content, tags, created_at, duration, memory_usage)
61
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
62
+ ON CONFLICT (id) DO UPDATE SET
63
+ content = EXCLUDED.content,
64
+ tags = EXCLUDED.tags,
65
+ duration = EXCLUDED.duration,
66
+ memory_usage = EXCLUDED.memory_usage`,
67
+ [
68
+ entry.id,
69
+ entry.batchId,
70
+ entry.type,
71
+ JSON.stringify(entry.content),
72
+ JSON.stringify(entry.tags),
73
+ entry.createdAt,
74
+ entry.duration ?? null,
75
+ entry.memoryUsage ?? null
76
+ ]
77
+ );
78
+ }
79
+ await client.query("COMMIT");
80
+ } catch (error) {
81
+ await client.query("ROLLBACK");
82
+ throw error;
83
+ } finally {
84
+ client.release();
85
+ }
86
+ }
87
+ async find(id) {
88
+ const result = await this.pool.query(
89
+ "SELECT * FROM nodescope_entries WHERE id = $1",
90
+ [id]
91
+ );
92
+ if (result.rows.length === 0) return null;
93
+ return this.rowToEntry(result.rows[0]);
94
+ }
95
+ async list(options = {}) {
96
+ const { type, batchId, tags, search, before, after, limit = 50, offset = 0 } = options;
97
+ let where = "1=1";
98
+ const params = [];
99
+ let paramIndex = 1;
100
+ if (type) {
101
+ where += ` AND type = $${paramIndex++}`;
102
+ params.push(type);
103
+ }
104
+ if (batchId) {
105
+ where += ` AND batch_id = $${paramIndex++}`;
106
+ params.push(batchId);
107
+ }
108
+ if (tags && tags.length > 0) {
109
+ where += ` AND tags ?| $${paramIndex++}`;
110
+ params.push(tags);
111
+ }
112
+ if (before) {
113
+ where += ` AND created_at < $${paramIndex++}`;
114
+ params.push(before);
115
+ }
116
+ if (after) {
117
+ where += ` AND created_at > $${paramIndex++}`;
118
+ params.push(after);
119
+ }
120
+ if (search) {
121
+ where += ` AND content::text ILIKE $${paramIndex++}`;
122
+ params.push(`%${search}%`);
123
+ }
124
+ const countResult = await this.pool.query(
125
+ `SELECT COUNT(*) as count FROM nodescope_entries WHERE ${where}`,
126
+ params
127
+ );
128
+ const total = parseInt(countResult.rows[0].count, 10);
129
+ const dataResult = await this.pool.query(
130
+ `SELECT * FROM nodescope_entries WHERE ${where} ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
131
+ [...params, limit, offset]
132
+ );
133
+ return {
134
+ data: dataResult.rows.map((row) => this.rowToEntry(row)),
135
+ total,
136
+ limit,
137
+ offset,
138
+ hasMore: offset + limit < total
139
+ };
140
+ }
141
+ async findByBatch(batchId) {
142
+ const result = await this.pool.query(
143
+ "SELECT * FROM nodescope_entries WHERE batch_id = $1 ORDER BY created_at ASC",
144
+ [batchId]
145
+ );
146
+ return result.rows.map((row) => this.rowToEntry(row));
147
+ }
148
+ async prune(beforeDate) {
149
+ const result = await this.pool.query(
150
+ "DELETE FROM nodescope_entries WHERE created_at < $1",
151
+ [beforeDate]
152
+ );
153
+ return result.rowCount ?? 0;
154
+ }
155
+ async clear() {
156
+ await this.pool.query("DELETE FROM nodescope_entries");
157
+ }
158
+ async stats() {
159
+ const entriesByType = defaultEntryCounts();
160
+ const typeResult = await this.pool.query(
161
+ "SELECT type, COUNT(*) as count FROM nodescope_entries GROUP BY type"
162
+ );
163
+ for (const row of typeResult.rows) {
164
+ entriesByType[row.type] = parseInt(row.count, 10);
165
+ }
166
+ const totalResult = await this.pool.query(
167
+ "SELECT COUNT(*) as count FROM nodescope_entries"
168
+ );
169
+ const rangeResult = await this.pool.query(
170
+ "SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM nodescope_entries"
171
+ );
172
+ return {
173
+ totalEntries: parseInt(totalResult.rows[0].count, 10),
174
+ entriesByType,
175
+ oldestEntry: rangeResult.rows[0].oldest ? new Date(rangeResult.rows[0].oldest) : void 0,
176
+ newestEntry: rangeResult.rows[0].newest ? new Date(rangeResult.rows[0].newest) : void 0
177
+ };
178
+ }
179
+ async close() {
180
+ await this.pool.end();
181
+ }
182
+ rowToEntry(row) {
183
+ return {
184
+ id: row.id,
185
+ batchId: row.batch_id,
186
+ type: row.type,
187
+ content: typeof row.content === "string" ? JSON.parse(row.content) : row.content,
188
+ tags: typeof row.tags === "string" ? JSON.parse(row.tags) : row.tags,
189
+ createdAt: new Date(row.created_at),
190
+ duration: row.duration ?? void 0,
191
+ memoryUsage: row.memory_usage ?? void 0
192
+ };
193
+ }
194
+ };
195
+
196
+ export {
197
+ PostgreSQLStorage
198
+ };