@lensjs/express 1.1.0 → 1.2.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/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # @lensjs/express
2
+
3
+ Express.js adapter for Lens. This package provides middleware and integration points to connect your Express application with the Lens monitoring and debugging tool. It enables automatic logging of requests, queries (via `@lensjs/watchers`), and cache events.
4
+
5
+ ## Features
6
+
7
+ * **`lens(config: ExpressAdapterConfig)` function**: The main entry point to initialize and integrate Lens with an Express application.
8
+ * **`ExpressAdapter` class**: Extends `LensAdapter` from `@lensjs/core` to provide Express-specific implementations for setting up watchers, registering routes, and serving the Lens UI.
9
+ * **Request Watching**: Automatically captures incoming request details (method, path, headers, body, status, duration, IP) and logs them.
10
+ * **Query Watching**: Integrates with `@lensjs/watchers` to capture database queries from various ORMs (Kysely, Prisma, Sequelize) if configured.
11
+ * **Cache Watching**: Integrates with `@lensjs/watchers` to capture cache events if configured.
12
+ * **Exception Watching**: Captures and logs unhandled exceptions and errors within your Express application.
13
+ * **UI Serving**: Serves the Lens UI within your Express application at a configurable path.
14
+ * **Configurable Paths**: Allows specifying base paths, ignored paths, and only paths for request watching.
15
+ * **Body Purging**: Prevents sensitive information from being logged in responses by purging certain body types (e.g., file paths, binary data).
16
+ * **Authentication/User Context**: Supports optional `isAuthenticated` and `getUser` functions to associate request logs with authenticated users.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pnpm add @lensjs/express
22
+ ```
23
+
24
+ ## Usage Example
25
+
26
+ ```typescript
27
+ import express from 'express';
28
+ import { lens } from '@lensjs/express';
29
+ import { createKyselyHandler } from '@lensjs/watchers/query/kysely'; // Example for Kysely
30
+
31
+ const app = express();
32
+ app.use(express.json()); // Enable JSON body parsing
33
+
34
+ // Example Kysely setup (replace with your actual Kysely instance)
35
+ const db = {
36
+ // ... your Kysely instance methods
37
+ // Mocking a Kysely-like interface for demonstration
38
+ on: (event: string, callback: (payload: any) => void) => {
39
+ if (event === 'query') {
40
+ // Simulate a query event
41
+ setTimeout(() => {
42
+ callback({
43
+ level: 'query',
44
+ query: { sql: 'SELECT * FROM users', parameters: [] },
45
+ queryDurationMillis: 15.2,
46
+ error: undefined,
47
+ });
48
+ }, 100);
49
+ }
50
+ },
51
+ };
52
+
53
+ // Initialize Lens with Express
54
+ lens({
55
+ app,
56
+ appName: 'My Express App',
57
+ enabled: true, // Set to false in production
58
+ path: '/lens-dashboard', // Access Lens UI at /lens-dashboard
59
+ requestWatcherEnabled: true,
60
+ cacheWatcherEnabled: true,
61
+ exceptionWatcherEnabled: true,
62
+ queryWatcher: {
63
+ enabled: true,
64
+ handler: createKyselyHandler({ provider: 'sqlite' }), // Or createPrismaHandler, createSequelizeHandler
65
+ },
66
+ // Optional: Integrate with your authentication system
67
+ isAuthenticated: async (req) => {
68
+ return !!req.headers.authorization;
69
+ },
70
+ getUser: async (req) => {
71
+ // Return user details based on your auth system
72
+ return { id: '1', name: 'Authenticated User' };
73
+ },
74
+ });
75
+
76
+ // Your Express routes
77
+ app.get('/', (req, res) => {
78
+ res.send('Hello World!');
79
+ });
80
+
81
+ app.get('/users', async (req, res) => {
82
+ // Simulate a database query
83
+ db.on('query', ({ query, queryDurationMillis }) => {
84
+ console.log(`Simulated query: ${query.sql}, Duration: ${queryDurationMillis}ms`);
85
+ });
86
+ res.json([{ id: 1, name: 'Alice' }]);
87
+ });
88
+
89
+ app.listen(3000, () => {
90
+ console.log('Express app listening on port 3000');
91
+ console.log('Lens UI available at http://localhost:3000/lens-dashboard');
92
+ });
93
+ ```
package/dist/adapter.cjs CHANGED
@@ -53,13 +53,19 @@ var ExpressAdapter = class extends import_core.LensAdapter {
53
53
  for (const watcher of this.getWatchers()) {
54
54
  switch (watcher.name) {
55
55
  case import_core.WatcherTypeEnum.REQUEST:
56
- this.watchRequests(watcher);
56
+ if (this.config.requestWatcherEnabled) {
57
+ this.watchRequests(watcher);
58
+ }
57
59
  break;
58
60
  case import_core.WatcherTypeEnum.QUERY:
59
- void this.watchQueries(watcher);
61
+ if (this.config.queryWatcher.enabled) {
62
+ void this.watchQueries(watcher);
63
+ }
60
64
  break;
61
65
  case import_core.WatcherTypeEnum.CACHE:
62
- void this.watchCache(watcher);
66
+ if (this.config.cacheWatcherEnabled) {
67
+ void this.watchCache(watcher);
68
+ }
63
69
  break;
64
70
  }
65
71
  }
@@ -119,11 +125,11 @@ var ExpressAdapter = class extends import_core.LensAdapter {
119
125
  watchRequests(requestWatcher) {
120
126
  if (!this.config.requestWatcherEnabled) return;
121
127
  this.app.use((req, res, next) => {
128
+ if (this.shouldIgnorePath(req.path)) return next();
122
129
  const context = {
123
130
  requestId: import_core.lensUtils.generateRandomUuid()
124
131
  };
125
132
  import_core.lensContext.run(context, () => {
126
- if (this.shouldIgnorePath(req.path)) return next();
127
133
  const start = process.hrtime();
128
134
  this.patchResponseMethods(res);
129
135
  res.on("finish", async () => {
package/dist/adapter.js CHANGED
@@ -25,13 +25,19 @@ var ExpressAdapter = class extends LensAdapter {
25
25
  for (const watcher of this.getWatchers()) {
26
26
  switch (watcher.name) {
27
27
  case WatcherTypeEnum.REQUEST:
28
- this.watchRequests(watcher);
28
+ if (this.config.requestWatcherEnabled) {
29
+ this.watchRequests(watcher);
30
+ }
29
31
  break;
30
32
  case WatcherTypeEnum.QUERY:
31
- void this.watchQueries(watcher);
33
+ if (this.config.queryWatcher.enabled) {
34
+ void this.watchQueries(watcher);
35
+ }
32
36
  break;
33
37
  case WatcherTypeEnum.CACHE:
34
- void this.watchCache(watcher);
38
+ if (this.config.cacheWatcherEnabled) {
39
+ void this.watchCache(watcher);
40
+ }
35
41
  break;
36
42
  }
37
43
  }
@@ -91,11 +97,11 @@ var ExpressAdapter = class extends LensAdapter {
91
97
  watchRequests(requestWatcher) {
92
98
  if (!this.config.requestWatcherEnabled) return;
93
99
  this.app.use((req, res, next) => {
100
+ if (this.shouldIgnorePath(req.path)) return next();
94
101
  const context = {
95
102
  requestId: lensUtils.generateRandomUuid()
96
103
  };
97
104
  lensContext.run(context, () => {
98
- if (this.shouldIgnorePath(req.path)) return next();
99
105
  const start = process.hrtime();
100
106
  this.patchResponseMethods(res);
101
107
  res.on("finish", async () => {
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ handleExceptions: () => handleExceptions,
33
34
  lens: () => lens
34
35
  });
35
36
  module.exports = __toCommonJS(index_exports);
@@ -56,13 +57,19 @@ var ExpressAdapter = class extends import_core.LensAdapter {
56
57
  for (const watcher of this.getWatchers()) {
57
58
  switch (watcher.name) {
58
59
  case import_core.WatcherTypeEnum.REQUEST:
59
- this.watchRequests(watcher);
60
+ if (this.config.requestWatcherEnabled) {
61
+ this.watchRequests(watcher);
62
+ }
60
63
  break;
61
64
  case import_core.WatcherTypeEnum.QUERY:
62
- void this.watchQueries(watcher);
65
+ if (this.config.queryWatcher.enabled) {
66
+ void this.watchQueries(watcher);
67
+ }
63
68
  break;
64
69
  case import_core.WatcherTypeEnum.CACHE:
65
- void this.watchCache(watcher);
70
+ if (this.config.cacheWatcherEnabled) {
71
+ void this.watchCache(watcher);
72
+ }
66
73
  break;
67
74
  }
68
75
  }
@@ -122,11 +129,11 @@ var ExpressAdapter = class extends import_core.LensAdapter {
122
129
  watchRequests(requestWatcher) {
123
130
  if (!this.config.requestWatcherEnabled) return;
124
131
  this.app.use((req, res, next) => {
132
+ if (this.shouldIgnorePath(req.path)) return next();
125
133
  const context = {
126
134
  requestId: import_core.lensUtils.generateRandomUuid()
127
135
  };
128
136
  import_core.lensContext.run(context, () => {
129
- if (this.shouldIgnorePath(req.path)) return next();
130
137
  const start = process.hrtime();
131
138
  this.patchResponseMethods(res);
132
139
  res.on("finish", async () => {
@@ -212,6 +219,10 @@ var ExpressAdapter = class extends import_core.LensAdapter {
212
219
  };
213
220
 
214
221
  // src/index.ts
222
+ var import_core3 = require("@lensjs/core");
223
+ var import_core4 = require("@lensjs/core");
224
+ var import_core5 = require("@lensjs/core");
225
+ var import_core6 = require("@lensjs/core");
215
226
  var defaultConfig = {
216
227
  appName: "Lens",
217
228
  enabled: true,
@@ -219,7 +230,8 @@ var defaultConfig = {
219
230
  ignoredPaths: [],
220
231
  onlyPaths: [],
221
232
  requestWatcherEnabled: true,
222
- cacheWatcherEnabled: false
233
+ cacheWatcherEnabled: false,
234
+ exceptionWatcherEnabled: true
223
235
  };
224
236
  var lens = async (config) => {
225
237
  const adapter = new ExpressAdapter({ app: config.app });
@@ -240,6 +252,10 @@ var lens = async (config) => {
240
252
  {
241
253
  enabled: mergedConfig.queryWatcher?.enabled,
242
254
  watcher: new import_core2.QueryWatcher()
255
+ },
256
+ {
257
+ enabled: mergedConfig.exceptionWatcherEnabled,
258
+ watcher: new import_core2.ExceptionWatcher()
243
259
  }
244
260
  ];
245
261
  defaultWatchers.forEach((watcher) => {
@@ -247,7 +263,6 @@ var lens = async (config) => {
247
263
  watchers.push(watcher.watcher);
248
264
  }
249
265
  });
250
- console.log("currentWatchers", watchers);
251
266
  const { ignoredPaths, normalizedPath } = import_core2.lensUtils.prepareIgnoredPaths(
252
267
  mergedConfig.path,
253
268
  mergedConfig.ignoredPaths
@@ -258,8 +273,31 @@ var lens = async (config) => {
258
273
  enabled: mergedConfig.enabled,
259
274
  basePath: normalizedPath
260
275
  });
276
+ return {
277
+ handleExceptions: (app) => handleExceptions({
278
+ app,
279
+ enabled: mergedConfig.exceptionWatcherEnabled,
280
+ watcher: watchers.find((w) => w.name === import_core3.WatcherTypeEnum.EXCEPTION)
281
+ })
282
+ };
261
283
  };
284
+ function handleExceptions({
285
+ app,
286
+ enabled,
287
+ watcher
288
+ }) {
289
+ if (!enabled || !watcher) return;
290
+ app.use(async (err, _, __, next) => {
291
+ await watcher.log({
292
+ ...import_core6.lensExceptionUtils.constructErrorObject(err),
293
+ requestId: import_core4.lensContext.getStore()?.requestId
294
+ });
295
+ next(err);
296
+ });
297
+ (0, import_core5.handleUncaughExceptions)(watcher);
298
+ }
262
299
  // Annotate the CommonJS export names for ESM import in node:
263
300
  0 && (module.exports = {
301
+ handleExceptions,
264
302
  lens
265
303
  });
package/dist/index.d.cts CHANGED
@@ -1,8 +1,15 @@
1
+ import { ExceptionWatcher } from '@lensjs/core';
1
2
  import { ExpressAdapterConfig } from './types/index.cjs';
2
- import 'express';
3
+ import { Application } from 'express';
3
4
  import '@lensjs/watchers';
4
- import '@lensjs/core';
5
5
 
6
- declare const lens: (config: ExpressAdapterConfig) => Promise<void>;
6
+ declare const lens: (config: ExpressAdapterConfig) => Promise<{
7
+ handleExceptions: (app: Application) => void;
8
+ }>;
9
+ declare function handleExceptions({ app, enabled, watcher, }: {
10
+ app: Application;
11
+ enabled: boolean;
12
+ watcher?: ExceptionWatcher;
13
+ }): void;
7
14
 
8
- export { lens };
15
+ export { handleExceptions, lens };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,15 @@
1
+ import { ExceptionWatcher } from '@lensjs/core';
1
2
  import { ExpressAdapterConfig } from './types/index.js';
2
- import 'express';
3
+ import { Application } from 'express';
3
4
  import '@lensjs/watchers';
4
- import '@lensjs/core';
5
5
 
6
- declare const lens: (config: ExpressAdapterConfig) => Promise<void>;
6
+ declare const lens: (config: ExpressAdapterConfig) => Promise<{
7
+ handleExceptions: (app: Application) => void;
8
+ }>;
9
+ declare function handleExceptions({ app, enabled, watcher, }: {
10
+ app: Application;
11
+ enabled: boolean;
12
+ watcher?: ExceptionWatcher;
13
+ }): void;
7
14
 
8
- export { lens };
15
+ export { handleExceptions, lens };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/index.ts
2
2
  import {
3
3
  CacheWatcher as CacheWatcher2,
4
+ ExceptionWatcher,
4
5
  Lens,
5
6
  lensUtils as lensUtils2,
6
7
  QueryWatcher as QueryWatcher2,
@@ -34,13 +35,19 @@ var ExpressAdapter = class extends LensAdapter {
34
35
  for (const watcher of this.getWatchers()) {
35
36
  switch (watcher.name) {
36
37
  case WatcherTypeEnum.REQUEST:
37
- this.watchRequests(watcher);
38
+ if (this.config.requestWatcherEnabled) {
39
+ this.watchRequests(watcher);
40
+ }
38
41
  break;
39
42
  case WatcherTypeEnum.QUERY:
40
- void this.watchQueries(watcher);
43
+ if (this.config.queryWatcher.enabled) {
44
+ void this.watchQueries(watcher);
45
+ }
41
46
  break;
42
47
  case WatcherTypeEnum.CACHE:
43
- void this.watchCache(watcher);
48
+ if (this.config.cacheWatcherEnabled) {
49
+ void this.watchCache(watcher);
50
+ }
44
51
  break;
45
52
  }
46
53
  }
@@ -100,11 +107,11 @@ var ExpressAdapter = class extends LensAdapter {
100
107
  watchRequests(requestWatcher) {
101
108
  if (!this.config.requestWatcherEnabled) return;
102
109
  this.app.use((req, res, next) => {
110
+ if (this.shouldIgnorePath(req.path)) return next();
103
111
  const context = {
104
112
  requestId: lensUtils.generateRandomUuid()
105
113
  };
106
114
  lensContext.run(context, () => {
107
- if (this.shouldIgnorePath(req.path)) return next();
108
115
  const start = process.hrtime();
109
116
  this.patchResponseMethods(res);
110
117
  res.on("finish", async () => {
@@ -190,6 +197,10 @@ var ExpressAdapter = class extends LensAdapter {
190
197
  };
191
198
 
192
199
  // src/index.ts
200
+ import { WatcherTypeEnum as WatcherTypeEnum2 } from "@lensjs/core";
201
+ import { lensContext as lensContext2 } from "@lensjs/core";
202
+ import { handleUncaughExceptions } from "@lensjs/core";
203
+ import { lensExceptionUtils } from "@lensjs/core";
193
204
  var defaultConfig = {
194
205
  appName: "Lens",
195
206
  enabled: true,
@@ -197,7 +208,8 @@ var defaultConfig = {
197
208
  ignoredPaths: [],
198
209
  onlyPaths: [],
199
210
  requestWatcherEnabled: true,
200
- cacheWatcherEnabled: false
211
+ cacheWatcherEnabled: false,
212
+ exceptionWatcherEnabled: true
201
213
  };
202
214
  var lens = async (config) => {
203
215
  const adapter = new ExpressAdapter({ app: config.app });
@@ -218,6 +230,10 @@ var lens = async (config) => {
218
230
  {
219
231
  enabled: mergedConfig.queryWatcher?.enabled,
220
232
  watcher: new QueryWatcher2()
233
+ },
234
+ {
235
+ enabled: mergedConfig.exceptionWatcherEnabled,
236
+ watcher: new ExceptionWatcher()
221
237
  }
222
238
  ];
223
239
  defaultWatchers.forEach((watcher) => {
@@ -225,7 +241,6 @@ var lens = async (config) => {
225
241
  watchers.push(watcher.watcher);
226
242
  }
227
243
  });
228
- console.log("currentWatchers", watchers);
229
244
  const { ignoredPaths, normalizedPath } = lensUtils2.prepareIgnoredPaths(
230
245
  mergedConfig.path,
231
246
  mergedConfig.ignoredPaths
@@ -236,7 +251,30 @@ var lens = async (config) => {
236
251
  enabled: mergedConfig.enabled,
237
252
  basePath: normalizedPath
238
253
  });
254
+ return {
255
+ handleExceptions: (app) => handleExceptions({
256
+ app,
257
+ enabled: mergedConfig.exceptionWatcherEnabled,
258
+ watcher: watchers.find((w) => w.name === WatcherTypeEnum2.EXCEPTION)
259
+ })
260
+ };
239
261
  };
262
+ function handleExceptions({
263
+ app,
264
+ enabled,
265
+ watcher
266
+ }) {
267
+ if (!enabled || !watcher) return;
268
+ app.use(async (err, _, __, next) => {
269
+ await watcher.log({
270
+ ...lensExceptionUtils.constructErrorObject(err),
271
+ requestId: lensContext2.getStore()?.requestId
272
+ });
273
+ next(err);
274
+ });
275
+ handleUncaughExceptions(watcher);
276
+ }
240
277
  export {
278
+ handleExceptions,
241
279
  lens
242
280
  };
@@ -11,6 +11,7 @@ type ExpressAdapterConfig = {
11
11
  onlyPaths?: RegExp[];
12
12
  requestWatcherEnabled?: boolean;
13
13
  cacheWatcherEnabled?: boolean;
14
+ exceptionWatcherEnabled?: boolean;
14
15
  queryWatcher?: {
15
16
  enabled: boolean;
16
17
  handler: QueryWatcherHandler;
@@ -11,6 +11,7 @@ type ExpressAdapterConfig = {
11
11
  onlyPaths?: RegExp[];
12
12
  requestWatcherEnabled?: boolean;
13
13
  cacheWatcherEnabled?: boolean;
14
+ exceptionWatcherEnabled?: boolean;
14
15
  queryWatcher?: {
15
16
  enabled: boolean;
16
17
  handler: QueryWatcherHandler;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensjs/express",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Express adapter for LensJs",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -20,14 +20,17 @@
20
20
  "dependencies": {
21
21
  "express": "^5.1.0",
22
22
  "@lensjs/date": "1.0.12",
23
- "@lensjs/watchers": "1.0.13",
24
- "@lensjs/core": "2.0.0",
25
- "@lensjs/typescript-config": "1.0.12"
23
+ "@lensjs/typescript-config": "1.0.12",
24
+ "@lensjs/watchers": "1.0.15",
25
+ "@lensjs/core": "2.1.1"
26
26
  },
27
27
  "devDependencies": {
28
- "@types/express": "^5.0.3"
28
+ "@types/express": "^5.0.3",
29
+ "supertest": "^7.1.4",
30
+ "vitest": "^3.2.4"
29
31
  },
30
32
  "scripts": {
31
- "build": "tsup --clean"
33
+ "build": "tsup --clean",
34
+ "test": "vitest run"
32
35
  }
33
36
  }