@lensjs/express 1.0.12 → 1.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 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,10 +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
+ }
64
+ break;
65
+ case import_core.WatcherTypeEnum.CACHE:
66
+ if (this.config.cacheWatcherEnabled) {
67
+ void this.watchCache(watcher);
68
+ }
60
69
  break;
61
70
  }
62
71
  }
@@ -90,6 +99,12 @@ var ExpressAdapter = class extends import_core.LensAdapter {
90
99
  return res.sendFile(path.join(uiPath, "index.html"));
91
100
  });
92
101
  }
102
+ async watchCache(watcher) {
103
+ if (!this.config.cacheWatcherEnabled) return;
104
+ import_core.lensEmitter.on("cache", async (data) => {
105
+ await watcher?.log(data);
106
+ });
107
+ }
93
108
  async watchQueries(watcher) {
94
109
  if (!this.config.queryWatcher.enabled) return;
95
110
  const handler = this.config.queryWatcher.handler;
@@ -98,7 +113,7 @@ var ExpressAdapter = class extends import_core.LensAdapter {
98
113
  const queryPayload = {
99
114
  query: query.query,
100
115
  duration: query.duration || "0 ms",
101
- createdAt: query.createdAt || (0, import_date.sqlDateTime)(),
116
+ createdAt: query.createdAt || (0, import_date.nowISO)(),
102
117
  type: query.type
103
118
  };
104
119
  await watcher?.log({
@@ -111,12 +126,17 @@ var ExpressAdapter = class extends import_core.LensAdapter {
111
126
  if (!this.config.requestWatcherEnabled) return;
112
127
  this.app.use((req, res, next) => {
113
128
  if (this.shouldIgnorePath(req.path)) return next();
114
- const start = process.hrtime();
115
- this.patchResponseMethods(res);
116
- res.on("finish", async () => {
117
- await this.finalizeRequestLog(req, res, requestWatcher, start);
129
+ const context = {
130
+ requestId: import_core.lensUtils.generateRandomUuid()
131
+ };
132
+ import_core.lensContext.run(context, () => {
133
+ const start = process.hrtime();
134
+ this.patchResponseMethods(res);
135
+ res.on("finish", async () => {
136
+ await this.finalizeRequestLog(req, res, requestWatcher, start);
137
+ });
138
+ next();
118
139
  });
119
- next();
120
140
  });
121
141
  }
122
142
  patchResponseMethods(res) {
@@ -158,7 +178,7 @@ var ExpressAdapter = class extends import_core.LensAdapter {
158
178
  const duration = import_core.lensUtils.prettyHrTime(process.hrtime(start));
159
179
  const logPayload = {
160
180
  request: {
161
- id: import_core.lensUtils.generateRandomUuid(),
181
+ id: import_core.lensContext.getStore()?.requestId || import_core.lensUtils.generateRandomUuid(),
162
182
  method: req.method,
163
183
  duration,
164
184
  path: req.originalUrl,
@@ -13,6 +13,7 @@ declare class ExpressAdapter extends LensAdapter {
13
13
  setup(): void;
14
14
  registerRoutes(routes: RouteDefinition[]): void;
15
15
  serveUI(uiPath: string, spaRoute: string, _dataToInject: Record<string, any>): void;
16
+ private watchCache;
16
17
  private watchQueries;
17
18
  private watchRequests;
18
19
  private patchResponseMethods;
package/dist/adapter.d.ts CHANGED
@@ -13,6 +13,7 @@ declare class ExpressAdapter extends LensAdapter {
13
13
  setup(): void;
14
14
  registerRoutes(routes: RouteDefinition[]): void;
15
15
  serveUI(uiPath: string, spaRoute: string, _dataToInject: Record<string, any>): void;
16
+ private watchCache;
16
17
  private watchQueries;
17
18
  private watchRequests;
18
19
  private patchResponseMethods;
package/dist/adapter.js CHANGED
@@ -2,12 +2,14 @@
2
2
  import {
3
3
  LensAdapter,
4
4
  lensUtils,
5
- WatcherTypeEnum
5
+ WatcherTypeEnum,
6
+ lensContext,
7
+ lensEmitter
6
8
  } from "@lensjs/core";
7
9
  import * as path from "path";
8
10
  import fs from "fs";
9
11
  import express from "express";
10
- import { nowISO, sqlDateTime } from "@lensjs/date";
12
+ import { nowISO } from "@lensjs/date";
11
13
  var ExpressAdapter = class extends LensAdapter {
12
14
  app;
13
15
  config;
@@ -23,10 +25,19 @@ var ExpressAdapter = class extends LensAdapter {
23
25
  for (const watcher of this.getWatchers()) {
24
26
  switch (watcher.name) {
25
27
  case WatcherTypeEnum.REQUEST:
26
- this.watchRequests(watcher);
28
+ if (this.config.requestWatcherEnabled) {
29
+ this.watchRequests(watcher);
30
+ }
27
31
  break;
28
32
  case WatcherTypeEnum.QUERY:
29
- void this.watchQueries(watcher);
33
+ if (this.config.queryWatcher.enabled) {
34
+ void this.watchQueries(watcher);
35
+ }
36
+ break;
37
+ case WatcherTypeEnum.CACHE:
38
+ if (this.config.cacheWatcherEnabled) {
39
+ void this.watchCache(watcher);
40
+ }
30
41
  break;
31
42
  }
32
43
  }
@@ -60,6 +71,12 @@ var ExpressAdapter = class extends LensAdapter {
60
71
  return res.sendFile(path.join(uiPath, "index.html"));
61
72
  });
62
73
  }
74
+ async watchCache(watcher) {
75
+ if (!this.config.cacheWatcherEnabled) return;
76
+ lensEmitter.on("cache", async (data) => {
77
+ await watcher?.log(data);
78
+ });
79
+ }
63
80
  async watchQueries(watcher) {
64
81
  if (!this.config.queryWatcher.enabled) return;
65
82
  const handler = this.config.queryWatcher.handler;
@@ -68,7 +85,7 @@ var ExpressAdapter = class extends LensAdapter {
68
85
  const queryPayload = {
69
86
  query: query.query,
70
87
  duration: query.duration || "0 ms",
71
- createdAt: query.createdAt || sqlDateTime(),
88
+ createdAt: query.createdAt || nowISO(),
72
89
  type: query.type
73
90
  };
74
91
  await watcher?.log({
@@ -81,12 +98,17 @@ var ExpressAdapter = class extends LensAdapter {
81
98
  if (!this.config.requestWatcherEnabled) return;
82
99
  this.app.use((req, res, next) => {
83
100
  if (this.shouldIgnorePath(req.path)) return next();
84
- const start = process.hrtime();
85
- this.patchResponseMethods(res);
86
- res.on("finish", async () => {
87
- await this.finalizeRequestLog(req, res, requestWatcher, start);
101
+ const context = {
102
+ requestId: lensUtils.generateRandomUuid()
103
+ };
104
+ lensContext.run(context, () => {
105
+ const start = process.hrtime();
106
+ this.patchResponseMethods(res);
107
+ res.on("finish", async () => {
108
+ await this.finalizeRequestLog(req, res, requestWatcher, start);
109
+ });
110
+ next();
88
111
  });
89
- next();
90
112
  });
91
113
  }
92
114
  patchResponseMethods(res) {
@@ -128,7 +150,7 @@ var ExpressAdapter = class extends LensAdapter {
128
150
  const duration = lensUtils.prettyHrTime(process.hrtime(start));
129
151
  const logPayload = {
130
152
  request: {
131
- id: lensUtils.generateRandomUuid(),
153
+ id: lensContext.getStore()?.requestId || lensUtils.generateRandomUuid(),
132
154
  method: req.method,
133
155
  duration,
134
156
  path: req.originalUrl,
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,10 +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
+ }
68
+ break;
69
+ case import_core.WatcherTypeEnum.CACHE:
70
+ if (this.config.cacheWatcherEnabled) {
71
+ void this.watchCache(watcher);
72
+ }
63
73
  break;
64
74
  }
65
75
  }
@@ -93,6 +103,12 @@ var ExpressAdapter = class extends import_core.LensAdapter {
93
103
  return res.sendFile(path.join(uiPath, "index.html"));
94
104
  });
95
105
  }
106
+ async watchCache(watcher) {
107
+ if (!this.config.cacheWatcherEnabled) return;
108
+ import_core.lensEmitter.on("cache", async (data) => {
109
+ await watcher?.log(data);
110
+ });
111
+ }
96
112
  async watchQueries(watcher) {
97
113
  if (!this.config.queryWatcher.enabled) return;
98
114
  const handler = this.config.queryWatcher.handler;
@@ -101,7 +117,7 @@ var ExpressAdapter = class extends import_core.LensAdapter {
101
117
  const queryPayload = {
102
118
  query: query.query,
103
119
  duration: query.duration || "0 ms",
104
- createdAt: query.createdAt || (0, import_date.sqlDateTime)(),
120
+ createdAt: query.createdAt || (0, import_date.nowISO)(),
105
121
  type: query.type
106
122
  };
107
123
  await watcher?.log({
@@ -114,12 +130,17 @@ var ExpressAdapter = class extends import_core.LensAdapter {
114
130
  if (!this.config.requestWatcherEnabled) return;
115
131
  this.app.use((req, res, next) => {
116
132
  if (this.shouldIgnorePath(req.path)) return next();
117
- const start = process.hrtime();
118
- this.patchResponseMethods(res);
119
- res.on("finish", async () => {
120
- await this.finalizeRequestLog(req, res, requestWatcher, start);
133
+ const context = {
134
+ requestId: import_core.lensUtils.generateRandomUuid()
135
+ };
136
+ import_core.lensContext.run(context, () => {
137
+ const start = process.hrtime();
138
+ this.patchResponseMethods(res);
139
+ res.on("finish", async () => {
140
+ await this.finalizeRequestLog(req, res, requestWatcher, start);
141
+ });
142
+ next();
121
143
  });
122
- next();
123
144
  });
124
145
  }
125
146
  patchResponseMethods(res) {
@@ -161,7 +182,7 @@ var ExpressAdapter = class extends import_core.LensAdapter {
161
182
  const duration = import_core.lensUtils.prettyHrTime(process.hrtime(start));
162
183
  const logPayload = {
163
184
  request: {
164
- id: import_core.lensUtils.generateRandomUuid(),
185
+ id: import_core.lensContext.getStore()?.requestId || import_core.lensUtils.generateRandomUuid(),
165
186
  method: req.method,
166
187
  duration,
167
188
  path: req.originalUrl,
@@ -198,27 +219,50 @@ var ExpressAdapter = class extends import_core.LensAdapter {
198
219
  };
199
220
 
200
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");
201
226
  var defaultConfig = {
202
227
  appName: "Lens",
203
228
  enabled: true,
204
229
  path: "/lens",
205
230
  ignoredPaths: [],
206
231
  onlyPaths: [],
207
- requestWatcherEnabled: true
232
+ requestWatcherEnabled: true,
233
+ cacheWatcherEnabled: false,
234
+ exceptionWatcherEnabled: true
208
235
  };
209
236
  var lens = async (config) => {
210
237
  const adapter = new ExpressAdapter({ app: config.app });
211
238
  const watchers = [];
212
239
  const mergedConfig = {
213
- ...config,
214
- ...defaultConfig
240
+ ...defaultConfig,
241
+ ...config
215
242
  };
216
- if (mergedConfig.requestWatcherEnabled) {
217
- watchers.push(new import_core2.RequestWatcher());
218
- }
219
- if (mergedConfig.queryWatcher?.enabled) {
220
- watchers.push(new import_core2.QueryWatcher());
221
- }
243
+ const defaultWatchers = [
244
+ {
245
+ enabled: mergedConfig.requestWatcherEnabled,
246
+ watcher: new import_core2.RequestWatcher()
247
+ },
248
+ {
249
+ enabled: mergedConfig.cacheWatcherEnabled,
250
+ watcher: new import_core2.CacheWatcher()
251
+ },
252
+ {
253
+ enabled: mergedConfig.queryWatcher?.enabled,
254
+ watcher: new import_core2.QueryWatcher()
255
+ },
256
+ {
257
+ enabled: mergedConfig.exceptionWatcherEnabled,
258
+ watcher: new import_core2.ExceptionWatcher()
259
+ }
260
+ ];
261
+ defaultWatchers.forEach((watcher) => {
262
+ if (watcher.enabled) {
263
+ watchers.push(watcher.watcher);
264
+ }
265
+ });
222
266
  const { ignoredPaths, normalizedPath } = import_core2.lensUtils.prepareIgnoredPaths(
223
267
  mergedConfig.path,
224
268
  mergedConfig.ignoredPaths
@@ -229,8 +273,31 @@ var lens = async (config) => {
229
273
  enabled: mergedConfig.enabled,
230
274
  basePath: normalizedPath
231
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
+ };
232
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
+ }
233
299
  // Annotate the CommonJS export names for ESM import in node:
234
300
  0 && (module.exports = {
301
+ handleExceptions,
235
302
  lens
236
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,5 +1,7 @@
1
1
  // src/index.ts
2
2
  import {
3
+ CacheWatcher as CacheWatcher2,
4
+ ExceptionWatcher,
3
5
  Lens,
4
6
  lensUtils as lensUtils2,
5
7
  QueryWatcher as QueryWatcher2,
@@ -10,12 +12,14 @@ import {
10
12
  import {
11
13
  LensAdapter,
12
14
  lensUtils,
13
- WatcherTypeEnum
15
+ WatcherTypeEnum,
16
+ lensContext,
17
+ lensEmitter
14
18
  } from "@lensjs/core";
15
19
  import * as path from "path";
16
20
  import fs from "fs";
17
21
  import express from "express";
18
- import { nowISO, sqlDateTime } from "@lensjs/date";
22
+ import { nowISO } from "@lensjs/date";
19
23
  var ExpressAdapter = class extends LensAdapter {
20
24
  app;
21
25
  config;
@@ -31,10 +35,19 @@ var ExpressAdapter = class extends LensAdapter {
31
35
  for (const watcher of this.getWatchers()) {
32
36
  switch (watcher.name) {
33
37
  case WatcherTypeEnum.REQUEST:
34
- this.watchRequests(watcher);
38
+ if (this.config.requestWatcherEnabled) {
39
+ this.watchRequests(watcher);
40
+ }
35
41
  break;
36
42
  case WatcherTypeEnum.QUERY:
37
- void this.watchQueries(watcher);
43
+ if (this.config.queryWatcher.enabled) {
44
+ void this.watchQueries(watcher);
45
+ }
46
+ break;
47
+ case WatcherTypeEnum.CACHE:
48
+ if (this.config.cacheWatcherEnabled) {
49
+ void this.watchCache(watcher);
50
+ }
38
51
  break;
39
52
  }
40
53
  }
@@ -68,6 +81,12 @@ var ExpressAdapter = class extends LensAdapter {
68
81
  return res.sendFile(path.join(uiPath, "index.html"));
69
82
  });
70
83
  }
84
+ async watchCache(watcher) {
85
+ if (!this.config.cacheWatcherEnabled) return;
86
+ lensEmitter.on("cache", async (data) => {
87
+ await watcher?.log(data);
88
+ });
89
+ }
71
90
  async watchQueries(watcher) {
72
91
  if (!this.config.queryWatcher.enabled) return;
73
92
  const handler = this.config.queryWatcher.handler;
@@ -76,7 +95,7 @@ var ExpressAdapter = class extends LensAdapter {
76
95
  const queryPayload = {
77
96
  query: query.query,
78
97
  duration: query.duration || "0 ms",
79
- createdAt: query.createdAt || sqlDateTime(),
98
+ createdAt: query.createdAt || nowISO(),
80
99
  type: query.type
81
100
  };
82
101
  await watcher?.log({
@@ -89,12 +108,17 @@ var ExpressAdapter = class extends LensAdapter {
89
108
  if (!this.config.requestWatcherEnabled) return;
90
109
  this.app.use((req, res, next) => {
91
110
  if (this.shouldIgnorePath(req.path)) return next();
92
- const start = process.hrtime();
93
- this.patchResponseMethods(res);
94
- res.on("finish", async () => {
95
- await this.finalizeRequestLog(req, res, requestWatcher, start);
111
+ const context = {
112
+ requestId: lensUtils.generateRandomUuid()
113
+ };
114
+ lensContext.run(context, () => {
115
+ const start = process.hrtime();
116
+ this.patchResponseMethods(res);
117
+ res.on("finish", async () => {
118
+ await this.finalizeRequestLog(req, res, requestWatcher, start);
119
+ });
120
+ next();
96
121
  });
97
- next();
98
122
  });
99
123
  }
100
124
  patchResponseMethods(res) {
@@ -136,7 +160,7 @@ var ExpressAdapter = class extends LensAdapter {
136
160
  const duration = lensUtils.prettyHrTime(process.hrtime(start));
137
161
  const logPayload = {
138
162
  request: {
139
- id: lensUtils.generateRandomUuid(),
163
+ id: lensContext.getStore()?.requestId || lensUtils.generateRandomUuid(),
140
164
  method: req.method,
141
165
  duration,
142
166
  path: req.originalUrl,
@@ -173,27 +197,50 @@ var ExpressAdapter = class extends LensAdapter {
173
197
  };
174
198
 
175
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";
176
204
  var defaultConfig = {
177
205
  appName: "Lens",
178
206
  enabled: true,
179
207
  path: "/lens",
180
208
  ignoredPaths: [],
181
209
  onlyPaths: [],
182
- requestWatcherEnabled: true
210
+ requestWatcherEnabled: true,
211
+ cacheWatcherEnabled: false,
212
+ exceptionWatcherEnabled: true
183
213
  };
184
214
  var lens = async (config) => {
185
215
  const adapter = new ExpressAdapter({ app: config.app });
186
216
  const watchers = [];
187
217
  const mergedConfig = {
188
- ...config,
189
- ...defaultConfig
218
+ ...defaultConfig,
219
+ ...config
190
220
  };
191
- if (mergedConfig.requestWatcherEnabled) {
192
- watchers.push(new RequestWatcher2());
193
- }
194
- if (mergedConfig.queryWatcher?.enabled) {
195
- watchers.push(new QueryWatcher2());
196
- }
221
+ const defaultWatchers = [
222
+ {
223
+ enabled: mergedConfig.requestWatcherEnabled,
224
+ watcher: new RequestWatcher2()
225
+ },
226
+ {
227
+ enabled: mergedConfig.cacheWatcherEnabled,
228
+ watcher: new CacheWatcher2()
229
+ },
230
+ {
231
+ enabled: mergedConfig.queryWatcher?.enabled,
232
+ watcher: new QueryWatcher2()
233
+ },
234
+ {
235
+ enabled: mergedConfig.exceptionWatcherEnabled,
236
+ watcher: new ExceptionWatcher()
237
+ }
238
+ ];
239
+ defaultWatchers.forEach((watcher) => {
240
+ if (watcher.enabled) {
241
+ watchers.push(watcher.watcher);
242
+ }
243
+ });
197
244
  const { ignoredPaths, normalizedPath } = lensUtils2.prepareIgnoredPaths(
198
245
  mergedConfig.path,
199
246
  mergedConfig.ignoredPaths
@@ -204,7 +251,30 @@ var lens = async (config) => {
204
251
  enabled: mergedConfig.enabled,
205
252
  basePath: normalizedPath
206
253
  });
254
+ return {
255
+ handleExceptions: (app) => handleExceptions({
256
+ app,
257
+ enabled: mergedConfig.exceptionWatcherEnabled,
258
+ watcher: watchers.find((w) => w.name === WatcherTypeEnum2.EXCEPTION)
259
+ })
260
+ };
207
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
+ }
208
277
  export {
278
+ handleExceptions,
209
279
  lens
210
280
  };
@@ -10,6 +10,8 @@ type ExpressAdapterConfig = {
10
10
  ignoredPaths?: RegExp[];
11
11
  onlyPaths?: RegExp[];
12
12
  requestWatcherEnabled?: boolean;
13
+ cacheWatcherEnabled?: boolean;
14
+ exceptionWatcherEnabled?: boolean;
13
15
  queryWatcher?: {
14
16
  enabled: boolean;
15
17
  handler: QueryWatcherHandler;
@@ -10,6 +10,8 @@ type ExpressAdapterConfig = {
10
10
  ignoredPaths?: RegExp[];
11
11
  onlyPaths?: RegExp[];
12
12
  requestWatcherEnabled?: boolean;
13
+ cacheWatcherEnabled?: boolean;
14
+ exceptionWatcherEnabled?: boolean;
13
15
  queryWatcher?: {
14
16
  enabled: boolean;
15
17
  handler: QueryWatcherHandler;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensjs/express",
3
- "version": "1.0.12",
3
+ "version": "1.2.0",
4
4
  "description": "Express adapter for LensJs",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -19,15 +19,18 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "express": "^5.1.0",
22
- "@lensjs/date": "1.0.12",
23
- "@lensjs/core": "1.0.12",
24
- "@lensjs/watchers": "1.0.12",
25
- "@lensjs/typescript-config": "1.0.12"
22
+ "@lensjs/core": "2.1.0",
23
+ "@lensjs/typescript-config": "1.0.12",
24
+ "@lensjs/watchers": "1.0.14",
25
+ "@lensjs/date": "1.0.12"
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
  }