@vipin733/nodescope 0.1.0 → 0.2.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/README.md +114 -1
- package/dist/index.cjs +321 -0
- package/dist/index.d.cts +100 -1
- package/dist/index.d.ts +100 -1
- package/dist/index.js +316 -0
- package/package.json +16 -5
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
- 📊 **Real-time Monitoring** - Watch requests, queries, and logs as they happen via WebSocket streaming
|
|
11
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
|
|
12
|
+
- 🔌 **Framework Agnostic** - First-class support for Express, Hono, Fastify, NestJS, and vanilla Node.js
|
|
13
13
|
- 🎨 **Beautiful Dashboard** - React-based UI with dark/light mode (coming soon)
|
|
14
14
|
- 🚀 **Zero Configuration** - Sensible defaults, just install and go
|
|
15
15
|
- 🔍 **Comprehensive Watchers** - Track requests, database queries, logs, exceptions, and more
|
|
@@ -119,6 +119,96 @@ fastify.get('/', async (request, reply) => {
|
|
|
119
119
|
await fastify.listen({ port: 3000 });
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
### NestJS
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
|
|
126
|
+
import { NestFactory } from '@nestjs/core';
|
|
127
|
+
import {
|
|
128
|
+
NodeScope,
|
|
129
|
+
NodeScopeModule,
|
|
130
|
+
NodeScopeMiddleware,
|
|
131
|
+
setupNodeScopeRoutes
|
|
132
|
+
} from '@vipin733/nodescope';
|
|
133
|
+
|
|
134
|
+
@Module({
|
|
135
|
+
imports: [
|
|
136
|
+
NodeScopeModule.forRoot({
|
|
137
|
+
storage: 'memory',
|
|
138
|
+
dashboardPath: '/_debug',
|
|
139
|
+
}),
|
|
140
|
+
],
|
|
141
|
+
})
|
|
142
|
+
export class AppModule implements NestModule {
|
|
143
|
+
constructor(private readonly nodescope: NodeScope) {}
|
|
144
|
+
|
|
145
|
+
configure(consumer: MiddlewareConsumer) {
|
|
146
|
+
// Apply NodeScope middleware to all routes
|
|
147
|
+
const middleware = new NodeScopeMiddleware(this.nodescope);
|
|
148
|
+
consumer
|
|
149
|
+
.apply((req, res, next) => middleware.use(req, res, next))
|
|
150
|
+
.forRoutes('*');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function bootstrap() {
|
|
155
|
+
const app = await NestFactory.create(AppModule);
|
|
156
|
+
|
|
157
|
+
// Set up dashboard routes
|
|
158
|
+
const nodescope = app.get('NODESCOPE_INSTANCE');
|
|
159
|
+
await setupNodeScopeRoutes(app, nodescope);
|
|
160
|
+
|
|
161
|
+
await app.listen(3000);
|
|
162
|
+
}
|
|
163
|
+
bootstrap();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### NestJS with ConfigService (Async Configuration)
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { Module } from '@nestjs/common';
|
|
170
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
171
|
+
import { NodeScopeModule } from '@vipin733/nodescope';
|
|
172
|
+
|
|
173
|
+
@Module({
|
|
174
|
+
imports: [
|
|
175
|
+
ConfigModule.forRoot(),
|
|
176
|
+
NodeScopeModule.forRootAsync({
|
|
177
|
+
useFactory: (configService: ConfigService) => ({
|
|
178
|
+
storage: configService.get('NODESCOPE_STORAGE') || 'sqlite',
|
|
179
|
+
dashboardPath: '/_debug',
|
|
180
|
+
enabled: configService.get('NODE_ENV') === 'development',
|
|
181
|
+
}),
|
|
182
|
+
inject: [ConfigService],
|
|
183
|
+
}),
|
|
184
|
+
],
|
|
185
|
+
})
|
|
186
|
+
export class AppModule {}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### NestJS Global Interceptor (Alternative to Middleware)
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { Module } from '@nestjs/common';
|
|
193
|
+
import { APP_INTERCEPTOR } from '@nestjs/core';
|
|
194
|
+
import { NodeScopeModule, NodeScopeInterceptor } from '@vipin733/nodescope';
|
|
195
|
+
|
|
196
|
+
@Module({
|
|
197
|
+
imports: [
|
|
198
|
+
NodeScopeModule.forRoot({
|
|
199
|
+
storage: 'memory',
|
|
200
|
+
}),
|
|
201
|
+
],
|
|
202
|
+
providers: [
|
|
203
|
+
{
|
|
204
|
+
provide: APP_INTERCEPTOR,
|
|
205
|
+
useClass: NodeScopeInterceptor,
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
})
|
|
209
|
+
export class AppModule {}
|
|
210
|
+
```
|
|
211
|
+
|
|
122
212
|
## ⚙️ Configuration
|
|
123
213
|
|
|
124
214
|
### Constructor Options
|
|
@@ -367,6 +457,28 @@ import { createFastifyPlugin } from '@vipin733/nodescope';
|
|
|
367
457
|
await fastify.register(createFastifyPlugin, options);
|
|
368
458
|
```
|
|
369
459
|
|
|
460
|
+
#### NestJS
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { NodeScopeModule, NodeScopeMiddleware, NodeScopeInterceptor } from '@vipin733/nodescope';
|
|
464
|
+
|
|
465
|
+
// Module approach
|
|
466
|
+
@Module({
|
|
467
|
+
imports: [NodeScopeModule.forRoot(options)],
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
// Or async configuration
|
|
471
|
+
NodeScopeModule.forRootAsync({
|
|
472
|
+
useFactory: (config: ConfigService) => ({...}),
|
|
473
|
+
inject: [ConfigService],
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
// Use middleware or interceptor
|
|
477
|
+
consumer.apply(NodeScopeMiddleware).forRoutes('*');
|
|
478
|
+
// or
|
|
479
|
+
{ provide: APP_INTERCEPTOR, useClass: NodeScopeInterceptor }
|
|
480
|
+
```
|
|
481
|
+
|
|
370
482
|
## 🛠️ Development
|
|
371
483
|
|
|
372
484
|
```bash
|
|
@@ -394,6 +506,7 @@ Check out the [examples](https://github.com/yourusername/nodescope/tree/main/exa
|
|
|
394
506
|
- Express.js example
|
|
395
507
|
- Hono example
|
|
396
508
|
- Fastify example
|
|
509
|
+
- NestJS example
|
|
397
510
|
|
|
398
511
|
## 🤝 Contributing
|
|
399
512
|
|
package/dist/index.cjs
CHANGED
|
@@ -806,6 +806,10 @@ __export(index_exports, {
|
|
|
806
806
|
MemoryStorage: () => MemoryStorage,
|
|
807
807
|
MySQLStorage: () => MySQLStorage,
|
|
808
808
|
NodeScope: () => NodeScope,
|
|
809
|
+
NodeScopeController: () => NodeScopeController,
|
|
810
|
+
NodeScopeInterceptor: () => NodeScopeInterceptor,
|
|
811
|
+
NodeScopeMiddleware: () => NodeScopeMiddleware,
|
|
812
|
+
NodeScopeModule: () => NodeScopeModule,
|
|
809
813
|
PostgreSQLStorage: () => PostgreSQLStorage,
|
|
810
814
|
QueryWatcher: () => QueryWatcher,
|
|
811
815
|
RealTimeServer: () => RealTimeServer,
|
|
@@ -831,6 +835,7 @@ __export(index_exports, {
|
|
|
831
835
|
mountExpressRoutes: () => mountExpressRoutes,
|
|
832
836
|
nodescope: () => nodescope,
|
|
833
837
|
setupGlobalErrorHandlers: () => setupGlobalErrorHandlers,
|
|
838
|
+
setupNodeScopeRoutes: () => setupNodeScopeRoutes,
|
|
834
839
|
wrapFetch: () => wrapFetch,
|
|
835
840
|
wrapJobProcessor: () => wrapJobProcessor,
|
|
836
841
|
wrapPrisma: () => wrapPrisma
|
|
@@ -2882,6 +2887,317 @@ async function fastifyNodeScope(fastify, options) {
|
|
|
2882
2887
|
return reply.status(response.status).send(response.body);
|
|
2883
2888
|
});
|
|
2884
2889
|
}
|
|
2890
|
+
|
|
2891
|
+
// src/adapters/nestjs.ts
|
|
2892
|
+
var NodeScopeMiddleware = class {
|
|
2893
|
+
constructor(nodescope2) {
|
|
2894
|
+
this.nodescope = nodescope2;
|
|
2895
|
+
}
|
|
2896
|
+
async use(req, res, next) {
|
|
2897
|
+
const nodescope2 = this.nodescope;
|
|
2898
|
+
if (!nodescope2 || !nodescope2.isEnabled) {
|
|
2899
|
+
return next();
|
|
2900
|
+
}
|
|
2901
|
+
const dashboardPath = nodescope2.dashboardPath;
|
|
2902
|
+
if (req.path?.startsWith(dashboardPath)) {
|
|
2903
|
+
return next();
|
|
2904
|
+
}
|
|
2905
|
+
const ctx = nodescope2.createContext();
|
|
2906
|
+
req.nodescope = ctx;
|
|
2907
|
+
const originalSend = res.send;
|
|
2908
|
+
const originalJson = res.json;
|
|
2909
|
+
let responseBody;
|
|
2910
|
+
res.send = function(body) {
|
|
2911
|
+
responseBody = body;
|
|
2912
|
+
return originalSend.call(this, body);
|
|
2913
|
+
};
|
|
2914
|
+
res.json = function(body) {
|
|
2915
|
+
responseBody = body;
|
|
2916
|
+
return originalJson.call(this, body);
|
|
2917
|
+
};
|
|
2918
|
+
res.on("finish", async () => {
|
|
2919
|
+
if (!nodescope2.requestWatcher.enabled) return;
|
|
2920
|
+
try {
|
|
2921
|
+
const entry = nodescope2.requestWatcher.record({
|
|
2922
|
+
batchId: ctx.batchId,
|
|
2923
|
+
startTime: ctx.startTime,
|
|
2924
|
+
method: req.method,
|
|
2925
|
+
url: req.originalUrl || req.url,
|
|
2926
|
+
path: req.path || req.route?.path,
|
|
2927
|
+
query: req.query,
|
|
2928
|
+
headers: req.headers,
|
|
2929
|
+
body: req.body,
|
|
2930
|
+
ip: req.ip || req.socket?.remoteAddress,
|
|
2931
|
+
userAgent: req.get?.("user-agent"),
|
|
2932
|
+
session: req.session,
|
|
2933
|
+
response: {
|
|
2934
|
+
status: res.statusCode,
|
|
2935
|
+
headers: res.getHeaders?.() || {},
|
|
2936
|
+
body: responseBody
|
|
2937
|
+
}
|
|
2938
|
+
});
|
|
2939
|
+
if (entry) {
|
|
2940
|
+
await nodescope2.recordEntry(entry);
|
|
2941
|
+
}
|
|
2942
|
+
} catch (error) {
|
|
2943
|
+
}
|
|
2944
|
+
});
|
|
2945
|
+
next();
|
|
2946
|
+
}
|
|
2947
|
+
};
|
|
2948
|
+
var NodeScopeInterceptor = class {
|
|
2949
|
+
constructor(nodescope2) {
|
|
2950
|
+
this.nodescope = nodescope2;
|
|
2951
|
+
}
|
|
2952
|
+
async intercept(context, next) {
|
|
2953
|
+
if (!this.nodescope.isEnabled) {
|
|
2954
|
+
return next.handle();
|
|
2955
|
+
}
|
|
2956
|
+
const http = context.switchToHttp();
|
|
2957
|
+
const request = http.getRequest();
|
|
2958
|
+
const response = http.getResponse();
|
|
2959
|
+
const dashboardPath = this.nodescope.dashboardPath;
|
|
2960
|
+
if (request.path?.startsWith(dashboardPath)) {
|
|
2961
|
+
return next.handle();
|
|
2962
|
+
}
|
|
2963
|
+
const ctx = this.nodescope.createContext();
|
|
2964
|
+
request.nodescope = ctx;
|
|
2965
|
+
const { tap } = await import("rxjs/operators");
|
|
2966
|
+
return next.handle().pipe(
|
|
2967
|
+
tap({
|
|
2968
|
+
next: async (data) => {
|
|
2969
|
+
if (!this.nodescope.requestWatcher.enabled) return;
|
|
2970
|
+
try {
|
|
2971
|
+
const entry = this.nodescope.requestWatcher.record({
|
|
2972
|
+
batchId: ctx.batchId,
|
|
2973
|
+
startTime: ctx.startTime,
|
|
2974
|
+
method: request.method,
|
|
2975
|
+
url: request.originalUrl || request.url,
|
|
2976
|
+
path: request.path || request.route?.path,
|
|
2977
|
+
query: request.query,
|
|
2978
|
+
headers: request.headers,
|
|
2979
|
+
body: request.body,
|
|
2980
|
+
ip: request.ip || request.socket?.remoteAddress,
|
|
2981
|
+
userAgent: request.get?.("user-agent"),
|
|
2982
|
+
session: request.session,
|
|
2983
|
+
response: {
|
|
2984
|
+
status: response.statusCode,
|
|
2985
|
+
headers: response.getHeaders?.() || {},
|
|
2986
|
+
body: data
|
|
2987
|
+
}
|
|
2988
|
+
});
|
|
2989
|
+
if (entry) {
|
|
2990
|
+
await this.nodescope.recordEntry(entry);
|
|
2991
|
+
}
|
|
2992
|
+
} catch (error) {
|
|
2993
|
+
console.error("NodeScope error recording request:", error);
|
|
2994
|
+
}
|
|
2995
|
+
},
|
|
2996
|
+
error: async (error) => {
|
|
2997
|
+
if (!this.nodescope.exceptionWatcher.enabled) return;
|
|
2998
|
+
try {
|
|
2999
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
3000
|
+
const entry = this.nodescope.exceptionWatcher.record({
|
|
3001
|
+
error: errorObj,
|
|
3002
|
+
context: {
|
|
3003
|
+
method: request.method,
|
|
3004
|
+
url: request.url,
|
|
3005
|
+
statusCode: error.status || 500
|
|
3006
|
+
}
|
|
3007
|
+
});
|
|
3008
|
+
if (entry) {
|
|
3009
|
+
await this.nodescope.recordEntry(entry);
|
|
3010
|
+
}
|
|
3011
|
+
} catch (recordError) {
|
|
3012
|
+
console.error("NodeScope error recording exception:", recordError);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
})
|
|
3016
|
+
);
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
var NodeScopeController = class {
|
|
3020
|
+
constructor(nodescope2) {
|
|
3021
|
+
this.nodescope = nodescope2;
|
|
3022
|
+
}
|
|
3023
|
+
async getDashboard(req, res) {
|
|
3024
|
+
const authorized = await this.nodescope.checkAuthorization(req);
|
|
3025
|
+
if (!authorized) {
|
|
3026
|
+
res.status(403).json({ error: "Unauthorized" });
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
res.setHeader("Content-Type", "text/html");
|
|
3030
|
+
res.send(getDashboardHtml(this.nodescope.dashboardPath));
|
|
3031
|
+
}
|
|
3032
|
+
async handleApi(req, res) {
|
|
3033
|
+
const authorized = await this.nodescope.checkAuthorization(req);
|
|
3034
|
+
if (!authorized) {
|
|
3035
|
+
res.status(403).json({ error: "Unauthorized" });
|
|
3036
|
+
return;
|
|
3037
|
+
}
|
|
3038
|
+
const apiPath = req.path.replace(this.nodescope.dashboardPath, "");
|
|
3039
|
+
const response = await this.nodescope.api.handle({
|
|
3040
|
+
method: req.method,
|
|
3041
|
+
url: apiPath,
|
|
3042
|
+
query: req.query,
|
|
3043
|
+
body: req.body
|
|
3044
|
+
});
|
|
3045
|
+
if (response.headers) {
|
|
3046
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
3047
|
+
res.setHeader(key, value);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
res.status(response.status).json(response.body);
|
|
3051
|
+
}
|
|
3052
|
+
};
|
|
3053
|
+
var NodeScopeModule = class _NodeScopeModule {
|
|
3054
|
+
/**
|
|
3055
|
+
* Create a dynamic NestJS module for NodeScope
|
|
3056
|
+
*
|
|
3057
|
+
* @example
|
|
3058
|
+
* ```typescript
|
|
3059
|
+
* import { Module } from '@nestjs/common';
|
|
3060
|
+
* import { NodeScopeModule } from '@vipin733/nodescope';
|
|
3061
|
+
*
|
|
3062
|
+
* @Module({
|
|
3063
|
+
* imports: [
|
|
3064
|
+
* NodeScopeModule.forRoot({
|
|
3065
|
+
* storage: 'sqlite',
|
|
3066
|
+
* dashboardPath: '/_debug',
|
|
3067
|
+
* }),
|
|
3068
|
+
* ],
|
|
3069
|
+
* })
|
|
3070
|
+
* export class AppModule {}
|
|
3071
|
+
* ```
|
|
3072
|
+
*/
|
|
3073
|
+
static forRoot(config = {}) {
|
|
3074
|
+
const nodescope2 = new NodeScope(config);
|
|
3075
|
+
nodescope2.initialize().catch((err) => {
|
|
3076
|
+
console.error("Failed to initialize NodeScope:", err);
|
|
3077
|
+
});
|
|
3078
|
+
return {
|
|
3079
|
+
module: _NodeScopeModule,
|
|
3080
|
+
providers: [
|
|
3081
|
+
{
|
|
3082
|
+
provide: "NODESCOPE_INSTANCE",
|
|
3083
|
+
useValue: nodescope2
|
|
3084
|
+
},
|
|
3085
|
+
{
|
|
3086
|
+
provide: NodeScope,
|
|
3087
|
+
useValue: nodescope2
|
|
3088
|
+
},
|
|
3089
|
+
{
|
|
3090
|
+
provide: NodeScopeMiddleware,
|
|
3091
|
+
useFactory: () => new NodeScopeMiddleware(nodescope2)
|
|
3092
|
+
},
|
|
3093
|
+
{
|
|
3094
|
+
provide: NodeScopeInterceptor,
|
|
3095
|
+
useFactory: () => new NodeScopeInterceptor(nodescope2)
|
|
3096
|
+
},
|
|
3097
|
+
{
|
|
3098
|
+
provide: NodeScopeController,
|
|
3099
|
+
useFactory: () => new NodeScopeController(nodescope2)
|
|
3100
|
+
}
|
|
3101
|
+
],
|
|
3102
|
+
exports: ["NODESCOPE_INSTANCE", NodeScope, NodeScopeMiddleware, NodeScopeInterceptor],
|
|
3103
|
+
global: true
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
/**
|
|
3107
|
+
* Create an async dynamic module
|
|
3108
|
+
* Useful when you need to inject other services
|
|
3109
|
+
*
|
|
3110
|
+
* @example
|
|
3111
|
+
* ```typescript
|
|
3112
|
+
* NodeScopeModule.forRootAsync({
|
|
3113
|
+
* useFactory: (configService: ConfigService) => ({
|
|
3114
|
+
* storage: configService.get('NODESCOPE_STORAGE'),
|
|
3115
|
+
* dashboardPath: '/_debug',
|
|
3116
|
+
* enabled: configService.get('NODE_ENV') === 'development',
|
|
3117
|
+
* }),
|
|
3118
|
+
* inject: [ConfigService],
|
|
3119
|
+
* })
|
|
3120
|
+
* ```
|
|
3121
|
+
*/
|
|
3122
|
+
static forRootAsync(options) {
|
|
3123
|
+
return {
|
|
3124
|
+
module: _NodeScopeModule,
|
|
3125
|
+
providers: [
|
|
3126
|
+
{
|
|
3127
|
+
provide: "NODESCOPE_CONFIG",
|
|
3128
|
+
useFactory: options.useFactory,
|
|
3129
|
+
inject: options.inject || []
|
|
3130
|
+
},
|
|
3131
|
+
{
|
|
3132
|
+
provide: "NODESCOPE_INSTANCE",
|
|
3133
|
+
useFactory: async (config) => {
|
|
3134
|
+
const nodescope2 = new NodeScope(config);
|
|
3135
|
+
await nodescope2.initialize();
|
|
3136
|
+
return nodescope2;
|
|
3137
|
+
},
|
|
3138
|
+
inject: ["NODESCOPE_CONFIG"]
|
|
3139
|
+
},
|
|
3140
|
+
{
|
|
3141
|
+
provide: NodeScope,
|
|
3142
|
+
useFactory: async (config) => {
|
|
3143
|
+
const nodescope2 = new NodeScope(config);
|
|
3144
|
+
await nodescope2.initialize();
|
|
3145
|
+
return nodescope2;
|
|
3146
|
+
},
|
|
3147
|
+
inject: ["NODESCOPE_CONFIG"]
|
|
3148
|
+
},
|
|
3149
|
+
{
|
|
3150
|
+
provide: NodeScopeMiddleware,
|
|
3151
|
+
useFactory: (nodescope2) => new NodeScopeMiddleware(nodescope2),
|
|
3152
|
+
inject: ["NODESCOPE_INSTANCE"]
|
|
3153
|
+
},
|
|
3154
|
+
{
|
|
3155
|
+
provide: NodeScopeInterceptor,
|
|
3156
|
+
useFactory: (nodescope2) => new NodeScopeInterceptor(nodescope2),
|
|
3157
|
+
inject: ["NODESCOPE_INSTANCE"]
|
|
3158
|
+
},
|
|
3159
|
+
{
|
|
3160
|
+
provide: NodeScopeController,
|
|
3161
|
+
useFactory: (nodescope2) => new NodeScopeController(nodescope2),
|
|
3162
|
+
inject: ["NODESCOPE_INSTANCE"]
|
|
3163
|
+
}
|
|
3164
|
+
],
|
|
3165
|
+
exports: ["NODESCOPE_INSTANCE", NodeScope, NodeScopeMiddleware, NodeScopeInterceptor],
|
|
3166
|
+
global: true
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
};
|
|
3170
|
+
async function setupNodeScopeRoutes(app, nodescope2) {
|
|
3171
|
+
const dashboardPath = nodescope2.dashboardPath;
|
|
3172
|
+
const controller = new NodeScopeController(nodescope2);
|
|
3173
|
+
app.getHttpAdapter().get(dashboardPath, (req, res) => {
|
|
3174
|
+
return controller.getDashboard(req, res);
|
|
3175
|
+
});
|
|
3176
|
+
app.getHttpAdapter().all(`${dashboardPath}/api/*`, (req, res) => {
|
|
3177
|
+
return controller.handleApi(req, res);
|
|
3178
|
+
});
|
|
3179
|
+
const httpServer = app.getHttpServer();
|
|
3180
|
+
if (httpServer) {
|
|
3181
|
+
import("ws").then(({ WebSocketServer }) => {
|
|
3182
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
3183
|
+
const wsPath = `${dashboardPath}/ws`;
|
|
3184
|
+
httpServer.on("upgrade", (request, socket, head) => {
|
|
3185
|
+
const url = new URL(request.url || "", `http://${request.headers.host}`);
|
|
3186
|
+
if (url.pathname === wsPath) {
|
|
3187
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
3188
|
+
nodescope2.realtime.handleConnection(ws);
|
|
3189
|
+
ws.on("close", () => {
|
|
3190
|
+
nodescope2.realtime.handleDisconnection(ws);
|
|
3191
|
+
});
|
|
3192
|
+
});
|
|
3193
|
+
}
|
|
3194
|
+
});
|
|
3195
|
+
console.log("[NodeScope] WebSocket server attached for real-time updates at", wsPath);
|
|
3196
|
+
}).catch((err) => {
|
|
3197
|
+
console.warn("[NodeScope] WebSocket not available, real-time updates disabled:", err.message);
|
|
3198
|
+
});
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
2885
3201
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2886
3202
|
0 && (module.exports = {
|
|
2887
3203
|
ApiHandler,
|
|
@@ -2895,6 +3211,10 @@ async function fastifyNodeScope(fastify, options) {
|
|
|
2895
3211
|
MemoryStorage,
|
|
2896
3212
|
MySQLStorage,
|
|
2897
3213
|
NodeScope,
|
|
3214
|
+
NodeScopeController,
|
|
3215
|
+
NodeScopeInterceptor,
|
|
3216
|
+
NodeScopeMiddleware,
|
|
3217
|
+
NodeScopeModule,
|
|
2898
3218
|
PostgreSQLStorage,
|
|
2899
3219
|
QueryWatcher,
|
|
2900
3220
|
RealTimeServer,
|
|
@@ -2920,6 +3240,7 @@ async function fastifyNodeScope(fastify, options) {
|
|
|
2920
3240
|
mountExpressRoutes,
|
|
2921
3241
|
nodescope,
|
|
2922
3242
|
setupGlobalErrorHandlers,
|
|
3243
|
+
setupNodeScopeRoutes,
|
|
2923
3244
|
wrapFetch,
|
|
2924
3245
|
wrapJobProcessor,
|
|
2925
3246
|
wrapPrisma
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as http from 'http';
|
|
2
2
|
import { RequestHandler } from 'express';
|
|
3
3
|
import { Hono, MiddlewareHandler } from 'hono';
|
|
4
|
+
import { ExecutionContext, CallHandler, NestMiddleware, DynamicModule } from '@nestjs/common';
|
|
5
|
+
import { Observable } from 'rxjs';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Entry types that NodeScope can capture and display
|
|
@@ -919,10 +921,107 @@ declare function fastifyNodeScope(fastify: FastifyInstance, options: {
|
|
|
919
921
|
nodescope: NodeScope;
|
|
920
922
|
}): Promise<void>;
|
|
921
923
|
|
|
924
|
+
/**
|
|
925
|
+
* NodeScope Middleware for NestJS
|
|
926
|
+
* Tracks HTTP requests and responses
|
|
927
|
+
*
|
|
928
|
+
* This middleware must be provided by NodeScopeModule to ensure proper DI
|
|
929
|
+
*/
|
|
930
|
+
declare class NodeScopeMiddleware implements NestMiddleware {
|
|
931
|
+
private readonly nodescope;
|
|
932
|
+
constructor(nodescope: NodeScope);
|
|
933
|
+
use(req: any, res: any, next: () => void): Promise<void>;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* NodeScope Interceptor for NestJS
|
|
937
|
+
* Alternative to middleware, provides more NestJS-native integration
|
|
938
|
+
*/
|
|
939
|
+
declare class NodeScopeInterceptor {
|
|
940
|
+
private readonly nodescope;
|
|
941
|
+
constructor(nodescope: NodeScope);
|
|
942
|
+
intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>>;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* NodeScope Controller for NestJS
|
|
946
|
+
* Serves the dashboard and API endpoints
|
|
947
|
+
*/
|
|
948
|
+
declare class NodeScopeController {
|
|
949
|
+
private readonly nodescope;
|
|
950
|
+
constructor(nodescope: NodeScope);
|
|
951
|
+
getDashboard(req: any, res: any): Promise<any>;
|
|
952
|
+
handleApi(req: any, res: any): Promise<any>;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* NodeScope Module for NestJS
|
|
956
|
+
* Dynamic module that provides NodeScope functionality
|
|
957
|
+
*/
|
|
958
|
+
declare class NodeScopeModule {
|
|
959
|
+
/**
|
|
960
|
+
* Create a dynamic NestJS module for NodeScope
|
|
961
|
+
*
|
|
962
|
+
* @example
|
|
963
|
+
* ```typescript
|
|
964
|
+
* import { Module } from '@nestjs/common';
|
|
965
|
+
* import { NodeScopeModule } from '@vipin733/nodescope';
|
|
966
|
+
*
|
|
967
|
+
* @Module({
|
|
968
|
+
* imports: [
|
|
969
|
+
* NodeScopeModule.forRoot({
|
|
970
|
+
* storage: 'sqlite',
|
|
971
|
+
* dashboardPath: '/_debug',
|
|
972
|
+
* }),
|
|
973
|
+
* ],
|
|
974
|
+
* })
|
|
975
|
+
* export class AppModule {}
|
|
976
|
+
* ```
|
|
977
|
+
*/
|
|
978
|
+
static forRoot(config?: NodeScopeConfig): DynamicModule;
|
|
979
|
+
/**
|
|
980
|
+
* Create an async dynamic module
|
|
981
|
+
* Useful when you need to inject other services
|
|
982
|
+
*
|
|
983
|
+
* @example
|
|
984
|
+
* ```typescript
|
|
985
|
+
* NodeScopeModule.forRootAsync({
|
|
986
|
+
* useFactory: (configService: ConfigService) => ({
|
|
987
|
+
* storage: configService.get('NODESCOPE_STORAGE'),
|
|
988
|
+
* dashboardPath: '/_debug',
|
|
989
|
+
* enabled: configService.get('NODE_ENV') === 'development',
|
|
990
|
+
* }),
|
|
991
|
+
* inject: [ConfigService],
|
|
992
|
+
* })
|
|
993
|
+
* ```
|
|
994
|
+
*/
|
|
995
|
+
static forRootAsync(options: {
|
|
996
|
+
useFactory: (...args: any[]) => NodeScopeConfig | Promise<NodeScopeConfig>;
|
|
997
|
+
inject?: any[];
|
|
998
|
+
}): DynamicModule;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Helper function to configure NestJS routes for NodeScope dashboard
|
|
1002
|
+
* Use this in your main.ts to set up dashboard routes
|
|
1003
|
+
*
|
|
1004
|
+
* @example
|
|
1005
|
+
* ```typescript
|
|
1006
|
+
* import { setupNodeScopeRoutes } from '@vipin733/nodescope';
|
|
1007
|
+
*
|
|
1008
|
+
* async function bootstrap() {
|
|
1009
|
+
* const app = await NestFactory.create(AppModule);
|
|
1010
|
+
*
|
|
1011
|
+
* // Get NodeScope instance from app
|
|
1012
|
+
* const nodescope = app.get('NODESCOPE_INSTANCE');
|
|
1013
|
+
* await setupNodeScopeRoutes(app, nodescope);
|
|
1014
|
+
*
|
|
1015
|
+
* await app.listen(3000);
|
|
1016
|
+
* }
|
|
1017
|
+
* ```
|
|
1018
|
+
*/
|
|
1019
|
+
declare function setupNodeScopeRoutes(app: any, nodescope: NodeScope): Promise<void>;
|
|
1020
|
+
|
|
922
1021
|
/**
|
|
923
1022
|
* Get the embedded dashboard HTML
|
|
924
1023
|
* This is a simple HTML page that loads the dashboard React app
|
|
925
1024
|
*/
|
|
926
1025
|
declare function getDashboardHtml(basePath: string): string;
|
|
927
1026
|
|
|
928
|
-
export { ApiHandler, BaseWatcher, type CacheEntryContent, type CacheOperation, CacheWatcher, type CacheWatcherOptions, type DumpEntryContent, type Entry, type EntryType, type EventEntryContent, EventWatcher, type EventWatcherOptions, type ExceptionEntryContent, ExceptionWatcher, type ExceptionWatcherOptions, type HttpClientEntryContent, HttpClientWatcher, type HttpClientWatcherOptions, type HttpMethod, type JobEntryContent, type JobStatus, JobWatcher, type JobWatcherOptions, type ListOptions, type LogEntryContent, type LogLevel, LogWatcher, type LogWatcherOptions, MemoryStorage, MySQLStorage, NodeScope, type NodeScopeConfig, type PaginatedResult, PostgreSQLStorage, type QueryEntryContent, QueryWatcher, type QueryWatcherOptions, RealTimeServer, type RequestContext, type RequestEntryContent, RequestWatcher, type RequestWatcherOptions, SQLiteStorage, type ScheduleEntryContent, type ScheduleWatcherOptions, type StorageAdapter, type StorageDriver, type StorageStats, TrackedEventEmitter, type WatcherConfig, attachWebSocket, createCacheWrapper, createExpressMiddleware, createHonoDashboardRoutes, createHonoMiddleware, createQueryInterceptor, createRequestContext, createStorageAdapter, fastifyNodeScope, getDashboardHtml, getDuration, getMemoryDelta, getNodeScope, initNodeScope, interceptConsole, interceptFetch, mountExpressRoutes, nodescope, setupGlobalErrorHandlers, wrapFetch, wrapJobProcessor, wrapPrisma };
|
|
1027
|
+
export { ApiHandler, BaseWatcher, type CacheEntryContent, type CacheOperation, CacheWatcher, type CacheWatcherOptions, type DumpEntryContent, type Entry, type EntryType, type EventEntryContent, EventWatcher, type EventWatcherOptions, type ExceptionEntryContent, ExceptionWatcher, type ExceptionWatcherOptions, type HttpClientEntryContent, HttpClientWatcher, type HttpClientWatcherOptions, type HttpMethod, type JobEntryContent, type JobStatus, JobWatcher, type JobWatcherOptions, type ListOptions, type LogEntryContent, type LogLevel, LogWatcher, type LogWatcherOptions, MemoryStorage, MySQLStorage, NodeScope, type NodeScopeConfig, NodeScopeController, NodeScopeInterceptor, NodeScopeMiddleware, NodeScopeModule, type PaginatedResult, PostgreSQLStorage, type QueryEntryContent, QueryWatcher, type QueryWatcherOptions, RealTimeServer, type RequestContext, type RequestEntryContent, RequestWatcher, type RequestWatcherOptions, SQLiteStorage, type ScheduleEntryContent, type ScheduleWatcherOptions, type StorageAdapter, type StorageDriver, type StorageStats, TrackedEventEmitter, type WatcherConfig, attachWebSocket, createCacheWrapper, createExpressMiddleware, createHonoDashboardRoutes, createHonoMiddleware, createQueryInterceptor, createRequestContext, createStorageAdapter, fastifyNodeScope, getDashboardHtml, getDuration, getMemoryDelta, getNodeScope, initNodeScope, interceptConsole, interceptFetch, mountExpressRoutes, nodescope, setupGlobalErrorHandlers, setupNodeScopeRoutes, wrapFetch, wrapJobProcessor, wrapPrisma };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as http from 'http';
|
|
2
2
|
import { RequestHandler } from 'express';
|
|
3
3
|
import { Hono, MiddlewareHandler } from 'hono';
|
|
4
|
+
import { ExecutionContext, CallHandler, NestMiddleware, DynamicModule } from '@nestjs/common';
|
|
5
|
+
import { Observable } from 'rxjs';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Entry types that NodeScope can capture and display
|
|
@@ -919,10 +921,107 @@ declare function fastifyNodeScope(fastify: FastifyInstance, options: {
|
|
|
919
921
|
nodescope: NodeScope;
|
|
920
922
|
}): Promise<void>;
|
|
921
923
|
|
|
924
|
+
/**
|
|
925
|
+
* NodeScope Middleware for NestJS
|
|
926
|
+
* Tracks HTTP requests and responses
|
|
927
|
+
*
|
|
928
|
+
* This middleware must be provided by NodeScopeModule to ensure proper DI
|
|
929
|
+
*/
|
|
930
|
+
declare class NodeScopeMiddleware implements NestMiddleware {
|
|
931
|
+
private readonly nodescope;
|
|
932
|
+
constructor(nodescope: NodeScope);
|
|
933
|
+
use(req: any, res: any, next: () => void): Promise<void>;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* NodeScope Interceptor for NestJS
|
|
937
|
+
* Alternative to middleware, provides more NestJS-native integration
|
|
938
|
+
*/
|
|
939
|
+
declare class NodeScopeInterceptor {
|
|
940
|
+
private readonly nodescope;
|
|
941
|
+
constructor(nodescope: NodeScope);
|
|
942
|
+
intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>>;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* NodeScope Controller for NestJS
|
|
946
|
+
* Serves the dashboard and API endpoints
|
|
947
|
+
*/
|
|
948
|
+
declare class NodeScopeController {
|
|
949
|
+
private readonly nodescope;
|
|
950
|
+
constructor(nodescope: NodeScope);
|
|
951
|
+
getDashboard(req: any, res: any): Promise<any>;
|
|
952
|
+
handleApi(req: any, res: any): Promise<any>;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* NodeScope Module for NestJS
|
|
956
|
+
* Dynamic module that provides NodeScope functionality
|
|
957
|
+
*/
|
|
958
|
+
declare class NodeScopeModule {
|
|
959
|
+
/**
|
|
960
|
+
* Create a dynamic NestJS module for NodeScope
|
|
961
|
+
*
|
|
962
|
+
* @example
|
|
963
|
+
* ```typescript
|
|
964
|
+
* import { Module } from '@nestjs/common';
|
|
965
|
+
* import { NodeScopeModule } from '@vipin733/nodescope';
|
|
966
|
+
*
|
|
967
|
+
* @Module({
|
|
968
|
+
* imports: [
|
|
969
|
+
* NodeScopeModule.forRoot({
|
|
970
|
+
* storage: 'sqlite',
|
|
971
|
+
* dashboardPath: '/_debug',
|
|
972
|
+
* }),
|
|
973
|
+
* ],
|
|
974
|
+
* })
|
|
975
|
+
* export class AppModule {}
|
|
976
|
+
* ```
|
|
977
|
+
*/
|
|
978
|
+
static forRoot(config?: NodeScopeConfig): DynamicModule;
|
|
979
|
+
/**
|
|
980
|
+
* Create an async dynamic module
|
|
981
|
+
* Useful when you need to inject other services
|
|
982
|
+
*
|
|
983
|
+
* @example
|
|
984
|
+
* ```typescript
|
|
985
|
+
* NodeScopeModule.forRootAsync({
|
|
986
|
+
* useFactory: (configService: ConfigService) => ({
|
|
987
|
+
* storage: configService.get('NODESCOPE_STORAGE'),
|
|
988
|
+
* dashboardPath: '/_debug',
|
|
989
|
+
* enabled: configService.get('NODE_ENV') === 'development',
|
|
990
|
+
* }),
|
|
991
|
+
* inject: [ConfigService],
|
|
992
|
+
* })
|
|
993
|
+
* ```
|
|
994
|
+
*/
|
|
995
|
+
static forRootAsync(options: {
|
|
996
|
+
useFactory: (...args: any[]) => NodeScopeConfig | Promise<NodeScopeConfig>;
|
|
997
|
+
inject?: any[];
|
|
998
|
+
}): DynamicModule;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Helper function to configure NestJS routes for NodeScope dashboard
|
|
1002
|
+
* Use this in your main.ts to set up dashboard routes
|
|
1003
|
+
*
|
|
1004
|
+
* @example
|
|
1005
|
+
* ```typescript
|
|
1006
|
+
* import { setupNodeScopeRoutes } from '@vipin733/nodescope';
|
|
1007
|
+
*
|
|
1008
|
+
* async function bootstrap() {
|
|
1009
|
+
* const app = await NestFactory.create(AppModule);
|
|
1010
|
+
*
|
|
1011
|
+
* // Get NodeScope instance from app
|
|
1012
|
+
* const nodescope = app.get('NODESCOPE_INSTANCE');
|
|
1013
|
+
* await setupNodeScopeRoutes(app, nodescope);
|
|
1014
|
+
*
|
|
1015
|
+
* await app.listen(3000);
|
|
1016
|
+
* }
|
|
1017
|
+
* ```
|
|
1018
|
+
*/
|
|
1019
|
+
declare function setupNodeScopeRoutes(app: any, nodescope: NodeScope): Promise<void>;
|
|
1020
|
+
|
|
922
1021
|
/**
|
|
923
1022
|
* Get the embedded dashboard HTML
|
|
924
1023
|
* This is a simple HTML page that loads the dashboard React app
|
|
925
1024
|
*/
|
|
926
1025
|
declare function getDashboardHtml(basePath: string): string;
|
|
927
1026
|
|
|
928
|
-
export { ApiHandler, BaseWatcher, type CacheEntryContent, type CacheOperation, CacheWatcher, type CacheWatcherOptions, type DumpEntryContent, type Entry, type EntryType, type EventEntryContent, EventWatcher, type EventWatcherOptions, type ExceptionEntryContent, ExceptionWatcher, type ExceptionWatcherOptions, type HttpClientEntryContent, HttpClientWatcher, type HttpClientWatcherOptions, type HttpMethod, type JobEntryContent, type JobStatus, JobWatcher, type JobWatcherOptions, type ListOptions, type LogEntryContent, type LogLevel, LogWatcher, type LogWatcherOptions, MemoryStorage, MySQLStorage, NodeScope, type NodeScopeConfig, type PaginatedResult, PostgreSQLStorage, type QueryEntryContent, QueryWatcher, type QueryWatcherOptions, RealTimeServer, type RequestContext, type RequestEntryContent, RequestWatcher, type RequestWatcherOptions, SQLiteStorage, type ScheduleEntryContent, type ScheduleWatcherOptions, type StorageAdapter, type StorageDriver, type StorageStats, TrackedEventEmitter, type WatcherConfig, attachWebSocket, createCacheWrapper, createExpressMiddleware, createHonoDashboardRoutes, createHonoMiddleware, createQueryInterceptor, createRequestContext, createStorageAdapter, fastifyNodeScope, getDashboardHtml, getDuration, getMemoryDelta, getNodeScope, initNodeScope, interceptConsole, interceptFetch, mountExpressRoutes, nodescope, setupGlobalErrorHandlers, wrapFetch, wrapJobProcessor, wrapPrisma };
|
|
1027
|
+
export { ApiHandler, BaseWatcher, type CacheEntryContent, type CacheOperation, CacheWatcher, type CacheWatcherOptions, type DumpEntryContent, type Entry, type EntryType, type EventEntryContent, EventWatcher, type EventWatcherOptions, type ExceptionEntryContent, ExceptionWatcher, type ExceptionWatcherOptions, type HttpClientEntryContent, HttpClientWatcher, type HttpClientWatcherOptions, type HttpMethod, type JobEntryContent, type JobStatus, JobWatcher, type JobWatcherOptions, type ListOptions, type LogEntryContent, type LogLevel, LogWatcher, type LogWatcherOptions, MemoryStorage, MySQLStorage, NodeScope, type NodeScopeConfig, NodeScopeController, NodeScopeInterceptor, NodeScopeMiddleware, NodeScopeModule, type PaginatedResult, PostgreSQLStorage, type QueryEntryContent, QueryWatcher, type QueryWatcherOptions, RealTimeServer, type RequestContext, type RequestEntryContent, RequestWatcher, type RequestWatcherOptions, SQLiteStorage, type ScheduleEntryContent, type ScheduleWatcherOptions, type StorageAdapter, type StorageDriver, type StorageStats, TrackedEventEmitter, type WatcherConfig, attachWebSocket, createCacheWrapper, createExpressMiddleware, createHonoDashboardRoutes, createHonoMiddleware, createQueryInterceptor, createRequestContext, createStorageAdapter, fastifyNodeScope, getDashboardHtml, getDuration, getMemoryDelta, getNodeScope, initNodeScope, interceptConsole, interceptFetch, mountExpressRoutes, nodescope, setupGlobalErrorHandlers, setupNodeScopeRoutes, wrapFetch, wrapJobProcessor, wrapPrisma };
|
package/dist/index.js
CHANGED
|
@@ -2049,6 +2049,317 @@ async function fastifyNodeScope(fastify, options) {
|
|
|
2049
2049
|
return reply.status(response.status).send(response.body);
|
|
2050
2050
|
});
|
|
2051
2051
|
}
|
|
2052
|
+
|
|
2053
|
+
// src/adapters/nestjs.ts
|
|
2054
|
+
var NodeScopeMiddleware = class {
|
|
2055
|
+
constructor(nodescope2) {
|
|
2056
|
+
this.nodescope = nodescope2;
|
|
2057
|
+
}
|
|
2058
|
+
async use(req, res, next) {
|
|
2059
|
+
const nodescope2 = this.nodescope;
|
|
2060
|
+
if (!nodescope2 || !nodescope2.isEnabled) {
|
|
2061
|
+
return next();
|
|
2062
|
+
}
|
|
2063
|
+
const dashboardPath = nodescope2.dashboardPath;
|
|
2064
|
+
if (req.path?.startsWith(dashboardPath)) {
|
|
2065
|
+
return next();
|
|
2066
|
+
}
|
|
2067
|
+
const ctx = nodescope2.createContext();
|
|
2068
|
+
req.nodescope = ctx;
|
|
2069
|
+
const originalSend = res.send;
|
|
2070
|
+
const originalJson = res.json;
|
|
2071
|
+
let responseBody;
|
|
2072
|
+
res.send = function(body) {
|
|
2073
|
+
responseBody = body;
|
|
2074
|
+
return originalSend.call(this, body);
|
|
2075
|
+
};
|
|
2076
|
+
res.json = function(body) {
|
|
2077
|
+
responseBody = body;
|
|
2078
|
+
return originalJson.call(this, body);
|
|
2079
|
+
};
|
|
2080
|
+
res.on("finish", async () => {
|
|
2081
|
+
if (!nodescope2.requestWatcher.enabled) return;
|
|
2082
|
+
try {
|
|
2083
|
+
const entry = nodescope2.requestWatcher.record({
|
|
2084
|
+
batchId: ctx.batchId,
|
|
2085
|
+
startTime: ctx.startTime,
|
|
2086
|
+
method: req.method,
|
|
2087
|
+
url: req.originalUrl || req.url,
|
|
2088
|
+
path: req.path || req.route?.path,
|
|
2089
|
+
query: req.query,
|
|
2090
|
+
headers: req.headers,
|
|
2091
|
+
body: req.body,
|
|
2092
|
+
ip: req.ip || req.socket?.remoteAddress,
|
|
2093
|
+
userAgent: req.get?.("user-agent"),
|
|
2094
|
+
session: req.session,
|
|
2095
|
+
response: {
|
|
2096
|
+
status: res.statusCode,
|
|
2097
|
+
headers: res.getHeaders?.() || {},
|
|
2098
|
+
body: responseBody
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
if (entry) {
|
|
2102
|
+
await nodescope2.recordEntry(entry);
|
|
2103
|
+
}
|
|
2104
|
+
} catch (error) {
|
|
2105
|
+
}
|
|
2106
|
+
});
|
|
2107
|
+
next();
|
|
2108
|
+
}
|
|
2109
|
+
};
|
|
2110
|
+
var NodeScopeInterceptor = class {
|
|
2111
|
+
constructor(nodescope2) {
|
|
2112
|
+
this.nodescope = nodescope2;
|
|
2113
|
+
}
|
|
2114
|
+
async intercept(context, next) {
|
|
2115
|
+
if (!this.nodescope.isEnabled) {
|
|
2116
|
+
return next.handle();
|
|
2117
|
+
}
|
|
2118
|
+
const http = context.switchToHttp();
|
|
2119
|
+
const request = http.getRequest();
|
|
2120
|
+
const response = http.getResponse();
|
|
2121
|
+
const dashboardPath = this.nodescope.dashboardPath;
|
|
2122
|
+
if (request.path?.startsWith(dashboardPath)) {
|
|
2123
|
+
return next.handle();
|
|
2124
|
+
}
|
|
2125
|
+
const ctx = this.nodescope.createContext();
|
|
2126
|
+
request.nodescope = ctx;
|
|
2127
|
+
const { tap } = await import("rxjs/operators");
|
|
2128
|
+
return next.handle().pipe(
|
|
2129
|
+
tap({
|
|
2130
|
+
next: async (data) => {
|
|
2131
|
+
if (!this.nodescope.requestWatcher.enabled) return;
|
|
2132
|
+
try {
|
|
2133
|
+
const entry = this.nodescope.requestWatcher.record({
|
|
2134
|
+
batchId: ctx.batchId,
|
|
2135
|
+
startTime: ctx.startTime,
|
|
2136
|
+
method: request.method,
|
|
2137
|
+
url: request.originalUrl || request.url,
|
|
2138
|
+
path: request.path || request.route?.path,
|
|
2139
|
+
query: request.query,
|
|
2140
|
+
headers: request.headers,
|
|
2141
|
+
body: request.body,
|
|
2142
|
+
ip: request.ip || request.socket?.remoteAddress,
|
|
2143
|
+
userAgent: request.get?.("user-agent"),
|
|
2144
|
+
session: request.session,
|
|
2145
|
+
response: {
|
|
2146
|
+
status: response.statusCode,
|
|
2147
|
+
headers: response.getHeaders?.() || {},
|
|
2148
|
+
body: data
|
|
2149
|
+
}
|
|
2150
|
+
});
|
|
2151
|
+
if (entry) {
|
|
2152
|
+
await this.nodescope.recordEntry(entry);
|
|
2153
|
+
}
|
|
2154
|
+
} catch (error) {
|
|
2155
|
+
console.error("NodeScope error recording request:", error);
|
|
2156
|
+
}
|
|
2157
|
+
},
|
|
2158
|
+
error: async (error) => {
|
|
2159
|
+
if (!this.nodescope.exceptionWatcher.enabled) return;
|
|
2160
|
+
try {
|
|
2161
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
2162
|
+
const entry = this.nodescope.exceptionWatcher.record({
|
|
2163
|
+
error: errorObj,
|
|
2164
|
+
context: {
|
|
2165
|
+
method: request.method,
|
|
2166
|
+
url: request.url,
|
|
2167
|
+
statusCode: error.status || 500
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
if (entry) {
|
|
2171
|
+
await this.nodescope.recordEntry(entry);
|
|
2172
|
+
}
|
|
2173
|
+
} catch (recordError) {
|
|
2174
|
+
console.error("NodeScope error recording exception:", recordError);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
})
|
|
2178
|
+
);
|
|
2179
|
+
}
|
|
2180
|
+
};
|
|
2181
|
+
var NodeScopeController = class {
|
|
2182
|
+
constructor(nodescope2) {
|
|
2183
|
+
this.nodescope = nodescope2;
|
|
2184
|
+
}
|
|
2185
|
+
async getDashboard(req, res) {
|
|
2186
|
+
const authorized = await this.nodescope.checkAuthorization(req);
|
|
2187
|
+
if (!authorized) {
|
|
2188
|
+
res.status(403).json({ error: "Unauthorized" });
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
res.setHeader("Content-Type", "text/html");
|
|
2192
|
+
res.send(getDashboardHtml(this.nodescope.dashboardPath));
|
|
2193
|
+
}
|
|
2194
|
+
async handleApi(req, res) {
|
|
2195
|
+
const authorized = await this.nodescope.checkAuthorization(req);
|
|
2196
|
+
if (!authorized) {
|
|
2197
|
+
res.status(403).json({ error: "Unauthorized" });
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
const apiPath = req.path.replace(this.nodescope.dashboardPath, "");
|
|
2201
|
+
const response = await this.nodescope.api.handle({
|
|
2202
|
+
method: req.method,
|
|
2203
|
+
url: apiPath,
|
|
2204
|
+
query: req.query,
|
|
2205
|
+
body: req.body
|
|
2206
|
+
});
|
|
2207
|
+
if (response.headers) {
|
|
2208
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
2209
|
+
res.setHeader(key, value);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
res.status(response.status).json(response.body);
|
|
2213
|
+
}
|
|
2214
|
+
};
|
|
2215
|
+
var NodeScopeModule = class _NodeScopeModule {
|
|
2216
|
+
/**
|
|
2217
|
+
* Create a dynamic NestJS module for NodeScope
|
|
2218
|
+
*
|
|
2219
|
+
* @example
|
|
2220
|
+
* ```typescript
|
|
2221
|
+
* import { Module } from '@nestjs/common';
|
|
2222
|
+
* import { NodeScopeModule } from '@vipin733/nodescope';
|
|
2223
|
+
*
|
|
2224
|
+
* @Module({
|
|
2225
|
+
* imports: [
|
|
2226
|
+
* NodeScopeModule.forRoot({
|
|
2227
|
+
* storage: 'sqlite',
|
|
2228
|
+
* dashboardPath: '/_debug',
|
|
2229
|
+
* }),
|
|
2230
|
+
* ],
|
|
2231
|
+
* })
|
|
2232
|
+
* export class AppModule {}
|
|
2233
|
+
* ```
|
|
2234
|
+
*/
|
|
2235
|
+
static forRoot(config = {}) {
|
|
2236
|
+
const nodescope2 = new NodeScope(config);
|
|
2237
|
+
nodescope2.initialize().catch((err) => {
|
|
2238
|
+
console.error("Failed to initialize NodeScope:", err);
|
|
2239
|
+
});
|
|
2240
|
+
return {
|
|
2241
|
+
module: _NodeScopeModule,
|
|
2242
|
+
providers: [
|
|
2243
|
+
{
|
|
2244
|
+
provide: "NODESCOPE_INSTANCE",
|
|
2245
|
+
useValue: nodescope2
|
|
2246
|
+
},
|
|
2247
|
+
{
|
|
2248
|
+
provide: NodeScope,
|
|
2249
|
+
useValue: nodescope2
|
|
2250
|
+
},
|
|
2251
|
+
{
|
|
2252
|
+
provide: NodeScopeMiddleware,
|
|
2253
|
+
useFactory: () => new NodeScopeMiddleware(nodescope2)
|
|
2254
|
+
},
|
|
2255
|
+
{
|
|
2256
|
+
provide: NodeScopeInterceptor,
|
|
2257
|
+
useFactory: () => new NodeScopeInterceptor(nodescope2)
|
|
2258
|
+
},
|
|
2259
|
+
{
|
|
2260
|
+
provide: NodeScopeController,
|
|
2261
|
+
useFactory: () => new NodeScopeController(nodescope2)
|
|
2262
|
+
}
|
|
2263
|
+
],
|
|
2264
|
+
exports: ["NODESCOPE_INSTANCE", NodeScope, NodeScopeMiddleware, NodeScopeInterceptor],
|
|
2265
|
+
global: true
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Create an async dynamic module
|
|
2270
|
+
* Useful when you need to inject other services
|
|
2271
|
+
*
|
|
2272
|
+
* @example
|
|
2273
|
+
* ```typescript
|
|
2274
|
+
* NodeScopeModule.forRootAsync({
|
|
2275
|
+
* useFactory: (configService: ConfigService) => ({
|
|
2276
|
+
* storage: configService.get('NODESCOPE_STORAGE'),
|
|
2277
|
+
* dashboardPath: '/_debug',
|
|
2278
|
+
* enabled: configService.get('NODE_ENV') === 'development',
|
|
2279
|
+
* }),
|
|
2280
|
+
* inject: [ConfigService],
|
|
2281
|
+
* })
|
|
2282
|
+
* ```
|
|
2283
|
+
*/
|
|
2284
|
+
static forRootAsync(options) {
|
|
2285
|
+
return {
|
|
2286
|
+
module: _NodeScopeModule,
|
|
2287
|
+
providers: [
|
|
2288
|
+
{
|
|
2289
|
+
provide: "NODESCOPE_CONFIG",
|
|
2290
|
+
useFactory: options.useFactory,
|
|
2291
|
+
inject: options.inject || []
|
|
2292
|
+
},
|
|
2293
|
+
{
|
|
2294
|
+
provide: "NODESCOPE_INSTANCE",
|
|
2295
|
+
useFactory: async (config) => {
|
|
2296
|
+
const nodescope2 = new NodeScope(config);
|
|
2297
|
+
await nodescope2.initialize();
|
|
2298
|
+
return nodescope2;
|
|
2299
|
+
},
|
|
2300
|
+
inject: ["NODESCOPE_CONFIG"]
|
|
2301
|
+
},
|
|
2302
|
+
{
|
|
2303
|
+
provide: NodeScope,
|
|
2304
|
+
useFactory: async (config) => {
|
|
2305
|
+
const nodescope2 = new NodeScope(config);
|
|
2306
|
+
await nodescope2.initialize();
|
|
2307
|
+
return nodescope2;
|
|
2308
|
+
},
|
|
2309
|
+
inject: ["NODESCOPE_CONFIG"]
|
|
2310
|
+
},
|
|
2311
|
+
{
|
|
2312
|
+
provide: NodeScopeMiddleware,
|
|
2313
|
+
useFactory: (nodescope2) => new NodeScopeMiddleware(nodescope2),
|
|
2314
|
+
inject: ["NODESCOPE_INSTANCE"]
|
|
2315
|
+
},
|
|
2316
|
+
{
|
|
2317
|
+
provide: NodeScopeInterceptor,
|
|
2318
|
+
useFactory: (nodescope2) => new NodeScopeInterceptor(nodescope2),
|
|
2319
|
+
inject: ["NODESCOPE_INSTANCE"]
|
|
2320
|
+
},
|
|
2321
|
+
{
|
|
2322
|
+
provide: NodeScopeController,
|
|
2323
|
+
useFactory: (nodescope2) => new NodeScopeController(nodescope2),
|
|
2324
|
+
inject: ["NODESCOPE_INSTANCE"]
|
|
2325
|
+
}
|
|
2326
|
+
],
|
|
2327
|
+
exports: ["NODESCOPE_INSTANCE", NodeScope, NodeScopeMiddleware, NodeScopeInterceptor],
|
|
2328
|
+
global: true
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
};
|
|
2332
|
+
async function setupNodeScopeRoutes(app, nodescope2) {
|
|
2333
|
+
const dashboardPath = nodescope2.dashboardPath;
|
|
2334
|
+
const controller = new NodeScopeController(nodescope2);
|
|
2335
|
+
app.getHttpAdapter().get(dashboardPath, (req, res) => {
|
|
2336
|
+
return controller.getDashboard(req, res);
|
|
2337
|
+
});
|
|
2338
|
+
app.getHttpAdapter().all(`${dashboardPath}/api/*`, (req, res) => {
|
|
2339
|
+
return controller.handleApi(req, res);
|
|
2340
|
+
});
|
|
2341
|
+
const httpServer = app.getHttpServer();
|
|
2342
|
+
if (httpServer) {
|
|
2343
|
+
import("ws").then(({ WebSocketServer }) => {
|
|
2344
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
2345
|
+
const wsPath = `${dashboardPath}/ws`;
|
|
2346
|
+
httpServer.on("upgrade", (request, socket, head) => {
|
|
2347
|
+
const url = new URL(request.url || "", `http://${request.headers.host}`);
|
|
2348
|
+
if (url.pathname === wsPath) {
|
|
2349
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
2350
|
+
nodescope2.realtime.handleConnection(ws);
|
|
2351
|
+
ws.on("close", () => {
|
|
2352
|
+
nodescope2.realtime.handleDisconnection(ws);
|
|
2353
|
+
});
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
});
|
|
2357
|
+
console.log("[NodeScope] WebSocket server attached for real-time updates at", wsPath);
|
|
2358
|
+
}).catch((err) => {
|
|
2359
|
+
console.warn("[NodeScope] WebSocket not available, real-time updates disabled:", err.message);
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2052
2363
|
export {
|
|
2053
2364
|
ApiHandler,
|
|
2054
2365
|
BaseWatcher,
|
|
@@ -2061,6 +2372,10 @@ export {
|
|
|
2061
2372
|
MemoryStorage,
|
|
2062
2373
|
MySQLStorage,
|
|
2063
2374
|
NodeScope,
|
|
2375
|
+
NodeScopeController,
|
|
2376
|
+
NodeScopeInterceptor,
|
|
2377
|
+
NodeScopeMiddleware,
|
|
2378
|
+
NodeScopeModule,
|
|
2064
2379
|
PostgreSQLStorage,
|
|
2065
2380
|
QueryWatcher,
|
|
2066
2381
|
RealTimeServer,
|
|
@@ -2086,6 +2401,7 @@ export {
|
|
|
2086
2401
|
mountExpressRoutes,
|
|
2087
2402
|
nodescope,
|
|
2088
2403
|
setupGlobalErrorHandlers,
|
|
2404
|
+
setupNodeScopeRoutes,
|
|
2089
2405
|
wrapFetch,
|
|
2090
2406
|
wrapJobProcessor,
|
|
2091
2407
|
wrapPrisma
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vipin733/nodescope",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A Laravel Telescope-inspired debugging and monitoring package for Node.js and Bun applications",
|
|
6
6
|
"author": "Vipin",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"LICENSE"
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "tsup src/index.ts --format cjs,esm --dts --clean --external better-sqlite3 --external pg --external mysql2 --external express --external hono --external fastify",
|
|
25
|
-
"dev": "tsup src/index.ts --format cjs,esm --dts --watch --external better-sqlite3 --external pg --external mysql2 --external express --external hono --external fastify",
|
|
24
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean --external better-sqlite3 --external pg --external mysql2 --external express --external hono --external fastify --external @nestjs/common --external rxjs",
|
|
25
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch --external better-sqlite3 --external pg --external mysql2 --external express --external hono --external fastify --external @nestjs/common --external rxjs",
|
|
26
26
|
"prepublishOnly": "npm run build",
|
|
27
27
|
"test": "vitest run",
|
|
28
28
|
"test:watch": "vitest",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"express",
|
|
51
51
|
"hono",
|
|
52
52
|
"fastify",
|
|
53
|
+
"nestjs",
|
|
53
54
|
"observability",
|
|
54
55
|
"developer-tools",
|
|
55
56
|
"debug",
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
"pg": "^8.11.0"
|
|
72
73
|
},
|
|
73
74
|
"devDependencies": {
|
|
75
|
+
"@nestjs/common": "^10.0.0",
|
|
74
76
|
"@types/better-sqlite3": "^7.6.8",
|
|
75
77
|
"@types/express": "^4.17.21",
|
|
76
78
|
"@types/node": "^20.11.0",
|
|
@@ -81,24 +83,33 @@
|
|
|
81
83
|
"express": "^4.18.2",
|
|
82
84
|
"fastify": "^4.26.0",
|
|
83
85
|
"hono": "^4.0.0",
|
|
86
|
+
"rxjs": "^7.8.0",
|
|
84
87
|
"supertest": "^7.2.2",
|
|
85
88
|
"tsup": "^8.0.0",
|
|
86
89
|
"typescript": "^5.3.0",
|
|
87
90
|
"vitest": "^1.2.0"
|
|
88
91
|
},
|
|
89
92
|
"peerDependencies": {
|
|
93
|
+
"@nestjs/common": ">=9.0.0",
|
|
90
94
|
"express": ">=4.0.0",
|
|
91
95
|
"fastify": ">=4.0.0",
|
|
92
|
-
"hono": ">=4.0.0"
|
|
96
|
+
"hono": ">=4.0.0",
|
|
97
|
+
"rxjs": ">=7.0.0"
|
|
93
98
|
},
|
|
94
99
|
"peerDependenciesMeta": {
|
|
100
|
+
"@nestjs/common": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
95
103
|
"express": {
|
|
96
104
|
"optional": true
|
|
97
105
|
},
|
|
106
|
+
"fastify": {
|
|
107
|
+
"optional": true
|
|
108
|
+
},
|
|
98
109
|
"hono": {
|
|
99
110
|
"optional": true
|
|
100
111
|
},
|
|
101
|
-
"
|
|
112
|
+
"rxjs": {
|
|
102
113
|
"optional": true
|
|
103
114
|
}
|
|
104
115
|
},
|